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