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:
- Cross-Site Scripting (XSS) Attacks: If tokens are stored insecurely, attackers can access them through XSS vulnerabilities.
- Token Theft: Improper storage of access tokens makes it easier for attacks to steal them an impersonate users.
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.
httpOnly : trueensures that client-side scripts cannot access the token preventing certain XSS attacks.secure : trueensures the token is only sent over HTTPS.sameSite : 'Strict'prevents the cookie from being sent with cross-site requests.
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
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:
- Use Cookies with
httpOnly : true,secure : trueandsameSite : 'Strict'settings. - Avoid Local Storage and Session Storage.
- Encapsulate token management in a Token Service.
- Use Angular HTTP Interceptors.
- Use short-lived access tokens and refresh tokens.
- Clear tokens on logout.
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.