Add Auth Service

Now that we have Auth0 set up, let's create an authentication service in our Angular app.

You should have your completed ngGirls workshop todo list application. If you need a finished version of this app and do not have one handy, you can clone it from this ngGirls-todo repo.

Note: If you would like to see the finished repo with Auth0 integration completed, check out the auth0 branch of this ngGirls-todo repo.

From the root of your todo app, create a new service with the following command:

ng g s auth

Note: We won't cover testing in this tutorial so if you'd like to generate components and services without *.spec.ts files, you can add the --no-spec flag to your Angular CLI commands.

Provide AuthService in app.module.ts

The Auth service has now been created but not provided. Let's import it and add it to the providers array in our app's @NgModule like so:

...
import { AuthService } from './auth.service';

@NgModule({
  ...,
  providers: [
    ...,
    AuthService
  ],
  ...

Our AuthService is now available to our entire app.

Authentication Service Code

Now let's open our auth.service.ts file and add the necessary code:

import { Injectable } from '@angular/core';
import * as auth0 from 'auth0-js';

@Injectable()
export class AuthService {
  private AUTH0_DOMAIN = '[AUTH0_DOMAIN]'; // e.g., yourname.auth0.com
  // Create Auth0 WebAuth instance
  private _webAuth = new auth0.WebAuth({
    domain: this.AUTH0_DOMAIN,
    clientID: '[AUTH0_CLIENT_ID]',
    responseType: 'token',
    redirectUri: 'http://localhost:4200',
    audience: `https://${this.AUTH0_DOMAIN}/userinfo`, // This audience grants access to user profile data
    scope: 'openid profile email'
  });
  // Store the user's profile locally once they log in
  userProfile: any;
  // Store access token to authorize an API (future)
  accessToken: string;

  constructor() {
    // You should explore token renewal with checkSession() to restore user login when returning
    // to an app with an unexpired session:
    // https://auth0.com/docs/libraries/auth0js/v9#using-checksession-to-acquire-new-tokens
  }

  login(): void {
    // Send Auth0 authorize request; opens the Auth0 login page
    this._webAuth.authorize();
  }

  handleLoginCallback(): void {
    // When Auth0 hash parsed, execute method to get user's profile and set session
    this._webAuth.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken) {
        window.location.hash = '';
        this.getUserInfo(authResult);
      } else if (err) {
        console.error(`Error: ${err.error}`);
      }
    });
  }

  private getUserInfo(authResult): void {
    // Use access token to retrieve user's profile and set session
    this._webAuth.client.userInfo(authResult.accessToken,
      (err, profile) => {
        const expTime = authResult.expiresIn * 1000 + Date.now();
        // Store auth data
        this.accessToken = authResult.accessToken;
        localStorage.setItem('expires_at', JSON.stringify(expTime));
        this.userProfile = profile;
      }
    );
  }

  logout(): void {
    // Remove tokens, profile, and expiration data
    localStorage.removeItem('expires_at');
    this.accessToken = undefined;
    this.userProfile = undefined;
  }

  get isLoggedIn(): boolean {
    // Check if current date is greater than expiration and an access token and profile are available.
    // This is an accessor, so calling it does not require use of parens; e.g., isLoggedIn
    const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
    return (Date.now() < expiresAt) && this.accessToken && this.userProfile;
  }

}

Make sure you replace [AUTH0_DOMAIN] and [AUTH0_CLIENT_ID] with your Auth0 information from the previous step. The Auth service follows the implicit grant flow for authentication with tokens in Single Page Applications. You can read the comments in the code above to learn more about what each method does.

Note: Our todo app currently uses localStorage to perform CRUD operations, so we will not use the retrieved access token to authorize and call an API. However, if we ever implement an API (such as with Node.js), it will be very important that we use the access token to call the API and verify the user's token before providing them access to a resource server.

Handle Authentication in App Component

Because the app.component.ts is our app's root component, this is where we should execute the Auth service's handleLoginCallback() method to check for a login hash and parse it if one is found.

Open the App component file and add the following:

import { AuthService } from './auth.service';
...

export class AppComponent {
  constructor(private authService: AuthService) {
    this.authService.handleLoginCallback();
  }
}

Now the handleLoginCallback() method is called any time the app is loaded, i.e., when a user arrives at the application. We want to do this because when a user logs in, they are sent away from the app and then redirected back to it with their access JWTs in the browser URI's hash.

If the app loads with no hash, nothing happens. If the app loads with a hash indicating a login has occurred, then we can parse the hash and set the user's authentication state appropriately as soon as our app initializes again after the redirect. This will be more clear once we implement the Auth component so that you can test the login yourself.

results matching ""

    No results matching ""