You may have heard recently about a topic whizzing around the Angular community: facades in NgRx. I thought it’d be a great idea to write an article to talk through this issue. Let’s learn what facades are, the arguments for and against them, and how to implement one. (You can find the sample code in this repository.)

What are facades?

Famous facade of Buckingham Palace in London I took this picture of the facade of Buckingham Palace when I was in London this November. Even though I know by looking at it that it’s Buckingham Palace, I have no idea what’s inside — and those guards are there to make sure I don’t find out.

In code, the term “facade” refers to the facade pattern, which is a structural design pattern from the famous book Design Patterns (usually called the “Gang of Four” in reference to the authors). Just like the front of a building hides what’s inside to passersby, a facade in code hides the complexity of underlying services by only exposing certain methods. You’ll often hear this hiding of complexity referred to as “adding a layer of abstraction.” An abstraction is a simpler and more general model of something that only provides what its consumer needs to know.

The facade pattern, and abstraction in general, is similar to the relationship between you and your car. You only need to know that when you turn the key and press the gas pedal, your car moves forward. You don’t need to care about the underlying mechanics of the engine or the science of combustion in order to go to the grocery store.

Here's how the facade pattern can be represened in a diagram:

Facade pattern UML diagram

Source

You can see here how the clients don't know anything about the other services. They simply call doSomething() and the facade handles working with the services behind the scenes.

Facades in NgRx

Recently, there has been a lot of discussion about whether or not to use a facade with NgRx to hide away bits like the store, actions, and selectors. This was sparked by an article by Thomas Burleson called NgRx + Facades: Better State Management. A facade is basically just an Angular service that handles any interaction with the store. When a component needs to dispatch an action or get the result of a selector, it would instead call the appropriate methods on the facade service.

This diagram illustrates the relationship between the component, the facade, and the rest of NgRx:

Facade pattern relationship between component, facade, and the rest of NgRx

Let’s take a look at an example to understand this better. Here’s the BooksPageComponent from the sample code for my NgRx Authentication Tutorial (I’ve hidden the code inside of the Component decorator to make it easier to read):

// src/app/books/components/books-page.component.ts
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';

import * as BooksPageActions from '../actions/books-page.actions';
import { Book } from '../models/book';
import * as fromBooks from '../reducers';
import { Logout } from '@app/auth/actions/auth.actions';

@Component({
 /* ...hidden for readability */
})
export class BooksPageComponent implements OnInit {
  books$: Observable<Book[]>;

  constructor(private store: Store<fromBooks.State>) {
    this.books$ = store.pipe(select(fromBooks.getAllBooks));
  }

  ngOnInit() {
    this.store.dispatch(new BooksPageActions.Load());
  }

  logout() {
    this.store.dispatch(new Logout());
  }
}

This component imports the store, some actions, and some reducers. It dispatches the Load action during ngOnInit and uses a selector for the books in the constructor. It also dispatches an action when the user logs out.

If we were instead using a facade in the component, the class would look something like this (I’ll leave out the imports and decorator for the sake of brevity):

// src/app/books/components/books-page.component.ts
// above remains the same except for the imports
export class BooksPageComponent implements OnInit {
  books$: Observable<Book[]>;

  constructor(private booksFacade: BooksFacade) {
    this.books$ = this.booksFacade.allBooks$;
  }

  ngOnInit() {
    this.booksFacade.loadBooks();
  }

  logout() {
    this.booksFacade.logout();
  }
}

We’ve replaced the selector with an observable on the booksFacade service. We’ve also replaced both action dispatches with calls to methods on the booksFacade service.

The facade would look like this (again leaving out the imports for the sake of brevity):

// imports above
@Injectable()
export class BooksFacade {
  allBooks$: Observable<Book[]>;

  constructor(private store: Store<fromBooks.State>) {
    this.allBooks$ = store.pipe(select(fromBooks.getAllBooks));
  }

  getBooks() {
    this.store.dispatch(new BooksPageActions.Load());
  }

  logout() {
    this.store.dispatch(new Logout());
  }
}

Looking at the component code in isolation, you’d have no idea that this Angular application is using NgRx for state management. So, is that a good thing or a bad thing? Let’s talk about some pros and cons to this approach.

