Securing routes in Angular without relying on LocalStorage or Cookies by implementing an Auth Guard

Currently, I am working on implementing an authentication guard in Angular. Instead of the conventional method of checking local storage or cookies to verify user authentication, I am utilizing API endpoints that respond with 200 OK if a httponly cookie containing the JWT is attached to the request, and 401 Unauthorized otherwise.

This is the approach I have taken:

// auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(
    private httpClient: HttpClient,
  ) {}

  login(username: string, password: string) {
    return this.httpClient.post('/users/login', { username, password });
  }

  isLoggedIn(): boolean {
    this.httpClient.get('/users/check').subscribe({
      next: (data) => {
        return true;
      },
      error: () => {
        return false;
      },
    });

    return false;
  }
}
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): boolean {
    if (!this.authService.isLoggedIn()) {
      this.router.navigate(['login']);
      return false;
    }

    return true;
  }
}
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { LayoutComponent } from './layout/layout.component';
import { ProjectsComponent } from './projects/projects.component';
import { AuthGuard } from './core/auth/auth.guard';

const routes: Routes = [
  {
    path: '',
    component: LayoutComponent,
    canActivate: [AuthGuard],
    children: [{ path: 'projects', component: ProjectsComponent }],
  },
  { path: 'login', component: LoginComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

However, there seems to be a problem once a user logs in and the authentication cookie is set. The isLoggedIn() function still returns false, redirecting the user back to the login page. My assumption is that I may not be handling the observable from httpclient correctly, causing it to return false; before the request finishes?

Any assistance would be greatly appreciated. Thank you in advance!

Answer ā„–1

This situation highlights a common issue with handling asynchronous operations and understanding function scoping...

When you encounter this scenario, two main issues become apparent:

isLoggedIn(): boolean {
    this.httpClient.get('/users/check').subscribe({
      next: (data) => {
        return true; // problem 02
      },
      error: () => {
        return false; // problem 02
      },
    });
    
    return false; // problem 01
  }
  • Problem 01: The function always returns false, as it is the only return statement within the scope of the isLoggedIn function. This is a classic async issue.

  • Problem 02: Although the callback functions in the subscription are executed, their returned values are not utilized correctly. This signifies a misunderstanding of function scoping. The return statements labeled "Problem 02" actually belong to the scope of the subscription callbacks, not the isLoggedIn function.

To address these issues:

  1. You should return the observable and transform the result to true or false using a pipe without subscribing:
isLoggedIn() {
  return this.httpClient.get('/users/check').pipe(
    map(() => true),
    catchError(() => of(false))
  )
}
  1. In your guard (where a similar issue may arise as in your service), refrain from subscribing and rely on the caller of the Guard to handle the subscription (angular internal):
canActivate() {
  return this.authService.isLoggedIn().pipe(take(1), tap(loggedIn => {
    if (!loggedIn) this.router.navigate(['login']);
  }));
}

Answer ā„–2

Give this a try, it has been effective for me.

Within the isLoggedIn method:

isLoggedIn(): boolean {
        return this.httpClient.get('/users/check').subscribe({
          next: (data) => {
            return true;
          },
          error: () => {
            return false;
          },
        });
      }

Within the canActivate Guard :

 canActivate(){
    return this.authService.isLoggedIn().subscribe(res => {
        if (!res) this.router.navigate(['login']);
        else return true;    
    });
 }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unable to extract numerical value from object using Dropdown (Angular 4)

When I retrieve data from an API, everything works smoothly except when I try to bind my JSON option number value into the [value] tag. Here's an example: SUCCESSFUL (data retrieved from API is selected in the option) <select [(ngModel)]="data.fr ...

Retrieve a variable in a child component by passing it down from the parent component and triggering it from the parent

I'm struggling to grasp this concept. In my current scenario, I pass two variables to a component like this: <app-selectcomp [plid]="plid" [codeId]="selectedCode" (notify)="getCompFromChild($event)"></app-select ...

Eliminating data type from union in Typescript

I have a specific type that I collect from various other types: type CustomType = { id: string; foo: (string | Type1)[]; bar: (string | Type2)[]; baz: string | Type3 | null; description: string | null; } I am interested in refining thi ...

The Angular 2 project, built with the CLI tool, has been transformed into an npm

We have a project in the works that involves creating a large application using angular 2. This project consists of one main parent angular 2 application and three separate sub-child applications that are unrelated to each other. Each of these sub-child ...

Can a map key value be converted into a param object?

I have a map containing key-value pairs as shown below: for (let controller of this.attributiFormArray.controls) { attributiAttivitaMap.set(controller.get('id').value, { value: controller.get('valoreDefault').value, mandatory ...

What strategies work well when it comes to developing translation files in Angular?

Currently, I am involved in a front-end project using Angular. For translation implementation, I am looking for the most effective approach to creating translation files. Instead of having a file per language, I am considering creating a translation file f ...

Defining a structure for an entity in which its attributes distinguish between different data types and an array combination

I strongly believe that the best way to showcase this concept is through a clear example: enum EventType { A, B, C }; type MyEvent = [EventType.A, number] | [EventType.B, string] | [EventType.C, number, string]; const eventsGrouped: Record<Event ...

Tips for modifying HTML template code within a Typescript document using Atom

There appears to be a current trend in Angular development where the template code is embedded within the Angular component, usually found in a Typescript or Javascript file. However, when attempting this approach, I noticed that I am missing html syntax ...

Having trouble figuring out how to display a tooltip using the show() method in @teamhive/ngx-tooltip?

I am looking for a way to toggle this tooltip on and off as I navigate with my mouse, especially because it is attached to nested elements. Although I can detect cursor movement for other purposes, I need a solution for controlling the tooltip display. Ac ...

Encountering a schematic flow error while utilizing the angular cli for project creation

Currently, I am in the process of learning Angular. I have successfully installed Node.js version 14 and AngularCLI, but when I attempt to create a new project using the command 'ng new proj', an error is encountered. During the package install ...

Steps for displaying a loader after refreshing data using queryClient.invalidateQueries:1. Utilize the query

I am currently working on a project where I need to redirect to a specific page after updating an item. While the code is functioning correctly, I have encountered an issue with the loader not displaying. export const useUpdateStatusArchiveSurvey = () => ...

disappearing of vue event on single file component HTML element

I'm currently working on an ElectronJs project with Electron Forge, using the Webpack + Typescript template project In addition to that, I've integrated Vue and vue-loader for webpack in order to utilize Single File Component (SFC) files: { ...

What steps can be taken for TypeScript to identify unsafe destructuring situations?

When working with TypeScript, it's important to prevent unsafe destructuring code that can lead to runtime errors. In the example below, trying to destructure undefined can cause a destructuring error. How can we ensure TypeScript does not compile suc ...

Jasmine Destiny - Error Encountered: macroTask 'setTimeout': unable to switch to 'active' state, expecting 'planned' state, but it was 'notScheduled'

I am currently using Angular 7 with the Zone.js version of approximately ~0.8.26. Inside my test.ts file, I have included the import statement for 'zone.js/dist/zone-testing'. Below is a snippet from my spec file: import { HttpClientTestingModul ...

Enhance your TypeScript React development with NeoVim

As a newcomer to vim, I decided to test my configuration with react and typescript. To do this, I created a simple demo app using the command npx create-react-app demo --template typescript. Next, I opened the directory in neovim by running nvim .. However ...

Clearing dropdown values when navigating back on a page in Angular 6: A step-by-step guide

Image Within the App Component, I have implemented a dropdown list. When an option is selected from the dropdown, it should navigate to another page using routing. Additionally, upon clicking the back button, it should return the user to the app compone ...

Testing Angular: Inability to achieve full code coverage for Ternary branch in unit testing

Currently, I am attempting to test a ternary branch that utilizes the window.location property. @Injectable() export class ABCService { private hostUrl = (window.location.host.indexOf('localhost') > -1) ? 'example.com' : window.lo ...

Creating a return type in TypeScript for a React Higher Order Component that is compatible with a

Currently utilizing React Native paired with TypeScript. Developed a HOC that functions as a decorator to add a badge to components: import React, { Component, ComponentClass, ReactNode } from "react"; import { Badge, BadgeProps } from "../Badge"; functi ...

Oops! Property 'month' cannot be set on undefined value due to a TypeError

Despite not receiving any errors from Visual Studio Code, Iā€™m encountering an error in Chrome's console. Below is the code snippet from my interfaces.ts file: export interface Data1{ month: string; employeeName: string; date: string; employmentSta ...

The error message "TypeError: (0 , N.createContext) is not a function" indicates that

I'm currently in the process of developing a cutting-edge application using Next.js 14, TypeScript, and Telegram Open Network. However, I've hit a roadblock while attempting to incorporate the TONConnectUIProvider at the root of my app. Upon run ...