Home > Enterprise >  Use an observable inside of an interceptor
Use an observable inside of an interceptor

Time:05-14

I want to write an interceptor to add an auth token to all the requests. My token comes from a lib, angularx-social-login, that only provides an Observable to get the token. So I wrote this but my request is never sent, as if the value was never outputed from the observable.

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import { SocialAuthService } from "angularx-social-login";
import {Observable, switchMap} from "rxjs";
import {Injectable} from "@angular/core";

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
  constructor(private authService: SocialAuthService) {}
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.authService.authState.pipe(
      switchMap((user) => {
        const token = user.idToken
        if (token) {
          request = request.clone({
            setHeaders: {Authorization: `Bearer ${token}`}
          });
        }
        return next.handle(request)
      })
    );
  }
}

CodePudding user response:

I am not 100% sure on this, but I think using a switchMap you are switching to a new observable so return next.handle(request) never gets nexted. Seems like authState is an observable, so we could do pipe(take(1)) without the need to unsubscribe.

I would be more concise and split up getting the token and setting Bearer.

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { SocialAuthService } from 'angularx-social-login';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
  constructor(private authService: SocialAuthService) {}
  intercept( request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    let token;
    this.authService.authState.pipe(take(1)).subscribe(data => token = data);

    if (token) {
      // If we have a token, we set it to the header
      request = request.clone({
        setHeaders: { Authorization: `Bearer ${token}` },
      });
    }

    return next.handle(request).pipe(
      catchError((err) => {
        if (err instanceof HttpErrorResponse) {
          if (err.status === 401) {
            // redirect user to the logout page
          }
        }
        return throwError(err);
      })
    );
  }
}

Would also recommend refactoring the way token can be received. Like updating it to some storage and receiving it from storage via authService. Subscribing each time to an observable is a pain.

AuthService.ts

getAuthToken():string {
 return localeStarage.getItem('token')
 }

You could then just do:

const token = this.authService.getAuthToken();

CodePudding user response:

I guess what happens here is that by the time the subscription to the observable occurs, the value of authState was already emitted in the login process, so the request hangs waiting for a new value to be emitted, which happened already in the login process.

In order to deal with this, I suggest that you implement a service (providedInRoot) to be injected into the Login component and retrieve the user data in the login process.

You could subscribe to the authState observable of SocialAuthService service in the OnInit of the Login component:

// ...

  constructor(private authService: SocialAuthService, myUserService: MyUserService) { }

  ngOnInit() {
    this.authService.authState.subscribe((user) => {
      this.myUserService.user = user;
    });
  }

// ...

Then you could use the value from myUserService.user in your interceptor to retrieve the token.

  • Related