The Case for Facades

Let’s first consider some pros of the facade pattern for NgRx.

Pro #1: Facades provide a better developer experience.

One of the biggest arguments for using the facade pattern is its boost to developer experience. NgRx often gets flack for requiring a lot of repetitive code for setup and for the difficulty of maintaining and scaling a lot of moving parts. Every feature requires changes to state, the store, actions, reducers, selectors, and effects. By adding a layer of abstraction through the facade, we decrease the need to directly interact with these pieces. For example, a new developer writing a new feature listing books could just inject the BooksFacade and call loadBooks() without needing to worry about learning the intricacies of the store.

Pro #2: The facade pattern is easier to scale than plain NgRx.

The second argument for the facade pattern is how easy it is to scale. Let’s say, for example, we needed to develop seven new features for a large application. Several of those features would probably overlap in some of the ways they affect the state of the application, as well as in the data they consumed. We could cut out a lot of repetitive work by using facades:

  • First, we'd determine the smallest number of state changes we need to make based on unique use cases.
  • Next, we'd add them to the right places in the application-wide NgRx setup files (like changes to the application state or reducers).
  • Finally, we'd appropriate methods and selectors to our facade.

The seven new features could then be added quickly while letting the facade worry about the underlying NgRx pieces.

"Using facades in NgRx can increase dev productivity and make apps easier to scale."

The Case Against Facades

This reduced friction in development and scaling really sound great, but is everything a bed of roses? Let’s take a look at the other side of the argument.

Con #1: Facades break the indirection of NgRx.

The first time I saw facades used with NgRx, I had an immediate response of, “Wait, we just spent all this time setting up NgRx actions, reducers, and effects, but now we’re hiding all of that away with a service?” It turns out that gut feeling I had is one of the main arguments against using facades.

While NgRx does get criticized for having a lot of moving parts, each of those parts has been designed to perform a specific function and communicate with other parts in a specific way. At its core, NgRx is like a messaging system. When a user clicks a “Load Books” button, the component sends a message (an action) that says, “Hey, load some books!” The effect hears this message, fetches the data from the server, and sends another message as action: “Books are loaded!” The reducer hears this message and updates the state of the application to “Books loaded.”

We call this indirection. Indirection is when part of your application is responsible for something, and the other pieces simply communicate with it through messaging. Neither the reducer nor the effects know about the button that was pressed. Likewise, the reducer doesn’t know anything about where the data came from or the address of the API endpoint.

When you use facades in NgRx, you circumvent this design (at least in practice). The facade now knows about dispatching actions and accessing selectors. At the same time, the developer working on the application no longer knows anything about this indirection. There’s simply now a new service to call.

Con #2: Facades can lead to reusing actions.

With NgRx’s indirection circumvented and hidden away from developers, it becomes very tempting to reuse actions. This is the second major disadvantage to the facade pattern.

Let’s say we’re working on our books application. We’ve got two places where a user can add a new book: 1) the list of books and 2) a book’s detail page. It would be tempting to add a method to our facade called addBook() and use it to dispatch the same action in both of these instances (something like [Books] Add Book).

However, that would be an example of poor action hygiene. When we come back to this code in a year or two because of a bug that’s cropped up, we won’t know when we’re debugging where [Books] Add Book came from. Instead, we’d be better off following Mike Ryan’s advice in his ng-conf 2018 talk Good Action Hygiene. It’s better to use actions to capture events, not commands. In our example, our booksReducer could simply have an additional case:

function booksReducer(state, action){
  switch (action.type) {
    case '[Books List] Add Book':
    case '[Book Detail] Add Book':
      return [...state, action.book];
    default:
      return state;
  }
}

When writing actions, we always want to focus on clarity over brevity. Good actions are actions you can read after a year and tell where they are being dispatched.

When it comes to the facade pattern, we can mitigate this problem by creating a dispatch method in our facade instead of abstracting away actions. In our example above, instead of having a generic addBook() method, we’d call facadeService.dispatch(new AddBookFromList()) or facadeService.dispatch(new AddBookFromDetail()). We lose a little bit of abstraction by doing this, but it will save us headaches in the future by following best practices for action creation.

So, which is it?

