profile
viewpoint

Ask questionsRedirect to page after login in Angular Guard

I have the following angular-oauth2-oidc configuration (and I am using Identity Server 4):

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

import { AuthConfig, OAuthService, JwksValidationHandler } from 'angular-oauth2-oidc';

export const authConfig: AuthConfig = {
  issuer: 'https://localhost:5005',
  redirectUri: window.location.origin + "/index.html",
  clientId: 'spa',
  responseType: 'code',
  scope: 'openid profile email offline_access api',
  showDebugInformation: true,
  clearHashAfterLogin: false
};

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})

export class AppComponent {
  constructor(private authService: OAuthService) {
    this.authService.configure(authConfig);
    this.authService.tokenValidationHandler = new JwksValidationHandler();
    this.authService.loadDiscoveryDocumentAndTryLogin();
  }
}

And I created the Angular's 7 Guard:

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { OAuthService, OAuthEvent } from 'angular-oauth2-oidc';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})

export class AuthenticatedGuard implements CanActivate {

  constructor(private router: Router, private authService: OAuthService) { }

  canActivate(router: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    if (this.authService.hasValidIdToken() && this.authService.hasValidAccessToken()) 
      return true;

    this.authService.events.subscribe(({ type }: OAuthEvent) => {
      switch (type) {
        case 'token_received':
          return this.router.navigate(['/protected-route']); // How to get the actually route?
      }
    });

    this.authService.initLoginFlow();

    return false;

  }

}

If I try to access a protected route without being authenticated I am redirected to login.

After successful login I am redirected to Angular's home page but not to the protected route I tried to access ...

How to get redirected, after login, to the protected route that I tried to access before?

Shouldn't I get the route from ReturnUrl parameter?

Should not exist a auth-callback route on Angular's component to handle this?

I am not sure about the best way to do this.

Thank you

manfredsteyer/angular-oauth2-oidc

Answer questions mdmoura

@jeroenheijmans Yes, and I have been trying your Auth Service. I am not sure why but when I click to access the protected route I am not able to access the page even if I am authenticated. I am trying to figure it out ...

The Auth.Service I am using, based on yours and using Core Flow + PKCE is:

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

import { filter, map } from 'rxjs/operators';

import { OAuthService } from 'angular-oauth2-oidc';
import { BehaviorSubject, Observable, ReplaySubject, combineLatest } from 'rxjs';

@Injectable({ 
  providedIn: 'root' 
})

export class AuthService {

  private isAuthenticatedSubject$ = new BehaviorSubject<boolean>(false);
  public isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();

  private isDoneLoadingSubject$ = new ReplaySubject<boolean>();
  public isDoneLoading$ = this.isDoneLoadingSubject$.asObservable();

  public canActivateProtectedRoutes$: Observable<boolean> = combineLatest(this.isAuthenticated$, this.isDoneLoading$).pipe(map(values => values.every(x => x)));

  constructor (private router: Router, private oauthService: OAuthService) {

    window.addEventListener('storage', (event) => {
  
      if (event.key !== 'access_token' && event.key !== null) 
        return;
      
      this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());

      if (!this.oauthService.hasValidAccessToken())
        this.oauthService.initLoginFlow();

    });

    this.oauthService.events.subscribe(x => { this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken()); });

    this.oauthService.events.pipe(filter(x => ['token_received'].includes(x.type))).subscribe(x => this.oauthService.loadUserProfile());

    this.oauthService.events.pipe(filter(x => ['session_terminated', 'session_error'].includes(x.type))).subscribe(x => this.oauthService.initLoginFlow());

    this.oauthService.setupAutomaticSilentRefresh();

  }


  public runLoginFlow() : Promise<void> {

    return this.oauthService.loadDiscoveryDocument()

      .then(() => this.oauthService.tryLogin())

      .then(() => {

        if (this.oauthService.hasValidAccessToken())
          return Promise.resolve();

        return this.oauthService.silentRefresh()
          .then(() => Promise.resolve())
          .catch(result => {
            const errorResponsesRequiringUserInteraction = [
              'interaction_required',
              'login_required',
              'account_selection_required',
              'consent_required',
            ];

            if (result && result.reason && errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {
              this.oauthService.initLoginFlow();
              return Promise.resolve();
            }

            return Promise.reject(result);

          });

      })

      .then(() => {

        this.isDoneLoadingSubject$.next(true);

        if (this.oauthService.state && this.oauthService.state !== 'undefined' && this.oauthService.state !== 'null') 
          this.router.navigateByUrl(this.oauthService.state);

      })

      .catch(() => this.isDoneLoadingSubject$.next(true));

  }

  public login(targetUrl?: string) : void {
    this.oauthService.initLoginFlow(encodeURIComponent(targetUrl || this.router.url));
  }

  public logout() : void { 
    this.oauthService.logOut(); 
  }

  public refresh() { 
    this.oauthService.silentRefresh(); 
  }

  public hasValidToken() : boolean { 
    return this.oauthService.hasValidAccessToken(); 
  }

  public getIdentityClaims() : object { 
    return this.oauthService.getIdentityClaims(); 
  }

}

The Guard I am using is AuthGuardWithForcedLogin.

One thing I am not sure about is your use of window in AuthService.

Wouldn't this be a problem if I would reuse the Angular services between a Web application and a Native application using, for example, Native Script?

Basically, I am trying to create 2 guards:

  1. AuthenticatedGuard which checks if the user is Authenticated. If not then redirect user to Login. So this will always force a user to login.

  2. ClaimsGuard which checks is a user has some claims. If the user is not authenticated then it redirects to login. If the user is authenticated but does not have the claims then redirect to /forbidden. If the user is authenticate but has the claims then return true.

Does this make sense?

useful!
source:https://uonfu.com/
answerer
Miguel Moura mdmoura Moleky Lisbon Focus on project development and innovation
Github User Rank List