Enhancing Security in Angular: A Guide to Seamless Refresh Token Integration

SMK Solutions
4 min readJan 12, 2024

--

The token management process, highlighting access tokens, refresh tokens, and secure communication

Introduction:

Authentication stands as a cornerstone in web development, ensuring that only authorized users can access protected resources. In Angular applications, developers frequently rely on access tokens to verify user identity. However, managing token expiration gracefully is pivotal, and this is where refresh tokens come into play. They offer an elegant solution, allowing the renewal of access tokens without requiring user intervention. In this article, we’ll delve into the process of seamlessly integrating refresh tokens into an Angular application, enhancing security, and providing users with a smoother experience.

Understanding Access and Refresh Tokens:

Before we embark on the integration process, it’s crucial to grasp the roles of access and refresh tokens in the authentication process:

Access Token:

  • Grants access to protected resources on the server.
  • Has a limited lifespan, typically ranging from a few minutes to a few hours.
  • Requires re-authentication once expired.

Refresh Token:

  • Facilitates obtaining a new access token without user interaction.
  • Generally has a longer lifespan than access tokens.
  • Stored securely on the client-side.

Integration Steps:

  1. Implement Authentication Service:
  2. Create an Angular service responsible for handling authentication logic.
  3. Include methods for login, logout, and token refresh.
  4. Token Storage:
    Store access and refresh tokens securely. Consider HTTP-only cookies or local storage based on security requirements.
  5. Interceptor for Token Refresh:
    Develop an HTTP interceptor to intercept outgoing requests and check for token expiration.
    If the access token is expired, use the refresh token to obtain a new access token.
  6. Handle Token Expiry:
    Implement logic to handle scenarios where the access token is expired.
    Redirect users to the login page or initiate an automatic token refresh.
  7. Secure Communication:
    Ensure communication between your Angular application and the server occurs over HTTPS to protect sensitive data, including tokens.
  8. Error Handling:
    Implement robust error handling for token refresh requests.
    Handle cases where the refresh token is invalid or expired.

Example:

An example of how you can implement a simple Angular authentication service with token handling and an HTTP interceptor for token refresh. This example assumes the usage of Angular’s HttpClientModule for making HTTP requests.

// auth.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Injectable({
providedIn: 'root',
})
export class AuthService {
private apiUrl = 'your_api_endpoint';
private refreshTokenEndpoint = 'refresh_token_endpoint';

constructor(private http: HttpClient) {}

login(credentials: { username: string; password: string }): Observable<any> {
// Your login API call here
return this.http.post<any>(`${this.apiUrl}/login`, credentials);
}

refreshAccessToken(): Observable<any> {
// Call the refresh token endpoint to get a new access token
const refreshToken = localStorage.getItem('refreshToken');
return this.http.post<any>(`${this.apiUrl}/${this.refreshTokenEndpoint}`, { refreshToken }).pipe(
tap((response) => {
// Update the access token in the local storage
localStorage.setItem('accessToken', response.accessToken);
}),
catchError((error) => {
// Handle refresh token error (e.g., redirect to login page)
console.error('Error refreshing access token:', error);
return throwError(error);
})
);
}

logout(): void {
// Your logout logic here
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
}
}
// token-interceptor.service.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { AuthService } from './auth.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const accessToken = localStorage.getItem('accessToken');

if (accessToken) {
request = this.addToken(request, accessToken);
}

return next.handle(request).pipe(
catchError((error) => {
// Check if the error is due to an expired access token
if (error.status === 401 && accessToken) {
return this.handleTokenExpired(request, next);
}

return throwError(error);
})
);
}

private addToken(request: HttpRequest<any>, token: string): HttpRequest<any> {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
}

private handleTokenExpired(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Call the refresh token endpoint to get a new access token
return this.authService.refreshAccessToken().pipe(
switchMap(() => {
const newAccessToken = localStorage.getItem('accessToken');
// Retry the original request with the new access token
return next.handle(this.addToken(request, newAccessToken));
}),
catchError((error) => {
// Handle refresh token error (e.g., redirect to login page)
console.error('Error handling expired access token:', error);
return throwError(error);
})
);
}
}

To use these services, make sure to provide them in your app.module.ts:

// app.module.ts

import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { TokenInterceptor } from './token-interceptor.service';
import { AuthService } from './auth.service';

@NgModule({
declarations: [
// your components and directives
],
imports: [
HttpClientModule,
// other modules
],
providers: [
AuthService,
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true,
},
// other services and providers
],
bootstrap: [
// your main component
],
})
export class AppModule {}

The above example covers the basics of handling access tokens, refresh tokens, and automatically refreshing the access token when it expires using an HTTP interceptor. Keep in mind that this is a simplified example, and you may need to adapt it to your specific authentication flow and server implementation.

Conclusion:

Integrating refresh tokens into your Angular application is a pivotal step in maintaining a secure and user-friendly authentication system. By seamlessly renewing access tokens, you can enhance the user experience while ensuring your application remains protected against unauthorized access. Following these steps will contribute to a robust authentication mechanism in your Angular application, providing both security and usability.

Written by Shankar Dhaduti, SMK Solutions

--

--

SMK Solutions

SMK Solutions provides software development services and consultancy to software product companies.