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.

If you’re implementing authentication, you might also want to learn how to store tokens securely in Angular for maximum security.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to Clone an Object in JavaScript
How to Clone an Object in JavaScript

How to Convert a Map to an Array in JavaScript
How to Convert a Map to an Array in JavaScript

How to Disable Right Click on a Website Using JavaScript
How to Disable Right Click on a Website Using JavaScript

How to check if an element is visible in JavaScript
How to check if an element is visible in JavaScript

Answers by CoreUI Core Team