skip to Main Content

I’m building an authentication app using the PEAN stack (i.e., PostgreSQL – ExpressJS – Angular – NodeJS). For authentication, I’m using express-session on the backend.

I check for user sign-in status as follows:

  1. On the backend, check the session cookie to see if the user property exists in the req.session object.

server.js

/* ... */

app.post('/api/get-signin-status', async (req, res) => {
  try {
    if (req.session.user) {
      return res.status(200).json({ message: 'User logged in' });
    } else {
      return res.status(400).json({ message: 'User logged out' });
    }
  } catch {
    return res.status(500).json({ message: 'Internal server error' });
  }
});

/* ... */
  1. Send an HTTP POST request to the api/get-signin-status endpoint with optional data and include a cookie in the request.

auth.service.ts

/* ... */

getSignInStatus(data?: any) {
  return this.http.post(this.authUrl + 'api/get-signin-status', data, {
    withCredentials: true,
  });
}

/* ... */
  1. Intercept any HTTP request and provide an observable interceptorResponse$ for subscribing to the response of intercepted requests.

interceptor.service.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { AuthService } from 'src/app/auth/services/auth.service';

@Injectable({
  providedIn: 'root',
})
export class InterceptorService implements HttpInterceptor {
  private interceptorResponse$: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const signInStatusObserver = {
      next: (x: any) => {
        this.interceptorResponse$.next({ success: true, response: x });
      },

      error: (err: any) => {
        this.interceptorResponse$.next({ success: false, response: err });
      },
    };

    this.authService.getSignInStatus().subscribe(signInStatusObserver);

    return next.handle(httpRequest);
  }

  getInterceptorResponse(): Observable<any> {
    return this.interceptorResponse$.asObservable();
  }

  constructor(private authService: AuthService) {}
}
  1. On the frontend, subscribe to the interceptorResponse observable from the InterceptorService and log the response to the console.

header.component.ts

import { Component, OnInit } from '@angular/core';
import { InterceptorService } from '../auth/services/interceptor.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
  interceptorResponse: any;

  constructor(
    private interceptorService: InterceptorService
  ) {
    this.interceptorService.getInterceptorResponse().subscribe((response: any) => {
      console.log(response);

      this.interceptorResponse = response;
      if (response) {
        console.log('Interceptor response success:', response.response);
      } else {
        console.log('Interceptor response is null');
      }
    });
  }

  ngOnInit(): void {}
}

Problem

According to the StackOverflow answer, I should use BehaviorSubject. The problem is that in the console, I always get the following:

Screenshot

But if I console log next and error like this:

interceptor.service.ts

/* ... */

const signInStatusObserver = {
  next: (x: any) => {
    console.log(x);
    this.interceptorResponse$.next({ success: true, response: x });
  },

  error: (err: any) => {
    console.log(err.error.message);
    this.interceptorResponse$.next({ success: false, response: err });
  },
};

/* ... */

I see the expected {message: 'User logged in'} in the console, as shown in the screenshot below. This means that the backend correctly passes sign-in status to the frontend.

Screenshot

Question

Why does the Angular interceptor always return null using BehaviorSubject?


EDIT

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { HeaderComponent } from './header/header.component';
import { AppComponent } from './app.component';
import { FooterComponent } from './footer/footer.component';

import { AppRoutingModule } from './app-routing.module';
import { RoutingComponents } from './app-routing.module';

import { SharedModule } from './shared/shared.module';

import { HttpClientModule } from '@angular/common/http';

import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';

import { CodeInputModule } from 'angular-code-input';

import { IfSignedOut } from './auth/guards/if-signed-out.guard';
import { IfSignedIn } from './auth/guards/if-signed-in.guard';

import { InterceptorService } from './auth/services/interceptor.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  declarations: [HeaderComponent, AppComponent, FooterComponent, RoutingComponents],
  imports: [BrowserModule, BrowserAnimationsModule, AppRoutingModule, SharedModule, HttpClientModule, MatMenuModule, MatSidenavModule, CodeInputModule],
  providers: [IfSignedOut, IfSignedIn, { provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true }],
  bootstrap: [AppComponent],
})
export class AppModule {}

2

