1

When I login API sends to me the token and token-life-time , when token-life-time is going to be end , I refresh my token by sending request to API and receive new token and new refresh-token-time.
When I refresh or navigate to another page (at the moment when token-life-time is over) my interceptor sends old value of token from LocalStorage and API gives me an error 'Not correct token' when I again refresh or navigate to another page it sends correct token.
But it repeats when the token-life-time is going to be over as described above.
Here is my token-interceptor.service.ts

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { LoginService } from '../services/login.service';

@Injectable()
export class TokenInterceptorService implements HttpInterceptor {
  constructor(
    private loginService: LoginService
  ) { }
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (
      this.loginService.isLogged
    ) {
      const token = localStorage.getItem('access-token');
      const headers = new HttpHeaders().set('Authorization', `Bearer ${token}`);
      request = request.clone({ headers: headers });
    }
    return next.handle(request);
  }
}

It takes token and send request to API.

I have the login.service.ts with login and refresh function .Login function put token value into the LocalStorage and Refresh function refreshes the token in LocalStorage if isNeedToRefresh var is true and it works well.

refresh(): Observable<boolean> {
    return this.http.post(`${environment.auth}/refresh`, {
      token_hash: localStorage.getItem('refresh-token')
    }).pipe(
      map((res: any) => {
        if (res.access && res.refresh) {
          localStorage.setItem('access-token', res.access.hash);
          localStorage.setItem('expires-at-access', res.access.expires_at);
          localStorage.setItem('refresh-token', res.refresh.hash);
          localStorage.setItem('expires-at-refresh', res.refresh.expires_at);
          return true;
        } else {
          this.notificationService.error(res && res.result_descr || '');
          return false;
        }
      }),
      catchError(() => of(false))
    );
  }

Here is where I refresh the token in login.component.ts

ngOnInit() {
    if (this.loginService.isLogged) {
      if (this.loginService.isNeedToRefresh === true) {
        this.loginService.refresh().subscribe((res: boolean) => {
          if (res === true) {
            this.router.navigate(['']);
          }
        });
      } else if (this.loginService.isNeedToRefresh === false) {
        this.router.navigate(['']);
      }
    }
  }

Also I update my token in app.component.ts

ngOnInit() {
    $(document).on('click', '[href="#"]', e => e.preventDefault());

    this.router.events.subscribe((val) => {
      if (val instanceof NavigationEnd) {
        if (!(val.url.indexOf('/login') === 0)) {
          this.authWatcher();
        }
      }
    });
  }

authWatcher() {
    if (this.loginService.isLogged) {
      if (this.loginService.isNeedToRefresh === true) {
        this.loginService.refresh().subscribe((refresh: boolean) => {
          if (refresh === false) {
            this.authModalRef = this.modalService.show(this.staticModal, { backdrop: 'static' });
          } else {
            this.loginService.checkToken().subscribe((check: boolean) => {
              if (!check) {
                this.logoutService.logout();
                this.router.navigate(['login']);
              }
            });
          }
        });
  }
}

What's the best way for my interceptor to work well ?

Little update , here is how I check isNeedToRefresh

 get isNeedToRefresh(): boolean {
    const accessExpireTimestamp = new Date(
      localStorage.getItem('expires-at-access')
    ).getTime();
    const refreshExpireTimestamp = new Date(
      localStorage.getItem('expires-at-refresh')
    ).getTime();
    const nowTimestamp = new Date().getTime();
    if (nowTimestamp >= accessExpireTimestamp) {
      if (nowTimestamp >= refreshExpireTimestamp) {
        return null; // Refresh token expired
      } else {
        return true; // Refresh token not expired
      }
    }
    return false;
  }
Sunstrike527
  • 515
  • 1
  • 4
  • 17
  • On the first sight I don't see anything wrong (although I was looking only at the first two blocks). Try logging inside `if (res.access && res.refresh) {` just to check if your code even goes there. – Gynteniuxas Oct 12 '20 at 07:18
  • @GytisTG yes it works , when I reload page. The issue that when session token time is over Interceptor doesn't know that he should use new token , he just takes value form localstorage. I need hard reload page to refresh token in localstorage or find another best way – Sunstrike527 Oct 12 '20 at 08:20
  • I guess I need to refresh token in interceptor , but I dont know how yet – Sunstrike527 Oct 12 '20 at 08:49
  • I checked again carefully and I'm now wondering how are you refreshing the value of `this.loginService.isNeedToRefresh === true`? Because if you don't refresh it after time is expired, this would evaluate to `false` and it wouldn't refresh on time, only on the second time. – Gynteniuxas Oct 12 '20 at 09:08
  • @GytisTG I have updated my question. This method is described in login service – Sunstrike527 Oct 12 '20 at 09:32
  • If you were to update your `isNeedToRefresh` to return always true, would that always send update token or no effect? – Gynteniuxas Oct 12 '20 at 14:23
  • @GytisTG I've made `isNeedToRefresh` to return always true and it refreshes always when I reload page or go to next link inside my app – Sunstrike527 Oct 13 '20 at 03:24
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/222951/discussion-between-gytis-tg-and-aidos-omurzakov). – Gynteniuxas Oct 13 '20 at 11:00

1 Answers1

1

This desicion is worked for me , if someone else would meet such issue I have fully re-writed my interceptor , basing on this link

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { LoginService } from '../services/login.service';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { switchMap, take, filter } from 'rxjs/operators';
@Injectable()
export class TokenInterceptorService implements HttpInterceptor {
  private refreshTokenInProgress = false;
  private refreshTokenSubject: Subject<any> = new BehaviorSubject<any>(null);

  constructor(public loginService: LoginService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (request.url.indexOf('refresh') !== -1) {
      return next.handle(request);
    }

    const accessExpired = this.loginService.accessExpired;
    const refreshExpired = this.loginService.refreshExpired;

    if (accessExpired && refreshExpired) {
      return next.handle(request);
    }
    if (accessExpired && !refreshExpired) {
      if (!this.refreshTokenInProgress) {
        this.refreshTokenInProgress = true;
        this.refreshTokenSubject.next(null);
        return this.loginService.refresh().pipe(
          switchMap((authResponse) => {
            console.log('authResponse ', authResponse)
            if (authResponse) {
              const token = localStorage.getItem('access-token');
              this.refreshTokenInProgress = false;
              this.refreshTokenSubject.next(token);
              return next.handle(this.injectToken(request));
            } else {
              return next.handle(request);
            }
          }),
        );
      } else {
        return this.refreshTokenSubject.pipe(
          filter(result => result !== null),
          take(1),
          switchMap((res) => {
            return next.handle(this.injectToken(request))
          })
        );
      }
    }

    if (!accessExpired) {
      return next.handle(this.injectToken(request));
    }
  }

  injectToken(request: HttpRequest<any>) {
    const token = localStorage.getItem('access-token');
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }
}
Sunstrike527
  • 515
  • 1
  • 4
  • 17