Angular – Managing Authentication State Using NGXS

| By Maina Wycliffe | | | Angular

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:

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

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

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 Model

@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

// Auth State

// ...
export class AuthState {
  // ...

  @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):

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.

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:


signin(): void {
    this._store
      .dispatch(new Signin('[email protected]', 'password'))
      .subscribe(success => {}, error => {});
}

The same goes for our sign out method:

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:

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

return next.handle(modifiedRequest);

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

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:

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:

// ...

export class GuardGuard implements CanActivate {
  // ...

  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.

Angular 6 - Angular CLI Workspaces

One of the least talked about features of Angular 6 is Angular CLI Workspaces. Workspaces or Angular CLI Workspaces give angular developers …

Read More
Angular Tips to Improve your Development Experience

Developing web apps using Angular and other frameworks and languages can be sometime a daunting task. You need to write thousands of lines …

Read More
A Guide for Building Angular 6 Libraries

In an earlier post, I wrote about Angular CLI Workspaces, a new feature in Angular 6 that allows you to have more than one project in a …

Read More
Angular 6 – Introduction to Progressive Web Apps

Earlier this week, I wrote a post about optimizing your angular application using lazy loading. In this post, I will focus on Progressive …

Read More
Optimizing your Angular App using Lazy Loading

Lazy loading is a technique where you defer loading of content until such a time it’s needed. In single page apps (SPA), usually the whole …

Read More
Using custom date formats for Angular Material Date Picker

Angular material has a very nice date picker but while its functionality can accommodate a lot of developers, sometimes it just falls short. …

Read More

Comments