Home > Software engineering >  Authentication guard with angular
Authentication guard with angular

Time:01-21

can anyone help me with this. I am trying to implement an authentication guard for on my LogIn component. This guard should allow users to access the dashboard only in the following conditions: If there is a session token defined in local storage and if the token is valid (validity must be checked through the GET call that checks the session token). If the token is not valid, I want to delete the token from the local storage and redirect to login page. Here is what I've done so far:

core/guard/auth.guard.ts

import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
import { Observable } from 'rxjs';
import {AuthService} from "../services/auth.service";

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor( private service: AuthService, private router:Router) {
  }
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    if(this.service.isLoggedIn()){
      return true;
    }else {
      return this.router.navigate(["login"]);
    }
  }
}`

app.module.ts

   import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { KpiComponent } from './kpi/kpi.component';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { PieComponent } from './kpi/pie/pie.component';
import { BarComponent } from './kpi/bar/bar.component';
import { MainTemplateComponent } from './atomic/main-template/main-template.component';
import { LoginPageComponent } from './atomic/organisms/login-page/login-page.component';
import { DashboardPageComponent } from './atomic/organisms/dashboard-page/dashboard-page.component';
import { NavbarComponent } from './navbar/navbar.component';
import { MatMenuModule } from '@angular/material/menu';
import { ReactiveFormsModule } from '@angular/forms';
import { reducers } from './store';
import { HrMaterialModule } from './material/material.module';


@NgModule({
  declarations: [
    AppComponent,
    KpiComponent,
    PieComponent,
    BarComponent,
    MainTemplateComponent,
    LoginPageComponent,
    DashboardPageComponent,
    NavbarComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot(reducers, {}),
    StoreDevtoolsModule.instrument(),
    BrowserAnimationsModule,
    MatCardModule,
    MatButtonModule,
    MatIconModule,
    ReactiveFormsModule,
    MatMenuModule,
    HrMaterialModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

navbar.component.ts

<div >
    <a routerLink="/dashboard">
        <img src="../../assets/imbus-logo.svg" alt="imbus-logo">
    </a>
    <div >
        <div >
            <a routerLink="/dashboard"  [matMenuTriggerFor]="menu">
                <img src="../../assets/manage-account.svg" alt="">
            </a>
            <mat-menu #menu="matMenu" xPosition="before">
                <button mat-menu-item>
                    <mat-icon>settings</mat-icon>
                    <span>My Settings</span>
                  </button>
                  <button mat-menu-item  routerLink="/login">
                    <mat-icon>keyboard_backspace</mat-icon>
                    <span>Logout</span>
                  </button>
            </mat-menu>
        </div>

        <a routerLink="/dashboard" >
            <img src="../../assets/setttings.svg" alt="">
        </a>
    </div>
</div>

login-page.component.html

<div >
  <div >
    <div >
      <form [formGroup]="loginForm" (ngSubmit)="loginUser()" >
        <div >
          <img src="../../../../assets/imbus-logo.svg" alt="imbus-logo" />
        </div>
        <div >
          <input #email
            formControlName="email"
            type="text"
            
            placeholder="User login"
          />
          <br />
          <span *ngIf="user && user.invalid && user.touched" style="color: red"
            >User name is required.</span
          >
        </div>
        <div >
          <input
            formControlName="password"
            type="{{ type }}"
            
            placeholder="Password"
          />

          <mat-icon
            (click)="togglePassword($event)"
            *ngIf="showPassword"
            svgIcon="hr:hide-text"
            
          ></mat-icon>
          <mat-icon
            (click)="togglePassword($event)"
            *ngIf="!showPassword"
            svgIcon="hr:show-text"
            
          ></mat-icon>

          <span
            *ngIf="password && password.invalid && password.touched"
            style="color: red"
          >
            Password is required.</span
          >
        </div>

        <button [disabled]="loginForm.invalid" >
          <span  (click)="proceedlogin(name.value)">LogIn</span>
        </button>
      </form>
      <p id="copy-rights-text" >&copy; IMBUS HR DASHBOARD 2023</p>
    </div>
  </div>
</div>

login-page.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import {Router} from "@angular/router";
import {AuthService} from "../../../core/services/auth.service";
@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.scss'],
})
export class LoginPageComponent implements OnInit {
  public showPassword = false;
  public type = 'password';

  constructor( private service: AuthService,
    private router: Router) {
    localStorage.clear();
  }

  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required]),
    password: new FormControl('', [
      Validators.required,
      Validators.minLength(8),
    ]),
  });

  loginUser() {
    console.warn(this.loginForm.value);
    if (this.loginForm.valid) {
      console.log('Form Submitted!', this.loginForm.value);
    }
  }


  ngOnInit():void {}
  proceedlogin(email:any){
    localStorage.setItem("user", email);
    this.router.navigate(["/dashboard"])
  }

  get user() {
    return this.loginForm.get('user');
  }
  get password() {
    return this.loginForm.get('password');
  }
  togglePassword(e: Event) {
    e.stopPropagation();
    e.preventDefault();
    this.showPassword = !this.showPassword;
    this.type = this.showPassword ? 'text' : 'password';
  }
}

core/service/authservice.ts

import { Injectable } from '@angular/core';


@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor() {}

  isLoggedIn(){
    return localStorage.getItem("user")!=null;
  }
}

I don't understand the logic on how to validate the token.

CodePudding user response:

You don't need the HTTP interceptor to implement an authentication guard

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuardService implements CanActivate {
  constructor(public auth: AuthService, public router: Router) {}
  canActivate(): boolean {
    if (!this.auth.isLoggedIn()) {
      this.router.navigate(['login']);
      return false;
    }
    return true;
  }
}

Once you have this you can use it in the routes file

import { Routes, CanActivate } from '@angular/router';
import { 
  AuthGuardService
} from './auth/auth-guard.service';
export const ROUTES: Routes = [
  { 
    path: '',
    component: TheComponent,
    canActivate: [AuthGuardService] 
  }
];

CodePudding user response:

The AuthGuardService from @SeF is the right way. Independently from it you need a HttpInterceptor, too I think. This one adds Bearer XXX to your header and will try to refresh your token (if needed). If both fails you can set a value in your AuthService to false as example and your AuthGuardService will redirect the user automatically.

import { Injectable } from "@angular/core";
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from "@angular/common/http";
import { AuthenticationService } from "../authentication.service";
import { Observable } from "rxjs/Observable";
import { BehaviorSubject } from "rxjs/BehaviorSubject";

@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
    private refreshTokenInProgress = false;
    // Refresh Token Subject tracks the current token, or is null if no token is currently
    // available (e.g. refresh pending).
    private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
        null
    );
    constructor(public auth: AuthenticationService) {}

    intercept(
        request: HttpRequest<any>,
        next: HttpHandler
    ): Observable<HttpEvent<any>> {
        return next.handle(request).catch(error => {
            // We don't want to refresh token for some requests like login or refresh token itself
            // So we verify url and we throw an error if it's the case
            if (
                request.url.includes("refreshtoken") ||
                request.url.includes("login")
            ) {
                // We do another check to see if refresh token failed
                // In this case we want to logout user and to redirect it to login page

                if (request.url.includes("refreshtoken")) {
                    this.auth.logout();
                }

                return Observable.throw(error);
            }

            // If error status is different than 401 we want to skip refresh token
            // So we check that and throw the error if it's the case
            if (error.status !== 401) {
                return Observable.throw(error);
            }

            if (this.refreshTokenInProgress) {
                // If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value
                // – which means the new token is ready and we can retry the request again
                return this.refreshTokenSubject
                    .filter(result => result !== null)
                    .take(1)
                    .switchMap(() => next.handle(this.addAuthenticationToken(request)));
            } else {
                this.refreshTokenInProgress = true;

                // Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
                this.refreshTokenSubject.next(null);

                // Call auth.refreshAccessToken(this is an Observable that will be returned)
                return this.auth
                    .refreshAccessToken()
                    .switchMap((token: any) => {
                        //When the call to refreshToken completes we reset the refreshTokenInProgress to false
                        // for the next time the token needs to be refreshed
                        this.refreshTokenInProgress = false;
                        this.refreshTokenSubject.next(token);

                        return next.handle(this.addAuthenticationToken(request));
                    })
                    .catch((err: any) => {
                        this.refreshTokenInProgress = false;

                        this.auth.logout();
                        return Observable.throw(error);
                    });
            }
        });
    }

    addAuthenticationToken(request) {
        // Get access token from Local Storage
        const accessToken = this.auth.getAccessToken();

        // If access token is null this means that user is not logged in
        // And we return the original request
        if (!accessToken) {
            return request;
        }

        // We clone the request, because the original request is immutable
        return request.clone({
            setHeaders: {
                Authorization: this.auth.getAccessToken()
            }
        });
    }
}

Don't forgot to change the links in the interceptor. It will be looking for refreshtoken inside the URL if it fails the second time.

  • Related