How to refresh JWT tokens in Angular
Automatic JWT token refresh prevents users from being logged out during active sessions while maintaining security. As the creator of CoreUI with 12 years of Angular development experience, I’ve implemented token refresh mechanisms for enterprise applications with millions of authenticated users.
The most secure approach is to use an HTTP interceptor that detects 401 errors and refreshes the token automatically before retrying the failed request.
Create Token Service
Create src/app/services/token.service.ts:
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { BehaviorSubject, Observable } from 'rxjs'
import { tap } from 'rxjs/operators'
interface TokenResponse {
accessToken: string
refreshToken: string
expiresIn: number
}
@Injectable({
providedIn: 'root'
})
export class TokenService {
private accessTokenSubject = new BehaviorSubject<string | null>(null)
private refreshTokenSubject = new BehaviorSubject<string | null>(null)
private isRefreshing = false
constructor(private http: HttpClient) {
this.loadTokens()
}
get accessToken(): string | null {
return this.accessTokenSubject.value
}
get refreshToken(): string | null {
return this.refreshTokenSubject.value
}
get isRefreshingToken(): boolean {
return this.isRefreshing
}
setTokens(tokens: TokenResponse): void {
this.accessTokenSubject.next(tokens.accessToken)
this.refreshTokenSubject.next(tokens.refreshToken)
this.saveTokens(tokens)
}
refreshAccessToken(): Observable<TokenResponse> {
this.isRefreshing = true
return this.http.post<TokenResponse>('/api/auth/refresh', {
refreshToken: this.refreshToken
}).pipe(
tap(tokens => {
this.setTokens(tokens)
this.isRefreshing = false
})
)
}
clearTokens(): void {
this.accessTokenSubject.next(null)
this.refreshTokenSubject.next(null)
localStorage.removeItem('auth_tokens')
}
private saveTokens(tokens: TokenResponse): void {
localStorage.setItem('auth_tokens', JSON.stringify({
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresAt: Date.now() + tokens.expiresIn * 1000
}))
}
private loadTokens(): void {
const stored = localStorage.getItem('auth_tokens')
if (stored) {
const tokens = JSON.parse(stored)
if (tokens.expiresAt > Date.now()) {
this.accessTokenSubject.next(tokens.accessToken)
this.refreshTokenSubject.next(tokens.refreshToken)
} else {
this.clearTokens()
}
}
}
}
Create Refresh Interceptor
Create src/app/interceptors/token-refresh.interceptor.ts:
import { Injectable } from '@angular/core'
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http'
import { Observable, throwError, BehaviorSubject } from 'rxjs'
import { catchError, switchMap, filter, take } from 'rxjs/operators'
import { TokenService } from '../services/token.service'
import { Router } from '@angular/router'
@Injectable()
export class TokenRefreshInterceptor implements HttpInterceptor {
private refreshTokenSubject = new BehaviorSubject<string | null>(null)
constructor(
private tokenService: TokenService,
private router: Router
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Add token to request
const token = this.tokenService.accessToken
if (token) {
req = this.addToken(req, token)
}
return next.handle(req).pipe(
catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(req, next)
}
return throwError(() => error)
})
)
}
private handle401Error(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!this.tokenService.isRefreshingToken) {
this.refreshTokenSubject.next(null)
return this.tokenService.refreshAccessToken().pipe(
switchMap(tokens => {
this.refreshTokenSubject.next(tokens.accessToken)
return next.handle(this.addToken(req, tokens.accessToken))
}),
catchError(error => {
this.tokenService.clearTokens()
this.router.navigate(['/login'])
return throwError(() => error)
})
)
} else {
return this.refreshTokenSubject.pipe(
filter(token => token !== null),
take(1),
switchMap(token => next.handle(this.addToken(req, token!)))
)
}
}
private addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
})
}
}
Register Interceptor
In src/app/app.config.ts:
import { ApplicationConfig } from '@angular/core'
import { HTTP_INTERCEPTORS } from '@angular/common/http'
import { TokenRefreshInterceptor } from './interceptors/token-refresh.interceptor'
export const appConfig: ApplicationConfig = {
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TokenRefreshInterceptor,
multi: true
}
]
}
Automatic Refresh Before Expiry
Add proactive refresh:
export class TokenService {
private refreshTimer?: any
setTokens(tokens: TokenResponse): void {
this.accessTokenSubject.next(tokens.accessToken)
this.refreshTokenSubject.next(tokens.refreshToken)
this.saveTokens(tokens)
this.scheduleRefresh(tokens.expiresIn)
}
private scheduleRefresh(expiresIn: number): void {
clearTimeout(this.refreshTimer)
const refreshTime = (expiresIn - 60) * 1000
this.refreshTimer = setTimeout(() => {
this.refreshAccessToken().subscribe()
}, refreshTime)
}
}
Best Practice Note
This is the same token refresh pattern we use in CoreUI’s Angular admin templates. The interceptor automatically refreshes expired tokens and queues pending requests, providing seamless authentication without user interruption. Refreshing 60 seconds before expiry ensures no requests fail due to token expiration.
For production applications, consider using CoreUI’s Angular Admin Template which includes pre-built token refresh, automatic retry logic, and secure token storage.
Related Articles
If you’re implementing authentication, you might also want to learn how to store tokens securely in Angular for maximum security.



