title: Storing JWT token into HttpOnly cookies
body: <p>I read a few articles that local storage is not the preferred way to store JWT tokens, because it's not meant to be used for session storage, because you can access it easily through JavaScript code which might lead to XSS itself if there is a vulnerable third-party library or something.</p>
  <p><em>Summarized from these articles, the correct way is to use HttpOnly cookies instead of local storage for session/sensitive information.</em></p>
  <h3>Question 1</h3>
  <p>I found a service for the cookies just like the one I'm currently using for local storage. What is unclear to me is <code>expires=Thu, 1 Jan 1990 12:00:00 UTC; path=/</code>;`. Does it really have to expire at some point? I just need to store my JWT and refresh tokens. The whole information is in there.</p>
  <pre><code>import { Injectable } from '@angular/core';
  
  /**
   * Handles all business logic relating to setting and getting local storage items.
   */
  @Injectable({
    providedIn: 'root'
  })
  export class LocalStorageService {
    setItem(key: string, value: any): void {
      localStorage.setItem(key, JSON.stringify(value));
    }
  
    getItem&lt;T&gt;(key: string): T | null {
      const item: string | null = localStorage.getItem(key);
      return item !== null ? (JSON.parse(item) as T) : null;
    }
  
    removeItem(key: string): void {
      localStorage.removeItem(key);
    }
  }
  </code></pre>
  <pre><code>import { Inject, Injectable } from '@angular/core';
  
  @Injectable({
      providedIn: 'root',
    })
  export class AppCookieService {
      private cookieStore = {};
  
      constructor() {
          this.parseCookies(document.cookie);
      }
  
      public parseCookies(cookies = document.cookie) {
          this.cookieStore = {};
          if (!!cookies === false) { return; }
          const cookiesArr = cookies.split(';');
          for (const cookie of cookiesArr) {
              const cookieArr = cookie.split('=');
              this.cookieStore[cookieArr[0].trim()] = cookieArr[1];
          }
      }
  
      get(key: string) {
          this.parseCookies();
          return !!this.cookieStore[key] ? this.cookieStore[key] : null;
      }
  
      remove(key: string) {
        document.cookie = `${key} = ; expires=Thu, 1 jan 1990 12:00:00 UTC; path=/`;
      }
  
      set(key: string, value: string) {
          document.cookie = key + '=' + (value || '');
      }
  }
  </code></pre>
  <h3>Question 2</h3>
  <p>Look at the log out function <code>signOut()</code>. Isn't it a better practice to revoke the JWT token in the backend (additional subscription to the backend)?</p>
  <pre><code>import { Injectable } from '@angular/core';
  import { HttpClient } from '@angular/common/http';
  import { Router } from '@angular/router';
  import { map, Observable, of } from 'rxjs';
  
  import { JwtHelperService } from '@auth0/angular-jwt';
  import { environment } from '@env';
  import { LocalStorageService } from '@core/services';
  import { AuthResponse, User } from '@core/types';
  
  @Injectable({
    providedIn: 'root'
  })
  export class AuthService {
    private readonly ACTION_URL = `${environment.apiUrl}/Accounts/token`;
  
    private jwtHelperService: JwtHelperService;
  
    get userInfo(): User | null {
      const accessToken = this.getAccessToken();
      return accessToken ? this.jwtHelperService.decodeToken(accessToken) : null;
    }
  
    constructor(
      private httpClient: HttpClient,
      private router: Router,
      private localStorageService: LocalStorageService
    ) {
      this.jwtHelperService = new JwtHelperService();
    }
  
    signIn(credentials: { username: string; password: string }): Observable&lt;AuthResponse&gt; {
      return this.httpClient.post&lt;AuthResponse&gt;(`${this.ACTION_URL}/create`, credentials).pipe(
        map((response: AuthResponse) =&gt; {
          this.setUser(response);
          return response;
        })
      );
    }
  
    refreshToken(): Observable&lt;AuthResponse | null&gt; {
      const refreshToken = this.getRefreshToken();
      if (!refreshToken) {
        this.clearUser();
        return of(null);
      }
  
      return this.httpClient.post&lt;AuthResponse&gt;(`${this.ACTION_URL}/refresh`, { refreshToken }).pipe(
        map((response) =&gt; {
          this.setUser(response);
          return response;
        })
      );
    }
  
    signOut(): void {
      this.clearUser();
      this.router.navigate(['/auth']);
    }
  
    getAccessToken(): string | null {
      return this.localStorageService.getItem('accessToken');
    }
  
    getRefreshToken(): string | null {
      return this.localStorageService.getItem('refreshToken');
    }
  
    hasAccessTokenExpired(token: string): boolean {
      return this.jwtHelperService.isTokenExpired(token);
    }
  
    isSignedIn(): boolean {
      return this.getAccessToken() ? true : false;
    }
  
    private setUser(response: AuthResponse): void {
      this.localStorageService.setItem('accessToken', response.accessToken);
      this.localStorageService.setItem('refreshToken', response.refreshToken);
    }
  
    private clearUser() {
      this.localStorageService.removeItem('accessToken');
      this.localStorageService.removeItem('refreshToken');
    }
  }
  
  </code></pre>
  <h3>Question 3</h3>
  <p>My backend is ASP.NET Core 5 and I'm using IdentityServer4. I'm not sure if I have to make the backend validate the cookies or how does it work?</p>
  <pre class="lang-cs prettyprint-override"><code>services.AddIdentityServer(options =&gt;
  {
      options.Events.RaiseErrorEvents = true;
      options.Events.RaiseInformationEvents = true;
      options.Events.RaiseFailureEvents = true;
      options.Events.RaiseSuccessEvents = true;
      
      options.EmitStaticAudienceClaim = true;
  })
      .AddDeveloperSigningCredential()
      .AddInMemoryIdentityResources(Configuration.GetIdentityResources())
      .AddInMemoryApiScopes(Configuration.GetApiScopes(configuration))
      .AddInMemoryApiResources(Configuration.GetApiResources(configuration))
      .AddInMemoryClients(Configuration.GetClients(configuration))
      .AddCustomUserStore();
  
  services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
      .AddIdentityServerAuthentication(options =&gt;
      {
          options.Authority = configuration[&quot;AuthConfiguration:ClientUrl&quot;];
          options.RequireHttpsMetadata = false;
          options.RoleClaimType = &quot;role&quot;;
          
          options.ApiName = configuration[&quot;AuthConfiguration:ApiName&quot;];
          options.SupportedTokens = SupportedTokens.Jwt;
          options.JwtValidationClockSkew = TimeSpan.FromTicks(TimeSpan.TicksPerMinute);
      });
  </code></pre>