Full Refresh Token System in Angular (Enterprise)
Full Refresh Token System in Angular (Enterprise)
This Angular setup includes:
- ✅ Access Token + Refresh Token
- ✅ HttpInterceptor
- ✅ Auto refresh when 401 happens
- ✅ Retry the original failed request
- ✅ Prevent multiple refresh calls (queue system)
- ✅ Works for multiple parallel API calls
- ✅ Enterprise clean structure
Folder Structure
src/app/core/auth/ auth.service.ts token.service.ts auth.interceptor.ts auth.guard.ts (optional)
1️⃣ TokenService (Enterprise)
Handles storing and retrieving tokens from localStorage.
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class TokenService {
private accessKey = 'access_token';
private refreshKey = 'refresh_token';
getAccessToken(): string | null {
return localStorage.getItem(this.accessKey);
}
getRefreshToken(): string | null {
return localStorage.getItem(this.refreshKey);
}
setTokens(accessToken: string, refreshToken: string) {
localStorage.setItem(this.accessKey, accessToken);
localStorage.setItem(this.refreshKey, refreshToken);
}
clearTokens() {
localStorage.removeItem(this.accessKey);
localStorage.removeItem(this.refreshKey);
}
}
2️⃣ AuthService (Refresh Token API)
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface RefreshTokenRequest { refreshToken: string; }
export interface RefreshTokenResponse { accessToken: string; refreshToken: string; }
@Injectable({ providedIn: 'root' })
export class AuthService {
private baseUrl = 'https://localhost:5001/api/auth';
constructor(private http: HttpClient) {}
refreshToken(refreshToken: string): Observable {
return this.http.post(
`${this.baseUrl}/refresh-token`,
{ refreshToken }
);
}
}
3️⃣ AuthInterceptor (Enterprise Core)
Handles token injection, auto-refresh, retry, and queuing multiple requests.
import { Injectable } from '@angular/core';
import {
HttpInterceptor, HttpRequest, HttpHandler,
HttpEvent, HttpErrorResponse
} from '@angular/common/http';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { catchError, filter, switchMap, take } from 'rxjs/operators';
import { TokenService } from './token.service';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject = new BehaviorSubject(null);
constructor(private tokenService: TokenService, private authService: AuthService) {}
intercept(req: HttpRequest, next: HttpHandler): Observable> {
if (req.url.includes('/refresh-token')) return next.handle(req);
const accessToken = this.tokenService.getAccessToken();
const authReq = accessToken ? this.addToken(req, accessToken) : req;
return next.handle(authReq).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) return this.handle401Error(authReq, next);
return throwError(() => error);
})
);
}
private addToken(request: HttpRequest, token: string) {
return request.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
}
private handle401Error(request: HttpRequest, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
const refreshToken = this.tokenService.getRefreshToken();
if (!refreshToken) {
this.tokenService.clearTokens();
window.location.href = '/login';
return throwError(() => new Error('Refresh token missing.'));
}
return this.authService.refreshToken(refreshToken).pipe(
switchMap(res => {
this.isRefreshing = false;
this.tokenService.setTokens(res.accessToken, res.refreshToken);
this.refreshTokenSubject.next(res.accessToken);
return next.handle(this.addToken(request, res.accessToken));
}),
catchError(err => {
this.isRefreshing = false;
this.tokenService.clearTokens();
window.location.href = '/login';
return throwError(() => err);
})
);
}
return this.refreshTokenSubject.pipe(
filter(token => token !== null),
take(1),
switchMap(token => next.handle(this.addToken(request, token!)))
);
}
}
4️⃣ Register Interceptor
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './core/auth/auth.interceptor';
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
})
export class AppModule {}
5️⃣ How it works
- 1️⃣ Angular sends API request with Access Token
- 2️⃣ If API returns 401 → interceptor calls refresh-token API
- 3️⃣ New access token is received
- 4️⃣ Original request is retried automatically
- 5️⃣ Multiple parallel requests only trigger one refresh call; others wait in queue
6️⃣ Enterprise Add-ons
- Prevent infinite refresh loop by skipping refresh-token URL
- Logout and redirect on failed refresh
- BehaviorSubject queues waiting requests efficiently
Comments
Post a Comment