Facades can greatly speed up development time in large NgRx apps, which keeps developers happy and makes scaling up a lot easier. On the other hand, the added layer of abstraction can defeat the purpose of NgRx’s indirection, causing confusion and poor action hygiene. So, which wins out?

All developers at some point in their career will learn that increased abstraction always comes at a price. Abstraction trades transparency for convenience. This may turn up in difficulty debugging or difficulty maintaining, but it will come up at some point. It’s always up to you and your team to determine whether that trade-off is worth it and how to deal with any downsides. Some folks in the Angular community argue that the benefits of the facade pattern outweigh the cost of increased abstraction, and some argue they don’t.

I believe the facade pattern certainly has a place in NgRx development, but I’d offer two caveats. If you’re going to use facades with NgRx:

  1. Make sure your developers understand the NgRx pattern, how its indirection works, and why you’re using a facade, and
  2. Promote good action hygiene by using a dispatch method in your facade service instead of abstracting actions.

By teaching your teammates the NgRx pattern while also using a facade, you’ll save yourself some headaches when things start to break. By using a dispatch method in your facade, you’ll mitigate the tendency to reuse actions at the expense of keeping your code readable and easy to debug.

"Even though using facades in NgRx can be really helpful, it's important to keep good action hygiene in mind and not reuse actions."

Implementing a Facade

Now that we know why we may or may not want to use a facade with NgRx, let’s create one. We’re going to add a facade to a simple books application and use the facade to simplify the component that lists the book collection. We’ll also use good action hygiene by using the dispatch pattern described above.

Set Up the Application

To get started, we'll need to be sure Node and npm are installed. We’ll also need the Angular CLI:

npm install -g @angular/cli

The code for this tutorial is at the Auth0 Blog repository. Here are the commands we’ll need:

git clone https://github.com/auth0-blog/ngrx-facades.git
cd ngrx-facades
npm install
git checkout 8e24360

Test the Application

We should now be able to run ng serve, navigate to http://localhost:4200, and click “See my book collection.”

Angular application running on localhost port 4200

Add the Facade Service

Since a facade is just a service, we can generate our initial code with the Angular CLI:

ng generate service /books/services/books-facade --spec=false

We’ll now have a services folder inside of our books folder with a file called books-facade.service.ts. Opening it, we’ll see the following:

// src/app/books/services/books-facade.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class BooksFacadeService {

  constructor() { }
}

Note: You’re welcome to drop Service from the class name. Some people also remove service from the file name and move this file somewhere more related to state to prevent confusion with HTTP services. I’m leaving those details up to you and sticking with some simple defaults.

Let’s think about what we initially want our new facade to do for us. We’re going to use it in the BooksPageComponent we discussed above. Let’s remind ourselves what that component class looks like:

// src/app/books/components/books-page.component.ts
// Omitting Component decorator and imports for brevity.
export class BooksPageComponent implements OnInit {
  books$: Observable<Book[]>;

  constructor(private store: Store<fromBooks.State>) {
    this.books$ = store.pipe(select(fromBooks.getAllBooks));
  }

  ngOnInit() {
    this.store.dispatch(new BooksPageActions.Load());
  }
}

Our component does the following:

  • Calls a selector for the books
  • Dispatches a Load action from the store during the ngOnInit lifecycle hook.

Let’s implement replacements for these in our facade. We’ll be able to simply copy over quite a bit of this code with a few modifications.

We know that we need to inject the Store in the constructor, which will take the State exported from src/app/books/reducers/books.ts just as it is in the BooksPageComponent. We also know we’ll need a dispatch method that takes an Action (which we’ll need to import). Let’s update our facade accordingly by importing what we need and adding those things:

// src/app/books/services/books-facade.service.ts
import { Injectable } from '@angular/core';
import { Store, Action } from '@ngrx/store';

import * as fromBooks from '../reducers';

@Injectable({
  providedIn: 'root'
})
export class BooksFacadeService {

  constructor(private store: Store<fromBooks.State>) { }

  dispatch(action: Action) {
    this.store.dispatch(action);
  }
}

Notice that we’re using the same imports and the same injection of the store as in the component.

