Angular – Managing Authentication State Using NGXS

Managing an apps Authentication State is a vital task that as Single Page App (SPA) developers, have to handle very often. It’s every developers goal to provide a consistent user experience in your application, depending on whether they are signed in or not.

In this post, we are going to build an authentication system that relies on NGXS for state management. NGXS is a state management library made for Angular. There are other alternative of course like the popular NGRX. You can learn more about NGXS here.

Introduction

I want to demonstrate a comprehensive authentication solution that covers all foreseeable angles. This will include:

  1. Authentication Service – to interface with your backend
  2. A guard to protect your routes
  3. HTTP Interceptor – To automatically attach authorization tokens to outgoing requests
  4. State Management – Store Authorization Token, Maintain a State

Getting Started

First, you will need to install both NGXS and a NGXS Storage Plugin.

yarn add  @ngxs/store @ngxs/storage-plugin

Then, and it to your imports in the App Module:

// Main Module (app.module.ts)
import { NgModule } from '@angular/core';
import { NgxsModule } from '@ngxs/store';
​@NgModule({
  imports: [
    NgxsModule.forRoot([])
  ]
})

export class AppModule {}

For now, let’s leave the forRoot array empty. We will come back to this later once we have created our authentication state. Next, let’s work on our Authentication Service.

Authentication Service

In this service, we will have a couple of methods to authenticate users. The most common use case is to send http requests to the server and return observables.

In our service we will have two methods: A sign in and a sign out method. The sign in method will return an observable with user details while the sign out will return a null observable.

// Auth Service Methods (auth.service.ts)

signin(email: string,password: string): Observable<{ name: string; email: string }> {

    /*
      This will presumably call a http backend and return the response
      return this.http.post<{{ name: string; email: string }}>(url, { email: email, password: password })
      .pipe(
        map(res => res.body)
      )
      */

    return of({
      name: 'Some Name',
      email: '[email protected]'
    });
}

signout(): Observable<null> {
    return of(null);
}

Auth State Management

The first thing we need to create are model and action class for our state. You can put each on its own separate file, or all of them in on a single file. Then, we need to create a single model to hold our current authentication model/data.

// Auth State Model (state-models.ts)
export class AuthStateModel {
  // if you have an extra token like authorization, you can add it here plus any other user information you might want to store
  token?: string;
  // refreshToken?: string;
  email?: string;
  name?: string;
}

After that, we need to create two actions – A Sign in action and a Sign out action:

// Auth State Model (state-models.ts)
export class Signin {
  static readonly type = '[Auth] Signin';
  // these are parameters to pass to the action when dispatching it also known as metadata
  // they work just like normal parameters in normal parameters
  constructor(private email: string, private password: string) {}
}

export class Signout {
  static readonly type = '[Auth] Signout';
}

Next, we need to create our state class (authentication state class) and wire everything up:

// Auth State (auth-state.ts)
@State<AuthStateModel>({
  name: 'auth' // the name of our state
})

export class AuthState {
  @Selector()
  static token(state: AuthStateModel) {
    return state.token;
  }

  @Selector()
  static userDetails(state: AuthStateModel) {
    return {
      name: state.name,
      email: state.email
    };
  }
  /*

  You can add extra selectors for different information stored with our state like refresh token and other personal details
  @Selector()
  static refreshToken(state: AuthStateModel) {
    return state.refreshToken;
  }
  */

  constructor(private authService: AuthService) {}

  @Action(Signin)
  login({ patchState }: StateContext<AuthStateModel>,{ email, password }: Signin) {
    return this.authService.signin(email, password).pipe(
      tap(result => {
         patchState({
             token: result.token,
             name: result.name,
             email: result.email
         });
      })
    );
  }

  @Action(Signout)
  logout({ setState, getState }: StateContext<AuthStateModel>) {
    const { token } = getState();
    return this.authService.signout().pipe(
      tap(_ => {
        setState({});
      })
    );
  }
}

