Search code examples
javascriptangulartypescripturl-routingangular-routing

Angular routing including auth guard and redirections


I have an angular app and want to implement client side routing. I have 3 components: login, chat and admin. Access to admin and chat is restricted by an auth guard. Ideally the routing behavior should be:

  • click login -> route to login and redirect to admin
  • click admin or chat -> route to login and redirect on successful login to the clicked on (admin or chat respectlively)

I managed to setup the redirections nearly correct, but the redirection when clicking login still depends on where I clicked before/last. Meaning that if the user clicks on login it will goto login and on successful login it redirects to chat. The user then logs out and clicks login, it goes to login but redirects to chat instead of admin, which I don't want. Clicks on login should always go to admin regardless of which route was active in past.

How can I achieve this?

Thanks.

app.component

<nav>
  <ol>
    <li><a routerLink="/login">Login</a></li>
    <li><a routerLink="/admin">Admin</a></li>
    <li><a routerLink="/chat">Chat</a></li>
  </ol>
</nav>
<router-outlet></router-outlet>
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
}

Login component

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import {AuthService} from "../auth.service";

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  email: string;
  password: string;
  loginMessage: string;
  loginForm: FormGroup;

  constructor(
    private http: HttpClient,
  ) { }

  ngOnInit() {
    this.loginForm = new FormGroup({
      'email': new FormControl(this.email, [
        Validators.required,
        Validators.email
      ]),
      'password': new FormControl(this.password, [
        Validators.required,
        Validators.minLength(2)
      ])
    });
    console.log('init');
  }
  logout(): void {
    this.authService.loggedIn = false;
  }
  login(): void {
    if (!this.isValidInput()) { return; }

    const data = {email: this.email, pass: this.password};
    this.authService.login('localhost:3000/login', data).subscribe((response: any) => {
      this.loginForm.reset();
      this.authService.loggedIn=true;
      let redirect = this.authService.redirecturl ? this.router.parseUrl(this.authService.redirecturl) : '/admin';
      this.router.navigateByUrl(redirect);  
    });
  }

  isValidInput(): Boolean {
    if (this.loginForm.valid) {
      this.email = this.loginForm.get('email').value;
      this.password = this.loginForm.get('password').value;
      return true;
    }
    return false;
  }
}
<form [formGroup]="loginForm">
  <!-- this div is just for debugging purpose -->
  <div id="displayFormValues">
    Value: {{loginForm.value | json}}
  </div>

  <label for="email"><b>Email</b></label>
  <input id="email" type="email" formControlName="email" email="true" required>
  <label for="password"><b>Password</b></label>
  <input id="password" type="password" formControlName="password" required>
  <button (click)="login()" routerLink="/admin" routerLinkActive="active">Login</button>
  <div id="loginMessage">{{loginMessage}}</div>
</form>

admin component

<p>admin works!</p>
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.css']
})
export class AdminComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

chat component

<p>chat works!</p>
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-chat',
  templateUrl: './chat.component.html',
  styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

authgauard

import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor() {
  }

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    let url: string = state.url;

    if (this.authService.isLoggedIn()) {
      return true;
    } else {
      this.authService.redirecturl = url;
      this.router.navigate(['/login']);
      return false;
    }
  }

}

app-routing module

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ChatComponent } from './chat/chat.component';
import { AdminComponent } from './admin/admin.component';
import { LoginComponent } from './login/login.component';
import { AuthGuard } from './auth.guard';

const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'chat',
    component: ChatComponent,
    canActivate: [AuthGuard]
  }
];

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

auth service

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { throwError, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

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

  redirecturl: string; // used for redirect after successful login
  username: string;
  loginMessage: string;
  greeting = 'Hello guest!';
  loggedIn = false;
  config = {
    serverHost: 'localhost',
    serverPort: 3000,
    loginRoute: 'login',
    standardGreeting: `Hello guest!`,
    standardUsername: 'Guest'
  };

  constructor(private http: HttpClient) { }

  login(loginUrl: any, body: { pass: string }) {
    return this.http.post(loginUrl, body, httpOptions)
      .pipe(
        catchError(this.handleError)
      );
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      console.error('An error occurred:', error.error.message);
    } else {
      console.error(
        `Backend returned code ${error.status}, ` +
        `body was: ${error.error}`);
    }
    return throwError(
      'Something bad happened; please try again later.');
  }

  isLoggedIn(): boolean {
    return this.loggedIn;
  }
  }
}

Solution

  • Instead of doing this <button (click)="login()" routerLink="/admin" routerLinkActive="active">Login</button> in html put redirection url in typescript like this.

        login(): void {
            if (!this.isValidInput()) { return; }
    
            const data = {email: this.email, pass: this.password};
            this.authService.login('localhost:3000/login', data).subscribe((response: any) => { 
           if(response.isSuccess){
              this.loginForm.reset();
              this.authService.loggedIn=true;
               if(!this.authService.redirectUrl){
                this.router.navigateByUrl('/admin');  
                } else{
                 this.router.navigateByUrl(this.authService.redirectUrl);  
                }
             }
            });
          }
    

    and If you are navigating to Login URL then please remove redirectUrl other wise it will always redirect to last visited page.

    EDIT

    In App.component.html you are navigating to login using routerlink instead of that use this

    <nav>
      <ol>
        <li><a (click)='redirectToLogin()'>Login</a></li>
        <li><a routerLink="/admin">Admin</a></li>
        <li><a routerLink="/chat">Chat</a></li>
      </ol>
    </nav>
    <router-outlet></router-outlet>
    

    and in app.component.ts use this

    redirectToLogin(){
        this.authService.redirectUrl = null;
        this.router.navigateByUrl('/login');
    }