Finally, let’s initialize a new observable for the books and use a selector in the constructor. We’ll basically copy over what we’ve got in the component right now, but let’s change the name of the observable to allBooks$ just to be clear. We’ll also need to import Observable from rxjs, add select to our imports from ngrx/store, and import the Book model. The finished code will look like this:

// src/app/books/services/books-facade.service.ts
import { Injectable } from '@angular/core';
import { Store, Action, select } from '@ngrx/store';
import { Observable } from 'rxjs';

import * as fromBooks from '../reducers';
import { Book } from '../models/book';

@Injectable({
  providedIn: 'root'
})
export class BooksFacadeService {
  allBooks$: Observable<Book[]>;

  constructor(private store: Store<fromBooks.State>) {
    this.allBooks$ = store.pipe(select(fromBooks.getAllBooks));
  }

  dispatch(action: Action) {
    this.store.dispatch(action);
  }
}

Our facade is done! Now let’s update our books page component.

Update the Books Page

The first thing we’ll do to update the BooksPageComponent is replace the injection of the store with the BooksFacadeService:

// src/app/books/components/books-page.component.ts
// no changes to above imports
import { BooksFacadeService } from '../services/books-facade.service';

@Component({
// hidden, no changes
})
export class BooksPageComponent implements OnInit {
  books$: Observable<Book[]>;

  constructor(private booksFacade: BooksFacadeService) {
     this.books$ = store.pipe(select(fromBooks.getAllBooks));
  }

  ngOnInit() {
    this.store.dispatch(new BooksPageActions.Load());
  }
}

Of course, we’ll now see red squiggles in our editor underneath the references to the store. We can fix those by replacing the selector with booksFacade.allBooks$ and the other reference to store inside of ngOnInit with the booksFacade. The rest will remain unchanged. Cleaning up the imports, the finished code will look like this (again, omitting the decorator since there are no changes):

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import * as BooksPageActions from '../actions/books-page.actions';
import { Book } from '../models/book';
import { BooksFacadeService } from '../services/books-facade.service';

@Component({
  // hidden, no changes
})
export class BooksPageComponent implements OnInit {
  books$: Observable<Book[]>;

  constructor(private booksFacade: BooksFacadeService) {
    this.books$ = booksFacade.allBooks$;
  }

  ngOnInit() {
    this.booksFacade.dispatch(new BooksPageActions.Load());
  }
}

And that’s it! We’re no longer using the store in this component. We should still be able to run ng serve and look at the book list. To double-check that the facade is working, we can set a breakpoint on the dispatch method in the service. It should trigger when the books load.

Working facade Angular demo application

Because this is such a simple example, we didn’t get a ton of benefit to using the facade here. However, it’s easy to imagine how much of a lifesaver this would be if this component was using five selectors at a time and there were seven other components doing the same! You now know everything you need to scale this example up. Try adding more selectors or actions to the application!

Remember, you can access the finished sample code here.

Conclusion

The facade pattern can be extremely helpful when trying to build large Angular applications that use NgRx for state management. At the same time, it’s good to be aware of the pitfalls when using this approach. Increased abstraction can cause increased opacity if you’re not careful and can also lead to some bad habits when it comes to creating and using actions. You can avoid those pitfalls by keeping your team well-versed in the NgRx pattern, teaching new developers the reasons for using facades, and by using a dispatch method in your facade instead of abstracting actions. Good luck and happy coding!

Aside: Authenticate an Angular App and Node API with Auth0

We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with an Angular application and a Node API using Auth0. You can clone this sample app and API from the angular-auth0-aside repo on GitHub.

Auth0 login screen

Features

The sample Angular application and API has the following features:

  • Angular application generated with Angular CLI and served at http://localhost:4200
  • Authentication with auth0.js using a login page
  • Node server protected API route http://localhost:3001/api/dragons returns JSON data for authenticated GET requests
  • Angular app fetches data from API once user is authenticated with Auth0
  • Profile page requires authentication for access using route guards
  • Authentication service uses subjects to provide authentication and profile data to the app

Sign Up for Auth0

You'll need an Auth0 account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 application and API so Auth0 can interface with an Angular app and Node API.

