Skip to content

Instantly share code, notes, and snippets.

@Toilal
Last active February 21, 2023 10:30
Show Gist options
  • Select an option

  • Save Toilal/8849bd63d53bd2df2dd4df92d3b12f26 to your computer and use it in GitHub Desktop.

Select an option

Save Toilal/8849bd63d53bd2df2dd4df92d3b12f26 to your computer and use it in GitHub Desktop.

Revisions

  1. Toilal revised this gist Jun 19, 2018. No changes.
  2. Toilal created this gist Jun 19, 2018.
    47 changes: 47 additions & 0 deletions api.module.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { JWT_OPTIONS, JwtInterceptor, JwtModule } from '@auth0/angular-jwt';
    import { AuthorizationService } from './authorization.service';
    import { environment } from '../../environments/environment';
    import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
    import { RefreshTokenInterceptor } from './refresh-token-interceptor';

    function jwtOptionsFactory (authorizationService: AuthorizationService) {
    return {
    tokenGetter: () => {
    return authorizationService.getAccessToken();
    },
    blacklistedRoutes: [`${environment.apiBaseUrl}/login-check`]
    };
    }

    @NgModule({
    imports: [
    CommonModule,
    HttpClientModule,
    JwtModule.forRoot({
    jwtOptionsProvider: {
    provide: JWT_OPTIONS,
    useFactory: jwtOptionsFactory,
    deps: [AuthorizationService]
    }
    })
    ],
    providers: [
    AuthorizationService,
    JwtInterceptor, // Providing JwtInterceptor allow to inject JwtInterceptor manually into RefreshTokenInterceptor
    {
    provide: HTTP_INTERCEPTORS,
    useExisting: JwtInterceptor,
    multi: true
    },
    {
    provide: HTTP_INTERCEPTORS,
    useClass: RefreshTokenInterceptor,
    multi: true
    }
    ],
    declarations: []
    })
    export class ApiModule {
    }
    95 changes: 95 additions & 0 deletions authorization.service.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,95 @@
    import { Injectable } from '@angular/core';
    import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
    import { environment } from '../../environments/environment';
    import { Observable, ReplaySubject } from 'rxjs';
    import { LoginResponse } from './login-response';

    @Injectable({
    providedIn: 'root'
    })
    export class AuthorizationService {
    constructor (private httpClient: HttpClient) {
    }

    loginCheckUrl = `${environment.apiBaseUrl}/login-check`;
    refreshTokenUrl = `${environment.apiBaseUrl}/refresh-token`;

    login (username: string, password: string): Observable<LoginResponse> {
    const body = new HttpParams()
    .set('_username', username)
    .set('_password', password);

    const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');

    const postObservable = this.httpClient.post<LoginResponse>(this.loginCheckUrl, body.toString(), { headers });

    const subject = new ReplaySubject<LoginResponse>(1);
    subject.subscribe((r: LoginResponse) => {
    this.setAccessToken(r.token);
    this.setRefreshToken(r.refresh_token);
    }, (err) => {
    this.handleAuthenticationError(err);
    });

    postObservable.subscribe(subject);
    return subject;
    }

    refresh (): Observable<LoginResponse> {
    const body = new HttpParams().set('refresh_token', this.getRefreshToken());

    const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');

    const refreshObservable = this.httpClient.post<LoginResponse>(this.refreshTokenUrl, body.toString(), { headers });

    const refreshSubject = new ReplaySubject<LoginResponse>(1);
    refreshSubject.subscribe((r: LoginResponse) => {
    this.setAccessToken(r.token);
    this.setRefreshToken(r.refresh_token);
    }, (err) => {
    this.handleAuthenticationError(err);
    });

    refreshObservable.subscribe(refreshSubject);
    return refreshSubject;
    }

    logout () {
    this.setAccessToken(null);
    this.setRefreshToken(null);
    }

    isAuthenticated (): boolean {
    return !!this.getAccessToken();
    }

    private handleAuthenticationError (err: any) {
    // TODO: Only for authentication error codes
    this.setAccessToken(null);
    this.setRefreshToken(null);
    }

    private setAccessToken (accessToken: string) {
    if (!accessToken) {
    localStorage.removeItem('access_token');
    } else {
    localStorage.setItem('access_token', accessToken);
    }
    }

    private setRefreshToken (refreshToken: string) {
    if (!refreshToken) {
    localStorage.removeItem('refresh_token');
    } else {
    localStorage.setItem('refresh_token', refreshToken);
    }
    }

    getAccessToken () {
    return localStorage.getItem('access_token');
    }

    getRefreshToken () {
    return localStorage.getItem('refresh_token');
    }
    }
    29 changes: 29 additions & 0 deletions refresh-token-interceptor.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,29 @@
    import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { Observable, throwError } from 'rxjs';
    import { catchError, mergeMap } from 'rxjs/operators';
    import { AuthorizationService } from './authorization.service';
    import { JwtInterceptor } from '@auth0/angular-jwt';

    @Injectable()
    export class RefreshTokenInterceptor implements HttpInterceptor {
    constructor (private authorizationService: AuthorizationService, private jwtInterceptor: JwtInterceptor) {
    }

    intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (this.jwtInterceptor.isWhitelistedDomain(req) && !this.jwtInterceptor.isBlacklistedRoute(req)) {
    return next.handle(req).pipe(
    catchError((err) => {
    const errorResponse = err as HttpErrorResponse;
    if (errorResponse.status === 401 && errorResponse.error.message === 'Expired JWT Token') {
    return this.authorizationService.refresh().pipe(mergeMap(() => {
    return this.jwtInterceptor.intercept(req, next);
    }));
    }
    return throwError(err);
    }));
    } else {
    return next.handle(req);
    }
    }
    }