Answers


  1. The "interceptor response is null" might be expected: the way you are using the RxJS BehaviorSubject in your interceptor may be causing this issue.

    The purpose of an interceptor is to intercept HTTP requests and do something with those requests or responses.
    (See for instance "Intro to Angular Http Interceptors" from Cory Rylan)

    But in your interceptor, you are creating a new HTTP request by calling this.authService.getSignInStatus().subscribe(signInStatusObserver); instead of intercepting an existing one. That means the response to this request might not be available immediately when your component subscribes to getInterceptorResponse().
    … hence possibly the null interceptor response.


    As an example, you could use an interceptor to check the user’s authentication status:

    // interceptor.service.ts
    
    import { tap, catchError } from 'rxjs/operators';
    
    @Injectable({
      providedIn: 'root',
    })
    export class InterceptorService implements HttpInterceptor {
      constructor(private authService: AuthService) {}
    
      intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(httpRequest).pipe(
          tap((event: HttpEvent<any>) => {
            if (event instanceof HttpResponse && httpRequest.url.endsWith('/api/get-signin-status')) {
              this.authService.setSignInStatus({ success: true, response: event.body });
            }
          }),
          catchError((err: any) => {
            if (httpRequest.url.endsWith('/api/get-signin-status')) {
              this.authService.setSignInStatus({ success: false, response: err });
            }
            return throwError(err);
          })
        );
      }
    }
    

    In this updated version, we are subscribing to the handle method of next, which returns an Observable of the HTTP response. It uses the tap operator to do something with the successful responses and the catchError operator to handle errors.

    I am assuming setSignInStatus is a method in your AuthService that updates the sign-in status stored in a BehaviorSubject.
    I am also checking if the request URL ends with ‘/api/get-signin-status‘ to make sure we are only setting the sign-in status for that specific request.

    That means you might implement this user’s authentication status with:

    // auth.service.ts
    
    private signInStatus$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
    
    getSignInStatusObserver(): Observable<any> {
      return this.signInStatus$.asObservable();
    }
    
    setSignInStatus(status: any): void {
      this.signInStatus$.next(status);
    }
    

    You would need to update your header.component.ts to use authService instead of interceptorService:

    // header.component.ts
    
    constructor(private authService: AuthService) {
      this.authService.getSignInStatusObserver().subscribe((response: any) => {
        console.log(response);
    
        this.interceptorResponse = response;
        if (response) {
          console.log('Interceptor response success:', response.response);
        } else {
          console.log('Interceptor response is null');
        }
      });
    }
    

    Finally, in your component, you would then subscribe to getSignInStatusObserver instead of getInterceptorResponse.

    That ensures that the sign-in status is updated every time an HTTP response is received from ‘/api/get-signin-status‘ and that components can subscribe to the status updates.

    The subscription in your component would look like:

    // header.component.ts
    
    import { Component, OnInit } from '@angular/core';
    import { AuthService } from '../auth/services/auth.service';
    
    @Component({
      selector: 'app-header',
      templateUrl: './header.component.html',
      styleUrls: ['./header.component.scss'],
    })
    export class HeaderComponent implements OnInit {
      signInStatus: any;
    
      constructor(private authService: AuthService) {
        this.authService.getSignInStatusObserver().subscribe((response: any) => {
          // That block of code will run every time the signInStatus updates
          console.log(response);
    
          this.signInStatus = response;
          if (response) {
            console.log('Sign in status success:', response.response);
          } else {
            console.log('Sign in status is null');
          }
        });
      }
    
      ngOnInit(): void {}
    }
    

    In the constructor of HeaderComponent, we are injecting AuthService instead of InterceptorService. We are subscribing to getSignInStatusObserver(), which is a method in AuthService that returns an Observable of the sign-in status. When the sign-in status is updated, the subscribed function will be called with the new status as the argument.

    We are storing the sign-in status in the signInStatus property and logging it to the console. We are also logging a success message if the response exists and a message indicating the sign-in status is null otherwise. That mirrors the behavior of your original code.

    Login or Signup to reply.
  2. I’m guessing the problem is that Angular creates multiple instances of your interceptorService. One actually used as an interceptor and one is injected into your components/services.

    I vaguely remember having the same problem. Back then, I solved it like this:

    class Interceptor{
        constructor(private final service: SharedDataService){}
    }
    
    class Component{
        constructor(private final service: SharedDataService){}
    }
    

    Both the component and interceptor use another service to share data.

    EDIT

    A quick search actually confirms this. See Interceptor create two instances of singleton service

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search