Set Up an Auth0 Application

  1. Go to your Auth0 Dashboard: Applications section and click the "+ Create Application" button.
  2. Name your new app and select "Single Page Web Applications".
  3. In the Settings for your new Auth0 app, add http://localhost:4200/callback to the Allowed Callback URLs.
  4. Add http://localhost:4200 to both the Allowed Web Origins and Allowed Logout URLs. Click the "Save Changes" button.
  5. If you'd like, you can set up some social connections. You can then enable them for your app in the Application options under the Connections tab. The example shown in the screenshot above uses username/password database, Facebook, Google, and Twitter.

Note: Set up your own social keys and do not leave social connections set to use Auth0 dev keys or you will encounter issues with token renewal.

Set Up an API

  1. Go to APIs in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the Identifier to your API endpoint URL. In this example, this is http://localhost:3001/api/. The Signing Algorithm should be RS256.
  2. You can consult the Node.js example under the Quick Start tab in your new API's settings. We'll implement our Node API in this fashion, using Express, express-jwt, and jwks-rsa.

We're now ready to implement Auth0 authentication on both our Angular client and Node backend API.

Dependencies and Setup

The Angular app utilizes the Angular CLI. Make sure you have the CLI installed globally:

$ npm install -g @angular/cli

Once you've cloned the project on GitHub, install the Node dependencies for both the Angular app and the Node server by running the following commands in the root of your project folder:

$ npm install
$ cd server
$ npm install

The Node API is located in the /server folder at the root of our sample application.

Find the config.js.example file and remove the .example extension from the filename. Then open the file:

// server/config.js (formerly config.js.example)
module.exports = {
  CLIENT_DOMAIN: '[YOUR_AUTH0_DOMAIN]', // e.g., 'you.auth0.com'
  AUTH0_AUDIENCE: 'http://localhost:3001/api/'
};

