Skip to content

Instantly share code, notes, and snippets.

@carlosocarvalho
Forked from filipemansano/jwt.interceptor.ts
Created January 6, 2018 00:56
Show Gist options
  • Select an option

  • Save carlosocarvalho/facd15a64f6e613986a15b2673338a8b to your computer and use it in GitHub Desktop.

Select an option

Save carlosocarvalho/facd15a64f6e613986a15b2673338a8b to your computer and use it in GitHub Desktop.

Revisions

  1. @filipemansano filipemansano created this gist Jan 2, 2018.
    162 changes: 162 additions & 0 deletions jwt.interceptor.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,162 @@
    import { Injectable, Injector } from '@angular/core';

    import {
    HttpInterceptor,
    HttpRequest,
    HttpHandler,
    HttpEvent
    } from '@angular/common/http';


    import { Observable } from 'rxjs/Observable';
    import { BehaviorSubject } from 'rxjs/BehaviorSubject';

    import { finalize } from 'rxjs/operators/finalize';
    import { catchError } from 'rxjs/operators/catchError';
    import { switchMap } from 'rxjs/operators/switchMap';

    import { take } from 'rxjs/operators';
    import { filter } from 'rxjs/operators';

    import 'rxjs/add/observable/throw';

    import { AuthService } from '@app/core/security/auth/auth.service';


    /**
    * Interceptador HTTP para adicionar o Token nas requisições ao serve
    * e mapear os erros a fim de verificar erro de token expirado
    * tentando renova-lo caso possivel, aceitando multiplas chamadas assincronas
    * com o token expirado criando uma espece de "fila" até que a 1º chamada que
    * solicitou o token seja resolvida.
    *
    * @author Filipe Mansano
    * @version 1.0
    * @since 2018-01-02
    */

    @Injectable()
    export class JWTInterceptor implements HttpInterceptor {

    isRefreshingToken: boolean = false;
    tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);

    constructor(private inj: Injector) { }


    /**
    * Função que clona o request adicionando o token nos cabeçalhos
    * @param request
    * @param token
    */
    addToken(request: HttpRequest<any>, token: string): HttpRequest<any> {
    return request.clone({
    setHeaders: {
    Authorization: `Bearer ${token}`
    }
    });
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const authService = this.inj.get(AuthService);

    /**
    * no login nao quero interceptar nada, então
    * do continuidade no fluxo do request
    */
    if (request.url.includes("/auth/login")) {
    return next.handle(request);
    }

    const requestJWT = this.addToken(request, authService.getToken().token);

    // capturando os possiveis erros do request
    return next.handle(requestJWT).pipe(

    catchError(err => {

    /**
    * Caso o erro seja um código 401
    * inicio a logica pra o refresh do token
    */
    if (err.status === 401) {

    // verifico se o motivo foi token expirado
    if (err.error && err.error.errorMessage.includes("expirado")) {

    // se o refresh de token não tiver em andamento, inicio ele
    if (!this.isRefreshingToken) {

    this.isRefreshingToken = true;

    /**
    * Reiniciando o valor do token aqui para que os proximos pedidos
    * aguardem até que o token volte da chamada de atualização do token.
    */
    this.tokenSubject.next(null);

    return authService.refreshToken().pipe(
    finalize(() => {
    this.isRefreshingToken = false;
    }),
    switchMap((newToken: string) => {
    if(newToken){
    this.tokenSubject.next(newToken);
    return next.handle(this.addToken(request, newToken));
    }

    /**
    * se não for retornado o token, deu algum problema
    * então desconecto o usuario e lanço a exceção pra frente
    */
    authService.logoff();
    return Observable.throw("Não foi possivel obter o token");
    }),

    /**
    * Caso de qualquer erro no refresh do token
    * não tem oque fazer, então deslogo o usuario
    */
    catchError(errTokenRefresh => {

    authService.logoff();
    return Observable.throw(errTokenRefresh);
    })
    );
    }

    /**
    * Caso o token esteja em processe de atualização
    */
    else {

    return this.tokenSubject.pipe(
    filter(token => token != null),
    take(1),
    switchMap(token => {
    return next.handle(this.addToken(request,token));
    })
    );

    }
    }

    /**
    * Caso o motivo do erro 401 não contenha a palavra expirado
    * deslogo o usuario, pois é algum erro desconhecido...
    */
    else {
    authService.logoff();
    return Observable.throw(err);
    }
    }

    // qualquer outro erro lanço uma exceção
    else{
    return Observable.throw(err);
    }
    })
    );
    }
    }