{"id":174,"date":"2024-07-10T16:43:36","date_gmt":"2024-07-10T15:43:36","guid":{"rendered":"https:\/\/thevadasan.com\/?p=174"},"modified":"2024-07-10T16:43:37","modified_gmt":"2024-07-10T15:43:37","slug":"implementing-jwt-token-refresh-in-an-angular-application","status":"publish","type":"post","link":"https:\/\/thevadasan.com\/?p=174","title":{"rendered":"Implementing JWT Token Refresh in an Angular Application"},"content":{"rendered":"\n<p>Managing user authentication in a web application is crucial, and JWT (JSON Web Tokens) is a popular choice for this purpose. However, handling token expiration gracefully is a challenge. In this post, we&#8217;ll walk through how to implement a refresh token mechanism in an Angular application to ensure a smooth user experience without frequent reauthentication.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Use JWT and Refresh Tokens?<\/h2>\n\n\n\n<p>JWTs are great for stateless authentication because they are self-contained and easy to validate. However, JWTs often have short lifespans for security reasons, which means they can expire quickly. This is where refresh tokens come in. A refresh token has a longer lifespan and can be used to obtain a new access token without requiring the user to log in again.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting Up the Backend<\/h2>\n\n\n\n<p>Before diving into the Angular implementation, ensure your backend supports issuing and refreshing tokens. Typically, when a user logs in, the backend should return both an access token and a refresh token.<\/p>\n\n\n\n<p>Here&#8217;s a quick overview of the backend endpoints you should have:<\/p>\n\n\n\n<ol>\n<li><strong>Login<\/strong>: Issues access and refresh tokens.<\/li>\n\n\n\n<li><strong>Register<\/strong>: Registers a new user and issues tokens.<\/li>\n\n\n\n<li><strong>Refresh Token<\/strong>: Accepts a refresh token and issues a new access token.<\/li>\n\n\n\n<li><strong>Logout<\/strong>: Invalidates the refresh token.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Modifying the Angular AuthService<\/h2>\n\n\n\n<p>Let&#8217;s start by modifying our Angular <code>AuthService<\/code> to handle both access and refresh tokens.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">AuthService Modifications<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ frontend\/src\/app\/services\/auth.service.ts\n\nimport { Injectable } from '@angular\/core';\nimport { HttpClient } from '@angular\/common\/http';\nimport { BehaviorSubject, Observable, throwError } from 'rxjs';\nimport { catchError, map, switchMap, tap } from 'rxjs\/operators';\nimport { ApiConfigService } from '.\/api-config.service';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthService {\n  private currentUserSubject: BehaviorSubject&lt;any>;\n  public currentUser: Observable&lt;any>;\n  private baseUrl: string;\n  private refreshTokenInProgress: boolean = false;\n\n  constructor(private http: HttpClient, private apiConfig: ApiConfigService) {\n    const storedUser = localStorage.getItem('currentUser');\n    this.currentUserSubject = new BehaviorSubject&lt;any>(storedUser ? JSON.parse(storedUser) : null);\n    this.currentUser = this.currentUserSubject.asObservable();\n\n    this.baseUrl = this.apiConfig.getApiUrl();\n  }\n\n  public get currentUserValue() {\n    return this.currentUserSubject.value;\n  }\n\n  register(email: string, password: string) {\n    return this.http.post&lt;any>(`${this.baseUrl}\/api\/auth\/register`, { email, password })\n      .pipe(\n        map(user => {\n          if (user &amp;&amp; user.accessToken &amp;&amp; user.refreshToken) {\n            this.storeTokens(user.accessToken, user.refreshToken);\n            this.currentUserSubject.next(user);\n          }\n          return user;\n        })\n      );\n  }\n\n  login(email: string, password: string) {\n    return this.http.post&lt;any>(`${this.baseUrl}\/api\/auth\/login`, { email, password })\n      .pipe(\n        map(response => {\n          if (response &amp;&amp; response.accessToken &amp;&amp; response.refreshToken) {\n            const user = {\n              email: response.user.email,\n              role: response.user.role,\n              accessToken: response.accessToken,\n              refreshToken: response.refreshToken\n            };\n            this.storeTokens(response.accessToken, response.refreshToken);\n            this.currentUserSubject.next(user);\n          }\n          return response;\n        })\n      );\n  }\n\n  logout() {\n    this.clearTokens();\n    this.currentUserSubject.next(null);\n  }\n\n  getProfile() {\n    return this.http.get&lt;any>(`${this.baseUrl}\/api\/auth\/profile`).pipe(\n      catchError(error => {\n        console.error('Error fetching user profile:', error);\n        return throwError('Failed to fetch user profile');\n      })\n    );\n  }\n\n  refreshToken() {\n    const refreshToken = this.getRefreshToken();\n    if (refreshToken &amp;&amp; !this.refreshTokenInProgress) {\n      this.refreshTokenInProgress = true;\n      return this.http.post&lt;any>(`${this.baseUrl}\/api\/auth\/refresh-token`, { refreshToken })\n        .pipe(\n          tap(response => {\n            if (response &amp;&amp; response.accessToken) {\n              this.storeAccessToken(response.accessToken);\n              const currentUser = this.currentUserValue;\n              currentUser.accessToken = response.accessToken;\n              this.currentUserSubject.next(currentUser);\n            }\n            this.refreshTokenInProgress = false;\n          }),\n          catchError(error => {\n            this.refreshTokenInProgress = false;\n            this.logout();\n            return throwError('Failed to refresh token');\n          })\n        );\n    }\n    return throwError('No refresh token available');\n  }\n\n  private storeTokens(accessToken: string, refreshToken: string) {\n    localStorage.setItem('accessToken', accessToken);\n    localStorage.setItem('refreshToken', refreshToken);\n  }\n\n  private storeAccessToken(accessToken: string) {\n    localStorage.setItem('accessToken', accessToken);\n  }\n\n  private getAccessToken(): string | null {\n    return localStorage.getItem('accessToken');\n  }\n\n  private getRefreshToken(): string | null {\n    return localStorage.getItem('refreshToken');\n  }\n\n  private clearTokens() {\n    localStorage.removeItem('accessToken');\n    localStorage.removeItem('refreshToken');\n  }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Adding an HTTP Interceptor<\/h3>\n\n\n\n<p>Next, we&#8217;ll add an HTTP interceptor to automatically refresh the access token when it expires.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ frontend\/src\/app\/interceptors\/token.interceptor.ts\n\nimport { Injectable } from '@angular\/core';\nimport { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular\/common\/http';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError, switchMap, take } from 'rxjs\/operators';\nimport { AuthService } from '..\/services\/auth.service';\n\n@Injectable()\nexport class TokenInterceptor implements HttpInterceptor {\n\n  constructor(private authService: AuthService) { }\n\n  intercept(req: HttpRequest&lt;any>, next: HttpHandler): Observable&lt;HttpEvent&lt;any>> {\n    const accessToken = this.authService.getAccessToken();\n\n    let authReq = req;\n    if (accessToken) {\n      authReq = req.clone({\n        setHeaders: { Authorization: `Bearer ${accessToken}` }\n      });\n    }\n\n    return next.handle(authReq).pipe(\n      catchError((error: HttpErrorResponse) => {\n        if (error.status === 401) {\n          \/\/ Access token might have expired, try to refresh it\n          return this.authService.refreshToken().pipe(\n            take(1),\n            switchMap(() => {\n              const newAccessToken = this.authService.getAccessToken();\n              const newAuthReq = req.clone({\n                setHeaders: { Authorization: `Bearer ${newAccessToken}` }\n              });\n              return next.handle(newAuthReq);\n            }),\n            catchError(err => {\n              this.authService.logout();\n              return throwError(err);\n            })\n          );\n        }\n        return throwError(error);\n      })\n    );\n  }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Registering the Interceptor<\/h3>\n\n\n\n<p>Finally, register the interceptor in your <code>AppModule<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ frontend\/src\/app\/app.module.ts\n\nimport { BrowserModule } from '@angular\/platform-browser';\nimport { NgModule } from '@angular\/core';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular\/common\/http';\nimport { AppComponent } from '.\/app.component';\nimport { AuthService } from '.\/services\/auth.service';\nimport { TokenInterceptor } from '.\/interceptors\/token.interceptor';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    HttpClientModule\n  ],\n  providers: [\n    AuthService,\n    {\n      provide: HTTP_INTERCEPTORS,\n      useClass: TokenInterceptor,\n      multi: true\n    }\n  ],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Managing user authentication in a web application is crucial, and JWT (JSON Web Tokens) is a popular choice for this purpose. However, handling token expiration gracefully is a challenge. In this post, we&#8217;ll walk through how to implement a refresh token mechanism in an Angular application to ensure a smooth user experience without frequent reauthentication. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":176,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[18,23],"tags":[43],"_links":{"self":[{"href":"https:\/\/thevadasan.com\/index.php?rest_route=\/wp\/v2\/posts\/174"}],"collection":[{"href":"https:\/\/thevadasan.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thevadasan.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thevadasan.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/thevadasan.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=174"}],"version-history":[{"count":1,"href":"https:\/\/thevadasan.com\/index.php?rest_route=\/wp\/v2\/posts\/174\/revisions"}],"predecessor-version":[{"id":175,"href":"https:\/\/thevadasan.com\/index.php?rest_route=\/wp\/v2\/posts\/174\/revisions\/175"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/thevadasan.com\/index.php?rest_route=\/wp\/v2\/media\/176"}],"wp:attachment":[{"href":"https:\/\/thevadasan.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=174"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thevadasan.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=174"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thevadasan.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=174"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}