Change the CLIENT_DOMAIN value to your full Auth0 domain and set the AUTH0_AUDIENCE to your audience (in this example, this is http://localhost:3001/api/). The /api/dragons route will be protected with express-jwt and jwks-rsa.

Note: To learn more about RS256 and JSON Web Key Set, read Navigating RS256 and JWKS.

Our API is now protected, so let's make sure that our Angular application can also interface with Auth0. To do this, we'll activate the src/environments/environment.ts.example file by deleting .example from the file extension. Then open the file and change the [YOUR_CLIENT_ID] and [YOUR_AUTH0_DOMAIN] strings to your Auth0 information:

// src/environments/environment.ts (formerly environment.ts.example)
...
export const environment = {
  production: false,
  auth: {
    CLIENT_ID: '[YOUR_CLIENT_ID]',
    CLIENT_DOMAIN: '[YOUR_AUTH0_DOMAIN]', // e.g., 'you.auth0.com'
    ...
  }
};

Our app and API are now set up. They can be served by running ng serve from the root folder and node server from the /server folder. The npm start command will run both at the same time for you by using concurrently.

With the Node API and Angular app running, let's take a look at how authentication is implemented.

Authentication Service

Authentication logic on the front end is handled with an AuthService authentication service: src/app/auth/auth.service.ts file. We'll step through this code below.

// src/app/auth/auth.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, bindNodeCallback, of } from 'rxjs';
import * as auth0 from 'auth0-js';
import { environment } from './../../environments/environment';
import { Router } from '@angular/router';

@Injectable()
export class AuthService {
  // Create Auth0 web auth instance
  // @TODO: Update environment variables and remove .example
  // extension in src/environments/environment.ts.example
  private _Auth0 = new auth0.WebAuth({
    clientID: environment.auth.CLIENT_ID,
    domain: environment.auth.CLIENT_DOMAIN,
    responseType: 'id_token token',
    redirectUri: environment.auth.REDIRECT,
    audience: environment.auth.AUDIENCE,
    scope: 'openid profile email'
  });
  // Track whether or not to renew token
  private _authFlag = 'isLoggedIn';
  // Create stream for token
  token$: Observable<string>;
  // Create stream for user profile data
  userProfile$ = new BehaviorSubject<any>(null);
  // Authentication navigation
  onAuthSuccessUrl = '/';
  onAuthFailureUrl = '/';
  logoutUrl = environment.auth.LOGOUT_URL;
  // Create observable of Auth0 parseHash method to gather auth results
  parseHash$ = bindNodeCallback(this._Auth0.parseHash.bind(this._Auth0));
  // Create observable of Auth0 checkSession method to
  // verify authorization server session and renew tokens
  checkSession$ = bindNodeCallback(this._Auth0.checkSession.bind(this._Auth0));

  constructor(private router: Router) { }

  login() {
    this._Auth0.authorize();
  }

  handleLoginCallback() {
    if (window.location.hash && !this.authenticated) {
      this.parseHash$().subscribe(
        authResult => {
          this._setAuth(authResult);
          window.location.hash = '';
          this.router.navigate([this.onAuthSuccessUrl]);
        },
        err => this._handleError(err)
      )
    }
  }

  private _setAuth(authResult) {
    // Observable of token
    this.token$ = of(authResult.accessToken);
    // Emit value for user data subject
    this.userProfile$.next(authResult.idTokenPayload);
    // Set flag in local storage stating this app is logged in
    localStorage.setItem(this._authFlag, JSON.stringify(true));
  }

  get authenticated(): boolean {
    return JSON.parse(localStorage.getItem(this._authFlag));
  }

  renewAuth() {
    if (this.authenticated) {
      this.checkSession$({}).subscribe(
        authResult => this._setAuth(authResult),
        err => {
          localStorage.removeItem(this._authFlag);
          this.router.navigate([this.onAuthFailureUrl]);
        }
      );
    }
  }

  logout() {
    // Set authentication status flag in local storage to false
    localStorage.setItem(this._authFlag, JSON.stringify(false));
    // This does a refresh and redirects back to homepage
    // Make sure you have the logout URL in your Auth0
    // Dashboard Application settings in Allowed Logout URLs
    this._Auth0.logout({
      returnTo: this.logoutUrl,
      clientID: environment.auth.CLIENT_ID
    });
  }

  private _handleError(err) {
    if (err.error_description) {
      console.error(`Error: ${err.error_description}`);
    } else {
      console.error(`Error: ${JSON.stringify(err)}`);
    }
  }

}

This service uses the auth config variables from environment.ts to instantiate an auth0.js WebAuth instance. Next an _authFlag member is created, which is simply a flag that we can store in local storage. It tells us whether or not to attempt to renew tokens with the Auth0 authorization server (for example, after a full-page refresh or when returning to the app later). All it does is state, "This front-end application thinks this user is authenticated" and then allows us to apply logic based on that estimation and verify whether or not it's accurate.

We'll add and type a token$ observable, which will provide a stream of the access token string. This is for use with the token interceptor. We don't want our interceptor to utilize a stream that emits a default value without any useable values. We'll declare token$ in our _setAuth() method below, when the access token becomes available.

We will use an RxJS BehaviorSubject to provide a stream of the user profile that you can subscribe to anywhere in the app. We'll also store some paths for navigation so the app can easily determine where to send users when authentication succeeds, fails, or the user has logged out.

The next thing that we'll do is create observables of the auth0.js methods parseHash() (which allows us to extract authentication data from the hash upon login) and checkSession() (which allows us to acquire new tokens when a user has an existing session with the authorization server). Using observables with these methods allows us to easily publish authentication events and subscribe to them within our Angular application.

We'll create observables of the callbacks from these two auth0.js methods using using RxJS's bindNodeCallback. In order to preserve the scope of this, we'll bind() it like so:

bindNodeCallback(this._Auth0.parseHash.bind(this._Auth0))

The login() method authorizes the authentication request with Auth0 using the environment config variables. A login page will be shown to the user and they can then authenticate.

Note: If it's the user's first visit to our app and our callback is on localhost, they'll also be presented with a consent screen where they can grant access to our API. A first party client on a non-localhost domain would be highly trusted, so the consent dialog would not be presented in this case. You can modify this by editing your Auth0 Dashboard API Settings. Look for the "Allow Skipping User Consent" toggle.

We'll receive accessToken, expiresIn, and idTokenPayload in the URL hash from Auth0 when returning to our app after authenticating at the login page. The handleLoginCallback() method subscribes to the parseHash$() observable to stream authentication data (_setAuth()) by creating our token$ observable and emitting a value for the userProfile$ behavior subject. This way, any subscribed components in the app are informed that the token and user data has been updated. The _authFlag is also set to true and stored in local storage so if the user returns to the app later, we can check whether to ask the authorization server for a fresh token. Essentially, the flag serves to tell the authorization server, "This app thinks this user is authenticated. If they are, give me their data." We check the status of the flag in local storage with the accessor method authenticated.

Note: The user profile data takes the shape defined by OpenID standard claims.

The renewAuth() method, if the _authFlag is true, subscribes to the checkSession$() observable to ask the authorization server if the user is indeed authorized (we can pass arguments to this observable as we would to the auth0.js function). If they are, fresh authentication data is returned and we'll run the _setAuth() method to update the necessary authentication streams in our app. If the user is not authorized with Auth0, the _authFlag is removed and the user will be redirected to the URL we set as the authentication failure location.

Next, we have a logout() method that sets the _authFlag to false and logs out of the authentication session on Auth0's server. The Auth0 logout() method then redirects back to the location we set as our logoutUrl.

Once AuthService is provided in app.module.ts, its methods and properties can be used anywhere in our app, such as the home component.

Callback Component

The callback component is where the app is redirected after authentication. This component simply shows a loading message until the login process is completed. It executes the authentication service's handleLoginCallback() method to parse the hash and extract authentication information.

// src/app/callback/callback.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth/auth.service';

@Component({
  selector: 'app-callback',
  template: `<div>Loading...</div>`,
  styles: []
})
export class CallbackComponent implements OnInit {
  constructor(private auth: AuthService) { }

  ngOnInit() {
    this.auth.handleLoginCallback();
  }

}

Making Authenticated API Requests

In order to make authenticated HTTP requests, it's necessary to add an Authorization header with the access token to our outgoing requests. Note that the api.service.ts file does not do this.

Instead, this functionality is in an HTTP interceptor service called token.interceptor.ts.

// src/app/auth/token.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

@Injectable()
export class InterceptorService implements HttpInterceptor {
  constructor(private auth: AuthService) { }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    // @NOTE: If you have some endpoints that are public
    // and do not need Authorization header, implement logic
    // here to accommodate that and conditionally let public
    // requests pass through based on your requirements
    return this.auth.token$
      .pipe(
        mergeMap(token => {
          if (token) {
            const tokenReq = req.clone({
              setHeaders: { Authorization: `Bearer ${token}` }
            });
            return next.handle(tokenReq);
          }
        })
      );
  }
}

