Search code examples
angularrxjsangular-social-login

Use an observable inside of an interceptor


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)
      })
    );
  }
}

Solution

  • 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:

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { SocialAuthService } from "angularx-social-login";
    import { Subject } from 'rxjs';
    import { takeUntil } from 'rxjs/operators';
    
    // ... the rest of import
    
    @Component({
      // ... Component decorator props (selector, templateUrl, styleUrls)
    })
    export class LoginComponent implements OnInit, OnDestroy {
      destroy = new Subject<boolean>();
    
      constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
    
      ngOnInit(): void {
        this.authService.authState.pipe(takeUntil(this.destroy)).subscribe((user) => {
          this.myUserService.user = user;
        });
      }
    
      // ... the rest of the component
    
      ngOnDestroy(): void {
        this.destroy.next(true);
        this.destroy.complete();
      }
    
    }
    

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

    I have used the takeUntil rxjs operator with a Subject for ngOnDestroy, but you could also store the subscription as a class variable and perform the unsubscribe.

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { SocialAuthService } from "angularx-social-login";
    import { Subscription } from 'rxjs';
    
    // ... the rest of import
    
    @Component({
      // ... Component decorator props (selector, templateUrl, styleUrls)
    })
    export class LoginComponent implements OnInit, OnDestroy {
      authStateSubscription: Subscription;
    
      constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
    
      ngOnInit(): void {
        this.authStateSubscription = this.authService.authState.subscribe((user) => {
          this.myUserService.user = user;
        });
      }
    
      // ... the rest of the component
    
      ngOnDestroy(): void {
        this.authStateSubscription.unsubscribe();
      }
    
    }
    

    In both ways should work.