TL;DR: Shield your Angular apps with JWT authentication! Unveils secure storage practices, route protection with guards, and interceptor-based token injection for seamless API calls – empowering robust user authentication. Unlock best practices for JWT in Angular and fortify your app’s security.
Data without security is like a treasure box without a lock. In this tech-driven world, hackers are everywhere. To transmit your data’s securely, you need highly reliable standards. Considering this, JSON Web Tokens (JWT) provide the best security and authentication.
Angular is a widely used JavaScript platform. In this blog, we are going to see how to implement authenticated routings in Angular, manage tokens, and pass tokens to servers in client side. For logins, you can use any kind of authentication like OpenID, OAuth, or create your own login application logic.
This blog covers the following topics:
JSON Web Tokens (JWT) are an internet standard for creating JSON-based access tokens that assert a number of claims. We can generate JWT with custom claims that may contain user information and permission-based values, information like whether the user is an admin can also be stored in JWT. Best practice is to not store any confidential information in JWT. Please refer to jwt.io for detailed information about JWT.
Note: An Angular project can be created using Angular CLI commands. Please refer to the Angular CLI command documentation for more information on how to create a project.
There are three possible ways of storing access tokens in an Angular app. They are:
In this technique, a token is stored in the application page itself. The only drawback of this option is the data is not persistent; it is lost on page refresh and must be retrieved again.
In this technique, data is stored in browser storage. Data stored this way is accessible by all tabs in the same browser within the same domain. The two types of web storage are:
In this technique, a token is stored in cookies. Data stored this way can be accessed by the server. The browser automatically appends a cookie in requests sent to the server. Since the browser automatically adds a cookie on each request, tokens are vulnerable to CSRF/XSRF attacks.
We can store data in different ways, but we should take proper measures to protect tokens against CSRF and XSRF vulnerabilities. We should store tokens in a place that is not accessible by attackers. Two possible ways of storing tokens to reduce risk of CSRF/XSRF attack are:
For additional security, we must consider a few more things on the server side, such as:
Now that we have learned where to store tokens, let’s see how to create an Angular service to decode stored tokens and retrieve values from them in an Angular app.
This service is used for decoding JWT tokens and retrieving values from JWT. Let’s set one up.
First, create an Angular service file for JWT decode and inject it in the application module.
We can use the jwt-decode package for decoding JWT tokens. In this service, functions for getting user claim values like username and email ID have been included. A function has also been added for checking token expiration in this service.
import { Injectable } from '@angular/core'; import * as jwt_decode from 'jwt-decode'; @Injectable() export class JWTTokenService { jwtToken: string; decodedToken: { [key: string]: string }; constructor() { } setToken(token: string) { if (token) { this.jwtToken = token; } } decodeToken() { if (this.jwtToken) { this.decodedToken = jwt_decode(this.jwtToken); } } getDecodeToken() { return jwt_decode(this.jwtToken); } getUser() { this.decodeToken(); return this.decodedToken ? this.decodedToken.displayname : null; } getEmailId() { this.decodeToken(); return this.decodedToken ? this.decodedToken.email : null; } getExpiryTime() { this.decodeToken(); return this.decodedToken ? this.decodedToken.exp : null; } isTokenExpired(): boolean { const expiryTime: number = this.getExpiryTime(); if (expiryTime) { return ((1000 * expiryTime) - (new Date()).getTime()) < 5000; } else { return false; } } }
To store the token, you can use either a cookie or local storage service. Code examples for implementing the services are provided below. Depending on where you are storing tokens, cookie or local storage service can be implemented.
import { Inject, Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class AppCookieService { private cookieStore = {}; constructor() { this.parseCookies(document.cookie); } public parseCookies(cookies = document.cookie) { this.cookieStore = {}; if (!!cookies === false) { return; } const cookiesArr = cookies.split(';'); for (const cookie of cookiesArr) { const cookieArr = cookie.split('='); this.cookieStore[cookieArr[0].trim()] = cookieArr[1]; } } get(key: string) { this.parseCookies(); return !!this.cookieStore[key] ? this.cookieStore[key] : null; } remove(key: string) { document.cookie = `${key} = ; expires=Thu, 1 jan 1990 12:00:00 UTC; path=/`; } set(key: string, value: string) { document.cookie = key + '=' + (value || ''); } }
import { Injectable } from '@angular/core'; @Injectable() export class LocalStorageService { set(key: string, value: string) { localStorage.setItem(key, value); } get(key: string) { return localStorage.getItem(key); } remove(key: string) { localStorage.removeItem(key); } }
So far, we have learned about JWT tokens, where to store them, and how to access and pass them to the server using Angular services. Now, we are going to see how to use these tokens to protect Angular routing.
Before diving into this topic, we have to understand the routing guard canActivate.
{ path: 'app', component: AppComponent, canActivate: [AuthorizeGuard] }
The above routing configuration is protected by the AuthorizeGuard service.
AuthorizeGuard supports ‘Boolean | Promise | Observable’. If the AuthorizeGuard returns false/Promise reject/Observable error, then routing won’t happen. We need to resolve the error for the routing configuration to be successful.
The guard service in the sample code below checks whether the user is logged in. If the user is logged in, then it checks whether the token is expired. If both cases are satisfied, then the routing process will be successful. Otherwise, it will request the login service to get a new token or redirect the user to a custom login or access denied page.
import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; import { JWTTokenService } from './jwt-token.service'; import { LocalStorageService } from './storage.service'; import { LoginService } from './login.service'; @Injectable({ providedIn: 'root' }) export class AuthorizeGuard implements CanActivate { constructor(private loginService: LoginService, private authStorageService: LocalStorageService, private jwtService: JWTTokenService) { } canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean { if (this.jwtService.getUser()) { if (this.jwtService.isTokenExpired()) { // Should Redirect Sig-In Page } else { return true; } } else { return new Promise((resolve) => { this.loginService.signIncallBack().then((e) => { resolve(true); }).catch((e) => { // Should Redirect Sign-In Page }); }); } } }
Note: In the above code, logInService is used for login resolution purposes only. You can create your own service to resolve logins.
In order to have authenticated calls with APIs, we have to send the authorization token in every HTTP request being sent to the server so that the server can verify authentication of the request. It’s difficult to include code to add a token in every place an API call is made—it causes code duplication. To remedy this, Angular has an interceptor service for handling all HTTP requests and responses in a single place. By using this interceptor, we can handle including an authentication token in every API call globally.
HttpInterceptor controls all the HTTP requests and responses. Every request and response comes and goes through this service, so we can easily append custom headers containing authorization tokens in a single place.
Override HTTP_INTERCEPTORS as shown in the following app module.
{ provide: HTTP_INTERCEPTORS, useClass: UniversalAppInterceptor, multi: true },
HttpInterceptor service
The interceptor intercepts all Angular HTTP requests and adds authorization headers with the token.
import { Injectable, Inject, Optional } from ‘@angular/core’; import { HttpInterceptor, HttpHandler, HttpRequest } from ‘@angular/common/http’; import { AuthService } from ‘./auth.service’; @Injectable() export class UniversalAppInterceptor implements HttpInterceptor { constructor( private authService: AuthService) { } intercept(req: HttpRequest, next: HttpHandler) { const token = this.authService.getJWTToken(); req = req.clone({ url: req.url, setHeaders: { Authorization: `Bearer ${token}` } }); return next.handle(req); } }
Note: In the above code, AuthService is used for JWT token retrieval purposes only.
In this blog, I have explained the best practices for authentication in Angular apps using JWT tokens and the management of JWT tokens on the client side.
For Angular developers, Syncfusion offers over 65 high-performance, lightweight, modular, and responsive Angular components to speed up development.
For current customers, the latest version of our controls is available for download from the License and Downloads page. If you are not yet a customer, you can try our 30-day free trial to check out all our Angular components have to offer. You can also explore samples in our GitHub repository.