As mentioned above, we can return the token$ observable to acquire a token, then clone the outgoing HTTP request and attach an Authorization header before sending the request on its way.

The interceptor should be provided like so in the app-routing.module.ts file:

// src/app/app-routing.module.ts
...
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { InterceptorService } from './auth/token.interceptor';
...

@NgModule({
  imports: [...],
  providers: [
    ...,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: InterceptorService,
      multi: true
    }
  ],
  ...
})
export class AppRoutingModule {}

Note: We set multi to true because we could implement multiple interceptors, which would run in the order of declaration.

Final Touches: Route Guard and Profile Page

A profile page component can show an authenticated user's profile information. However, we only want this component to be accessible if the user is logged in.

With an authenticated API request and login/logout implemented in the Home component, the final touch is to protect our profile route from unauthorized access. The auth.guard.ts route guard can check authentication and activate routes conditionally. The guard is implemented on specific routes of our choosing in the app-routing.module.ts file like so:

// src/app/app-routing.module.ts
...
import { AuthGuard } from './auth/auth.guard';
...

@NgModule({
  imports: [
    RouterModule.forRoot([
      ...,
      {
        path: 'profile',
        component: ProfileComponent,
        canActivate: [
          AuthGuard
        ]
      },
      ...
    ])
  ],
  providers: [
    AuthGuard,
    ...
  ],
  ...
})
export class AppRoutingModule {}

To Do: Elegant Error Handling

Now that the primary functionality is there, you'll want to think about gracefully handling and reacting to errors. Some functionality will need to be implemented for this. The errors are there to react to, but you'll want to consider how you prefer to respond to them when they occur.

More Resources

That's it! We have an authenticated Node API and Angular application with login, logout, profile information, and a protected route. To learn more, check out the following resources: