Home

Best Practices for access tokens - Angular Edition

Access tokens are essential for securing the communication of web applications with APIs. However, handling these tokens improperly can lead to security vulnerarbilities.

This document describes best practices for handling access tokens in Angular applications, with pseudo-code examples.

1 Why storing access tokens matters

Access tokens contain sensitive information that provide access to protected resources. Storing them inappropriatly could expose your web application to several security risks, including:

2 Where to store access tokens

2.1 Local storage (not recommended)

Local Storage is persistent storage in the browser which retains the token until it is removed. It is *not safe* for storing sensitive data like access tokens due to vulnerabilities to XSS attacks.

localStorage.setItem('accessToken', token); /* This is not secure. */

sessionStorage.setItem('accessToken', token); /* This is still not secure. */

2.2 Session storage (not recommended)

Session Storage is persistent storage in the browser which retains the token until it is removed or the session ends (e.g., when the browser or the browser tab is closed). However, it is for storing sensitive data like access tokens due to vulnerabilities to XSS attacks.

sessionStorage.setItem('accessToken', token); /* This is not secure. */

2.3 Cookies

A more secure storage is provided by cookies if the proper settings are used. You should use the httpOnly : true, secure : true and sameSite : ''Strict' settings.

res.cookie
 (
  'accessToken',
  token,
  {
   httpOnly: true,
   secure: true,
   sameSite: 'Strict',
  }
 );

3 How to send access tokens

In Angular, you can configure your HTTP client to send cookies with each request.

import { HttpClient } from '@angular/common/http'; constructor(private http: HttpClient) {} getData() { return this.http.get('https://api.example.com/data', { withCredentials: true }); }

4 How to encapsulate token handling

Create a Token Service in Angular to handle all token-related operations. This way, you centralize token storage and avoid directly accessing localStorage, sessionStorage or res in your code.

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

@Injectable({
 providedIn: 'root'
})
export class TokenService {
...
 private ACCESS_TOKEN_KEY = 'accessToken';
 constructor() {}
 /* Set the token. */
 public setToken(token: string): void {
  sessionStorage.setItem(this.ACCESS_TOKEN_KEY, token);
 }
 /* Get the token. */
 public getToken(): string | null {
  return sessionStorage.getItem(this.ACCESS_TOKEN_KEY);
 }
 /* Remove the token. */
 public removeToken(): void {
  sessionStorage.removeItem(this.ACCESS_TOKEN_KEY);
 }
...
}

5 How to send tokens

Use Angular's HTTP Interceptors to attach access tokens to HTTP requests automatically, ensureing that *every* outgoing API call includes the required authentication token. Strictly speaking this is not a security issue but a maintainability issue: If an API call does not include the token, then it would fail.

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { TokenService } from './token.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
 constructor(private tokenService: TokenService) {}
 intercept(req: HttpRequest, next: HttpHandler): Observable> {
  const token = this.tokenService.getToken();
  if (token) {
   const cloned = req.clone({
     headers: req.headers.set('Authorization', `Bearer ${token}`)
    });
   return next.handle(cloned);
  }
  return next.handle(req);
 }
}

6 How to refresh access tokens

If your access token expires, in particular if it expires after a resonably short time which is recommended, use refresh tokens to obtain a new access token without requiring the user to re-authenticate.

refreshToken() {
 return this.http.post('https://api.example.com/refresh-token', {
  token: this.tokenService.getRefreshToken()
  }).subscribe((response: any) => {
  this.tokenService.setToken(response.newAccessToken);
  });
}

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

@Injectable({
 providedIn: 'root'
})
export class TokenService {
...
 private REFRESH_TOKEN_KEY = 'refreshToken';
 constructor() {}
 /* Set the refresh token. */
 public setRefreshToken(token: string): void {
  sessionStorage.setItem(this.REFRESH_TOKEN_KEY, token);
 }
 /* Get the refresh token. */
 public getRefreshToken(): string | null {
  return sessionStorage.getItem(this.REFRESH_TOKEN_KEY);
 }
 /* Remove the refresh token. */
 public removeRefreshToken(): void {
  sessionStorage.removeItem(this.REFRESH_TOKEN_KEY);
 }
...
}

7 How to clear access tokens on logout

Clear tokens on logout.

public clearSession(): void {
 sessionStorage.clear();
}

8 Summary

Apply best practices

Apply the following best practices:

9 Address security concerns

Address the following security concerns:

9.1 Cross-Site Scripting (XSS)

Concern: XSS attacks can expose access tokens.

Solution: The best way to prevent this is by not storing tokens in locations like localStorage or sessionStorage. Use Cookies with sameSite : 'Strict' settings to prevent JavaScript access and mitigate XSS attacks.

9.2 Cross-site request forgery (CSRF)

Concern: When using Cookies to store access tokens, the web application becomes vulnerable to CSRF attacks. Attackers can trick users into making unintended requests.

Solution: Use session cookies with `sameSite : ''Strict'` settings to prevent the browser from sending cookies with cross-origin requests.

9.3 Token theft via insecure storage

Concern: Storing tokens in browser storage without encryption exploses them to attackers, especially in shared environments.

Solution: Avoid browser storage for sensitive tokens and ensure tokens are short-lived and regularly refreshed.