My Angular 11 app uses oidc-client with Implicit flow. I have protected one of my routes (which is supposed to be available only for authenticated users) with guard. It works just fine when I navigate to restricted area (let's say http://localhost:4200/restricted-area/) using links in my application. However, the problem is that when I try to navigate to that very page "manually" (e.g. typing url in my browser), application treats me as if I am not authenticated. The same issue comes up when I simply refresh the page after navigating "properly". Interestingly, in both cases after redirect to main page I am logged in again.
As a workaround, I was thinking about implementing additional manual extraction of token from session storage in the beginning of [canActivate] method, but I hope there is better way to solve this.
AuthGuard implementation:
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './shared/services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.authService.isAuthenticated()) { return true; }
alert('Authentication required.');
this.router.navigate(['/menu'], { queryParams: { redirect: state.url }, replaceUrl: true });
return false;
}
}
AuthService implementation looks as follows:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserManager, UserManagerSettings, User } from 'oidc-client';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private _authNavStatusSource = new BehaviorSubject<boolean>(false);
authNavStatus$ = this._authNavStatusSource.asObservable();
private manager = new UserManager(getClientSettings());
private user: User | null;
constructor(private http: HttpClient) {
this.manager.getUser().then(user => {
this.user = user;
this._authNavStatusSource.next(this.isAuthenticated());
});
}
login() {
return this.manager.signinRedirect();
}
async completeAuthentication() {
this.user = await this.manager.signinRedirectCallback();
this._authNavStatusSource.next(this.isAuthenticated());
}
register(userRegistration: any) {
// return this.http.post(this.configService.authApiURI + '/account', userRegistration).pipe(catchError(this.handleError)); // temporary disabled
}
isAuthenticated(): boolean {
return this.user != null && !this.user.expired;
}
get authorizationHeaderValue(): string {
return `${this.user.token_type} ${this.user.access_token}`;
}
get name(): string {
return this.user != null ? this.user.profile.name : '';
}
async signout() {
await this.manager.signoutRedirect();
}
}
export function getClientSettings(): UserManagerSettings {
return {
authority: 'http://xxxxxxxxxx',
client_id: 'xxxxxxxxxxx',
redirect_uri: 'http://xxxxxxxxxxx/auth-callback',
post_logout_redirect_uri: 'http://xxxxxxxxxxx',
response_type:"id_token token",
scope:"xxxxxxxxxxxxxxxxxxxxxxxxxx",
filterProtocolClaims: true,
loadUserInfo: true,
automaticSilentRenew: true,
silent_redirect_uri: 'xxxxxxxxxxxxxxxxxxxxxxxxx'
};
}
Routing (app):
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
import { AuthGuard } from './auth.guard';
import { MenuComponent } from './menu/menu.component';
const routes: Routes = [
{ path: 'menu', component: MenuComponent },
{ path: 'auth-callback', component: AuthCallbackComponent },
{ path: 'suppliers', redirectTo: 'suppliers', pathMatch: 'full', canActivate: [AuthGuard] },
{ path: '**', redirectTo: '/menu', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Routing (parent module):
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard } from '../auth.guard';
import { SupplierContactFormComponent } from './supplier-contact-form/supplier-contact-form.component';
import { SupplierFormComponent } from './supplier-form/supplier-form.component';
import { SupplierListComponent } from './supplier-list/supplier-list.component';
import { SupplierViewComponent } from './supplier-view/supplier-view.component';
import { SupplierResolver } from './supplier.resolver';
import { SupplierComponent } from './supplier/supplier.component';
const routes: Routes = [
{
path: 'suppliers', canActivate: [AuthGuard], children: [
{ path: '', component: SupplierListComponent },
{ path: ':supplierId', component: SupplierComponent, resolve: { supplier: SupplierResolver }, children: [
{ path: '', pathMatch: 'full', component: SupplierViewComponent },
{ path: 'edit', component: SupplierFormComponent},
{ path: 'create-contact', component: SupplierContactFormComponent },
{ path: 'edit-contact/:contactId', component: SupplierContactFormComponent },
] },
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SupplierRoutingModule { }