Angular Authentication, Interceptors and Guards

In this article I will create step by step an Authentication library and along the way will describe Interceptors and Guards.

Application Description

This application will have these pages: Login, Home, User Dashboard and Admin Dashboard which will have different access levels as described in the table below.

For the implementation we will need an Angular Application that you can set up following these steps:

  1. ng new auth-demo Create a new Angular Application
  2. ng g lib authentication Generate the authentication library
  3. ng g c home Generate the Home Component
  4. ng g c user-dashboard Generate the User Dashboard Component
  5. ng g c admin-dashboard Generate the Admin Dashboard Component

At this point we have already set up the application and will start working on the Authentication Library.

**You can find the demo in this stack blitz. The only difference is that Authentication is a module instead of a library. The implementation will consider Authentication as a library, which means that it can be used everywhere and has no dependencies from the current app.

Authentication Library

Let’s start first by implementing the Login and Logout components. Login component will contain a form with username and password and logout will contain a button. Authentication Service will be responsible to manage the login, logout and checking if the user is authenticated.The scheme below shows how we will handle the login and logout.

For this demo I have implemented a simple mock backend service, where two users are provided: admin/admin and user/user.

As you will see in the implementation, we need to inject both the authentication endpoint and the initial page to be loaded after the user has been logged in. In order to make the library independent we inject them in the Authentication Module:

export class AuthenticationModule { 
static forRoot(config: LibraryConfig): ModuleWithProviders<AuthenticationModule> {
return { ngModule: AuthenticationModule,
providers: [{provide: 'config', useValue: config}] };
}
}

And then wherever we import it our apps we specify this config value as below:

export const AUTHENTICATION_CONFIG = 
{ authEndpoint: "/users/authenticate", initialPage: "home" };
imports: [ AuthenticationModule.forRoot(AUTHENTICATION_CONFIG) ]

This will ensure that the library will not change anything when one of these parameters needs to be adapted.

JWT Token

JSON Web Tokens are an open, industry standard for representing claims securely between two parties. When we dispatch a successful login request in a real word application, the backend would send us back a token. After that, we have established a secure connection between frontend and backend. With each request that the frontend will send to the backend we need to attach this token. Token can contain different types of information and it also can expire. At this point we can start explaining and implementing the TokenInterceptor.

Interceptor

According to Angular Documentation: “An Interceptor handles an HttpRequest or HttpResponse before passing to the next interceptor in the chain, by calling next.handle(transformedReq)”. So what an Interceptor basically does is to “listen” for either a request or response and transform the data or produce a side effect. I will implement two interceptors as below:

HttpRequest Interceptor

Inside the Authentication library, I have created the Token Interceptor. As we defined above, this interceptor will listen to any HttpRequest and will attach the token to the header of the request as below:

@Injectable() 
export class TokenInterceptor implements HttpInterceptor {
constructor( private authenticationService: AuthenticationService) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let user = this.authenticationService.getLoggedUser();
if (user && user.token) {
request = request.clone({ setHeaders:
{ Authorization: `Bearer ${user.token}` } }); }
return next.handle(request);
}
}

So this interceptor is getting the token of the logged user and attaching it to the header of the request. In this way the backend can validate the request and bring to the front end the result.

HttpResponse Interceptor

The second interceptor will listen to any incoming response and then will respond accordingly. Let’s suppose the token that the backend has generated expires, so even though we send the request and attach successfully the token, the backend brings us a 401 error (unauthorized user). In this case we should detect this error and automatically logout the user.

@Injectable() 
export class ErrorInterceptor implements HttpInterceptor {
constructor(private authentiationService: AuthenticationService) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).
pipe(catchError(err => {
if (err.status === 401) {
this.authentiationService.logout();
location.reload(); }
const error = err.error.message || err.statusText;
return throwError(error);
}))
}
}

After successfully implementing the interceptors we need to provide them inside the Authentication Module as below:

providers: [ 
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor,
multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor,
multi: true}
]

After implementing the interceptors, the next step is to implement Guards.

Guards

As we have described above, different users will have different access in the app. We will implement the CanActivate Interface from @angular/router which will decide if a route can be activated. To fulfill our requirements we will need two types of guards:

  1. AuthenticationGuard -> check if the user is authenticated and allow him to access Home
  2. RoleGuard -> check if the user has permission to access User or Admin Dashboard.

Authentication Guard

This Guard will do a simple check if the user is authenticated which means we have locally stored a token for this user. The implementation is as below.

@Injectable({ providedIn: 'root' }) 
export class AuthenticationGuard implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthenticationService ) {
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
const currentUser = this.authenticationService.getLoggedUser();

if (currentUser) {
return true;
}
this.router.navigate(['/login']); return false; }
}

In case the user is not logged in, we are simply redirecting him to login page.

Role Guard

For this case, the mock back end I have provided saves the role in the token. Which means the value of our token will be either user or admin. The token will be more complicated in your application, but this is a basic idea. This will be the implementation of our RoleGuard.

@Injectable({ providedIn: 'root' }) 
export class RoleGuard implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthenticationService ) {
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
const currentUser = this.authenticationService.getLoggedUser(); const expectedRole = route.data.expectedRole;
if (currentUser.token === expectedRole) {
return true;
}
this.router.navigate(['/home']);
return false;
}
}

After implementing the permission guard in order to make this work we need to set up routes for the component and specify the guards that will protect them as I have defined them in app-routing.module:

const routes: Routes = [ 
{path: 'login', component: LoginComponent},
{path: 'home', component: HomeComponent,
canActivate: [AuthenticationGuard]},
{path: 'admin', component: AdminDashboardComponent,
canActivate: [AuthenticationGuard, RoleGuard],
data: {expectedRole: ROLE.ADMIN}},
{path: 'user', component: UserDashboardComponent,
canActivate: [AuthenticationGuard, RoleGuard],
data: {expectedRole: ROLE.USER}},
{path: '**', component: NotFoundComponent}
];

So we can see, that in order to access the Home Component route the user needs just to pass the Authentication Guard, while AdminDashboard and UserDashboard can only be activated when the user is authenticated and has the role user/admin that the route is expecting.

You can go and check the implementation in this stack blitz link or run the application in this link.

Conclusions

To sum up we implemented an application with some components that can be activated by specific users. We implemented the Authentication Library, which can be used in different applications because we are providing it with all the information it needs such as authentication endpoint and initial route when login is successful. We went over JWT authentication, Interceptors and Guards. In different scenarios you might need to adapt the authentication according to your needs, but these are the key concepts related to it.

Originally published at http://agdev.tech on March 18, 2021.

Senior Front End Software Engineer focused on Angular. Passionate about learning new skills and sharing my knowledge. Blog agdev.tech in progress.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store