And finally, we need to add our new Auth State to the state of array of NgxsModule.forRoot([]) method (The array we left empty at the beginning):

// app.module.ts
NgxsModule.forRoot([AuthState])],

And because we want our authentication state to survive page reloads, lets import the NGXS Storage Plugin we installed into our app module.

// app.module.ts
NgxsStoragePluginModule.forRoot({
      key: ['auth.token ', 'auth.email ', 'auth.name ']
})

NB: The keys passed start with the name of our state followed by the item we are storing. So, if you check local storage on the browser, you will find the token under auth.token instead of just token.

Signing in and out

Now, we can add a method to sign in and sign out in our components. Instead of directly calling sign in and sign out methods in Auth Service, we will call on NGXS Store to dispatch sign in and sign out actions. Then we will subscribe to the observable:

// Sign in component (not-signed-in-user.component.ts)
signin(): void {
    this._store
      .dispatch(new Signin('[email protected]', 'password'))
      .subscribe(success => {}, error => {});
}

The same goes for our sign out method:

// Sign in component (signed-in-user.component.ts)
this._store.dispatch(new Signout()).subscribe(success => {}, error => {});

NB: Remember to inject NGXS Store into your components:

import { Store } from '@ngxs/store';

And:

constructor(private _store: Store) {}

HTTP Interceptor

In one of my earliest posts, I covered HTTP Interceptors in Angular, in this section I won’t go into details about them. Our http interceptor will catch all requests and attach our token from our auth state. We are going to fetch the token from our auth state and attach it to every outgoing requests.

First, get our auth token from the auth state:

const token = this._store.selectSnapshot(state => state.auth.authorization);

NB: We are using selectSnapshot to get the token because it returns a simple string/value that is readily usable by our http interceptor. There is no need to subscribe to get the token we need.

Then, clone and attach the token to the header of our intercepted http request:

// http interceptor (http-interceptor.ts)
// Clone the request to add the new header.
const modifiedRequest = req.clone({
      headers: req.headers.set('authorization', token ? token : '')
});

NB: Always remember to always validate the token on the server side.

And finally, send the modified request:

// http interceptor (http-interceptor.ts)
return next.handle(modifiedRequest);

Don’t forget to add it to your list of providers in the app module:

// app.module.ts

providers: [
    ...
    AuthService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: OurHttpInterceptor,
      multi: true
    },
    ...
],

Router Guard

Router Guards are used to protects routes from unauthorized access. In our case, we need to check whether the user is authorized and if not redirect to the login page. We need to get the token, from our auth state and check if it is set. If not, then redirect back to login or home page:

First, inject NGXS store and router into our auth guard:

// Route Guard (guard.guard.ts
constructor(private _store: Store, private router: Router) {}

Then, inside of our canActivate method, we need to check if the token is set, and if not redirect to the sign in page:

// Route Guard (guard.guard.ts) 
canActivate(next: ActivatedRouteSnapshot,state: RouterStateSnapshot): boolean {
    const token = !!this._store.selectSnapshot(
      ourState => ourState.auth.authorization
    );

    if (!token) {
      this.router.navigate(['/']);
      return false;
    }
    return true;
  }

The same will do for canActivateChild method if you need one.

And that’s it. You should now have a functioning authentication system using NGXS that can: sign in and sign out users, maintain authentication state, attach authorization tokens to your HTTP request and protect routes from unauthorized requests. You can find all the above code in the Github Repository here.

2 Replies to “Angular – Managing Authentication State Using NGXS”

  1. Hey there – great article!

    I have a small suggestion: you might want to point-out, that no matter how much you protect client-side code in the way you mentioned, it is also important to always perform token validity checks on the server-side, because tokens supplied to the client may be altered at will and thus invalid.

    Other than that I managed to follow this article for the most part. Thank you :)!

    1. Thanks, for the great suggestion. I will add so that people do not make the mistake of not checking the validity of the token on the server side.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.