Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Cody Lindley
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Cody Lindley@codylindley )

Experimenting With Auth0 Passwordless Email Authentication In Angular 2.4.1

By Ben Nadel on

This year - hopefully sooner than leter - I'd like to actually build and deploy an offline-first Angular 2 application. For my local data storage system, I've settled on PouchDB for its master-master replication capabilities. And, for user management and authentication, I'm currently exploring Auth0. So far, I like Auth0 because it provides both passwordless authentication and a rich Rules-based authentication workflow that facilitates a serverless architecture. This post is just my exploration of the Auth0 JavaScript client and its use within an Angular 2 application to send and verify one-time-use tokens for email-based, passwordless authentication.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

Unlike regular username and password based authentication protocols, "passwordless authentication" uses one-time tokens that are sent to your email address or your mobile phone number (via SMS). A passwordless authentication workflow means that you don't have to configure and remember yet another password. Instead, you provide a user-specific communications pathway (ex, email) to which a temporary password is sent. You can then use this temporary password to log into the target application.

Auth0 supports more than just passwordless authentication. In fact, the major selling point of Auth0 is that it easily and abstractly supports a wide variety of authentication protocols including SSO (Single Sign-On) and social media based logins (ex, Twitter). But, I chose passwordless authentication because it feels like a very low-friction workflow that neither requires sign-up nor ties a user to any particular social media profile. To me, it seems like the most flexible authentication model.

NOTE: There may be special security considerations with a passwordless authentication workflow. For example, I've heard that SMS communications may not be as secure as other communication options. That said, at this time, I do not have a robust mental model for passwordless authentication that would allow me to make any strong statements about concrete security concerns.

With Auth0, authentication - passwordless or otherwise - is much more than just login functionality. When Auth0 processes a user's credentials, it runs through a Rules engine that is hosted on WebTask.io - a serverless, Function-as-a-Service (FaaS) platform, built by the Auth0 team. As an administrator of my Auth0 account, I can create custom Rules in JavaScript that process and augment a user's profile as part of the authentication workflow. And, since these rules run in a sandboxed Node.js environment, I can do just about anything in these rules, including calling external APIs on behalf of the user.


 
 
 

 
 Auth0 passwordless authentication workflow with Angular 2 and WebTask.io. 
 
 
 

This Auth0 passwordless workflow, the way I understand it (which may have logical holes), goes as followed:

  1. User asks application / Auth0 to send the one-time-use password to the user's communications channel using the user identifier (email in my case).
  2. Auth0 sends the one-time-use password to the communications channel, which is valid for about 5-minutes.
  3. User retrieves the one-time password from the communications channel.
  4. User authenticates against Auth0 using the original user identifier (ex, email address) and the one-time password.
  5. Auth0 authenticates the user and then initiates the asynchronous Rules engine on Webtask.io. Each Rule builds on the results on the previous rule (kind of like a series of Middleware functions). Each Rule can consume the 900+ node modules available in the Webtask.io context. Each Rule can call out to other APIs. Each rule returns the augmented User object or an UnauthorizedError() instance. (5B) Any one of the Rules can make a RESTful call back to the Auth0 server in order to persist app_metadata and user_metadata.
  6. Webtask.io returns the authenticated, augmented User object back to Auth0 (or an UnauthorizedError in the case that authorization should be blocked).
  7. Auth0 returns the authorization result (including a JWT - JSON Web Token) to the client / browser.

NOTE: In the above list, when I use the term "returns", I am using it in a general sense, not in a technical implementation sense.

At this point, the user profile object that was augmented during the authorization process is cached for the current login transaction. The client-side application can then turn around and make a subsequent request back to Auth0 in order to retrieve said user profile. Transient values, calculated during the Rules engine workflow, are only available to the current login transaction. However, the user_metadata and app_metadata collections are shared across all login transactions for the given user.

CAVEAT: If the Rules engine wants to persist changes to the app_metadata or the user_metadata collections, it must make an explicit API request back to the Auth0 server (see step 5B).

ASIDE: The user_metadata should be used to store non-critical information about the user, like preferences and statistics. app_metadata, on the other hand, should be used to store critical "read only" information about how a user can operate within the application. I say "read only" because an authenticated user can make RESTful calls to update their own user_metadata but not their own app_metadata.

To experiment with this workflow in Angular 2, I've opted to use the core JavaScript library provided by Auth0. Auth0 also provides a library called Lock, which implements an entire user interface (UI) on top of the API; but, in the long term, I'll probably have a custom UI, so I think it makes more sense to bypass Lock.

First, I created an Angular 2 service that proxies the Auth0 API so that this service can be injected into other portions of my Angular 2 application. For this simple exploration, I only have three methods:

  • requestEmailCode() - Initiates the passwordless authentication workflow (ie, sends the email).
  • verifyEmailCode() - Authenticates the user against Auth0 using the user's email and the one-time token.
  • getUserInfo() - Gets the Profile object that was calculated during the authorization, Rules-based workflow.

You'll also notice that I have a number TypeScript interfaces at the top of the following service. I tried looking in the Definitely Typed repository; and, there are a number of Auth0 type definition files. But, the core one didn't seem to be up-to-date. Specifically, it didn't have the .getUserInfo() method defined. And, the type defintion files also seemed to have too many "optional" properties. So, I took a stab at creating my own TypeScript Interfaces. If nothing else, I think doing this helped me learn about what data was actually available.

  • // Import the core angular services.
  • import { Injectable } from "@angular/core";
  • import * as Auth0 from "auth0";
  •  
  • // CAUTION: I cobbled together the following interfaces in an attempt to self-document
  • // what the API calls were doing. These are NOT OFFICIAL interfaces provided by Auth0.
  • // I tried to find a "Definitely Typed" set of interfaces; but, they didn't appear to
  • // be up-to-date.
  • // --
  • // Definitely Types for JS - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/auth0-js/index.d.ts
  •  
  • export interface IAuthorization {
  • accessToken: string;
  • idToken: string; // JWT token.
  • idTokenPayload: { // Parsed JWT content.
  • aud: string; // The audience. Either a single case-sensitive string or URI or an array of such values that uniquely identify the intended recipients of this JWT. For an Auth0 issued id_token, this will be the Client ID of your Auth0 Client.
  • exp: number; // Expires at (UTC seconds).
  • iat: number; // Issued at (UTC seconds).
  • iss: string; // The issuer. A case-sensitive string or URI that uniquely identifies the party that issued the JWT. For an Auth0 issued id_token, this will be the URL of your Auth0 tenant.
  • sub: string; // The unique identifier of the user. This is guaranteed to be unique per user and will be in the format (identity provider)|(unique id in the provider), e.g. github|1234567890.
  • };
  • refreshToken?: string; // Optional, if the offline_access scope has been requested.
  • state?: any;
  • }
  •  
  • export interface IIdentity {
  • connection: string;
  • isSocial: boolean;
  • provider: string;
  • user_id: string;
  • }
  •  
  • export interface IMetadata {
  • [ key: string ]: any;
  • }
  •  
  • export interface IProfile {
  • // Fields that are always generated - https://auth0.com/docs/user-profile/normalized
  • identities: IIdentity[];
  • name: string;
  • nickname: string;
  • picture: string; // The profile picture of the user which is returned from the Identity Provider.
  • user_id: string; // The unique identifier of the user. This is guaranteed to be unique per user and will be in the format (identity provider)|(unique id in the provider), e.g. github|1234567890.
  •  
  • // Optional fields, but still "core" ?? !! The documentation is confusing !!
  • app_metadata?: IMetadata;
  • clientID: string; // The unique ID of the Auth0 client.
  • created_at: string; // TZ formatted date string.
  • sub: string; // The unique identifier of the user. This is guaranteed to be unique per user and will be in the format (identity provider)|(unique id in the provider), e.g. github|1234567890.
  • updated_at: string; // TZ formatted date string.
  • user_metadata?: IMetadata;
  •  
  • // Fields that are generated when the details are available:
  • email: string; // The email address of the user which is returned from the Identity Provider.
  • email_verified: boolean;
  • }
  •  
  • export class AuthenticationService {
  •  
  • private auth0: any;
  •  
  •  
  • // I initialize the Authentication service.
  • constructor() {
  •  
  • this.auth0 = new Auth0({
  • domain: "bennadel.auth0.com",
  • clientID: "erNlgZHZ4MyDFrfwFOc0JCAJ1Znzg6Fm", // JavaScript Demos Client.
  • responseType: "token"
  •  
  • // Since I am using an email-based token workflow, I don't need to define
  • // a callback URL - this would only be necessary if I was passing the user
  • // control over to Auth0's website.
  • // --
  • // callbackURL: "{YOUR APP URL}"
  • });
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I get the user info / profile for the given access token (which should have been
  • // returned as part of the authorization workflow).
  • // --
  • // NOTE: Internally, I am using the .getUserInfo() method, which takes the
  • // accessToken. In the Auth0 documentation, however, they discuss the .getProfile()
  • // method that takes the idToken. But, if you try to use that method, you get the
  • // following deprecation warning:
  • // --
  • // DEPRECATION NOTICE: This method will be soon deprecated, use `getUserInfo` instead.
  • // --
  • // Apparently Auth0 is trying to migrate to a slightly different workflow for
  • // accessing the API based on accessTokens. But, it is not yet fully rolled-out.
  • public getUserInfo( accessToken: string ) : Promise<IProfile> {
  •  
  • var promise = new Promise<IProfile>(
  • ( resolve, reject ) : void => {
  •  
  • this.auth0.getUserInfo(
  • accessToken,
  • ( error: any, result: IProfile ) : void => {
  •  
  • error
  • ? reject( error )
  • : resolve( result )
  • ;
  •  
  • }
  • );
  •  
  • }
  • );
  •  
  • return( promise );
  •  
  • }
  •  
  •  
  • // I send a one-time use password to the given email address.
  • public requestEmailCode( email: string ) : Promise<void> {
  •  
  • var promise = new Promise<void>(
  • ( resolve, reject ) : void => {
  •  
  • this.auth0.requestEmailCode(
  • {
  • email: email
  • },
  • ( error: any ) : void => {
  •  
  • error
  • ? reject( error )
  • : resolve()
  • ;
  •  
  • }
  • );
  •  
  • }
  • );
  •  
  • return( promise );
  •  
  • }
  •  
  •  
  • // I log the user into the application by verifying that the given one-time use
  • // password was provisioned for the given email address.
  • public verifyEmailCode( email: string, code: string ) : Promise<IAuthorization> {
  •  
  • var promise = new Promise<IAuthorization>(
  • ( resolve, reject ) : void => {
  •  
  • this.auth0.verifyEmailCode(
  • {
  • email: email,
  • code: code
  • },
  • ( error: any, result: IAuthorization ) : void => {
  •  
  • error
  • ? reject( error )
  • : resolve( result )
  • ;
  •  
  • }
  • );
  •  
  • }
  • );
  •  
  • return( promise );
  •  
  • }
  •  
  • }

Since Auth0 can, internally, authenticate against a large number of sources, it can't provide a universal set of information across all login transactions. As such, it constructs a normalized user Profile that always has a core set of attributes. Then, it adds any additional attributes that may be available from the given identify provider. The Auth0 documentation is pretty good; but, it's kind of spread-out. As such, I found it difficult to nail down which attributes I should or should not expect. You can see this confusion in my attempt to build the IProfile interface above.

Clearly, I have much more to learn about the nuances of Auth0 - I'm just getting my feet wet.

That said, once I had this authentication service, I could inject into my root component where I could experiment with the passwordless workflow. To keep things super simple, I'm just putting two inputs on the screen - one for the user's email address and one of for the one-time-use token that Auth0 will emit. Then, I'm just logging the results to the console as they are available.

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • // Import the application components and services.
  • import { AuthenticationService } from "./authentication.service";
  • import { IAuthorization } from "./authentication.service";
  • import { IProfile } from "./authentication.service";
  •  
  • @Component({
  • moduleId: module.id,
  • selector: "my-app",
  • styleUrls: [ "./app.component.css" ],
  • template:
  • `
  • <strong>Email:</strong>
  • <input type="text" [value]="email" (input)="email = $event.target.value;" />
  • <input type="button" value="Send Email" (click)="sendEmail()" />
  •  
  • <br /><br />
  •  
  • <strong>Code:</strong>
  • <input type="text" [value]="code" (input)="code = $event.target.value;" />
  • <input type="button" value="Verify Code" (click)="verifyCode()" />
  •  
  • <div *ngIf="name">
  •  
  • <h3>
  • Welcome {{ name }}
  • </h3>
  •  
  • <img [src]="avatarUrl" />
  •  
  • </div>
  • `
  • })
  • export class AppComponent {
  •  
  • public avatarUrl: string;
  • public code: string;
  • public email: string;
  • public name: string;
  •  
  • private authenticationService: AuthenticationService;
  •  
  •  
  • // I initialize the component.
  • constructor( authenticationService: AuthenticationService ) {
  •  
  • this.authenticationService = authenticationService;
  •  
  • this.email = "";
  • this.code = "";
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I send the one-time use password to the currently-entered email address. The
  • // one-time password is valid for about 5-minutes.
  • public sendEmail() : void {
  •  
  • this.code = "";
  •  
  • this.authenticationService
  • .requestEmailCode( this.email )
  • .then(
  • () => {
  •  
  • console.log( "Email sent (with one-time use code)." );
  •  
  • }
  • )
  • .catch(
  • ( error: any ) : void => {
  •  
  • console.error( error );
  •  
  • }
  • )
  • ;
  •  
  • }
  •  
  •  
  • // I log the current user into the application using the currently-entered email
  • // address and the one-time use token.
  • public verifyCode() : void {
  •  
  • // In the following workflow, first, we're going to log the user into the app;
  • // then, once the user is authenticated, we'll go back to the Auth0 API to get
  • // the user's full profile (included persisted metadata).
  • this.authenticationService
  • .verifyEmailCode( this.email, this.code )
  • .then(
  • ( authorization: IAuthorization ) : Promise<IProfile> => {
  •  
  • console.group( "Verify Email Code / Authorization Result" );
  • console.log( authorization );
  • console.groupEnd();
  •  
  • // Now that the user is logged-in, let's go back to the API to get
  • // the full user profile.
  • // --
  • // NOTE: There is an earlier API method call .getProfile() which
  • // takes the idToken. That workflow, however, is being deprecated
  • // in favor of a new workflow that emphasizes accessToken.
  • return( this.authenticationService.getUserInfo( authorization.accessToken ) );
  •  
  • }
  • )
  • .then(
  • ( profile: IProfile ) : void => {
  •  
  • console.group( "Profile Result" );
  • console.log( profile );
  • console.groupEnd();
  •  
  • this.name = profile.nickname;
  • this.avatarUrl = profile.picture;
  •  
  • }
  • )
  • .catch(
  • ( error: any ) : void => {
  •  
  • console.warn( "Something went wrong!" );
  • console.error( error );
  •  
  • }
  • )
  • ;
  •  
  • }
  •  
  • }

On its own, this code exercises the authentication workflow. Of course, this exploration wouldn't be nearly as fun if we didn't take a look at the Rules engine as well. As such, I went into my Auth0 dashboard and I created two rules. One that reports the number of logins:

  • function addLoginCount( user, context, callback ) {
  •  
  • // Ensure that the user_meteadata exists.
  • // --
  • // NOTE: It won't exist on the user object until one of the rules explicitly
  • // creates it (or we assign metadata to the user through something like the API).
  • user.user_metadata = ( user.user_metadata || {} );
  •  
  • // NOTE: I believe that stats.loginsCount always exists; but, the documentation only
  • // goes so far as confirming "stats" - it doesn't state that loginsCount is always
  • // available. As such, I am trying to be safe about accessing it.
  • user.user_metadata.loginCount = ( context.stats && context.stats.loginsCount )
  • ? context.stats.loginsCount
  • : 0
  • ;
  •  
  • callback( null, user, context );
  •  
  • }

... and, one that keeps track of the 10 most recent logins:

  • function addLoginAudit( user, context, callback ) {
  •  
  • // Ensure that the user_meteadata exists.
  • // --
  • // NOTE: It won't exist on the user object until one of the rules explicitly
  • // creates it (or we assign metadata to the user through something like the API).
  • user.user_metadata = ( user.user_metadata || {} );
  •  
  • // Ensure that the login audit log exists.
  • user.user_metadata.logins = ( user.user_metadata.logins || [] );
  •  
  • // Track the current login (in descending order).
  • user.user_metadata.logins.unshift({
  • ip: context.request.ip,
  • userAgent: context.request.userAgent,
  • createdAt: Date.now()
  • });
  •  
  • // Limit the audit log to only 10 most recent logins.
  • user.user_metadata.logins = user.user_metadata.logins.slice( 0, 10 );
  •  
  • // At this point, all we've done is updated the in-memory user-metadata associated
  • // with this login request. Now, we have to push this data back over to Auth0 (from
  • // the current webtask.io server), using an API call, so that these changes will be
  • // persisted over to the next login.
  • auth0.users
  • .updateUserMetadata( user.user_id, user.user_metadata )
  • .then(
  • function handleResolve() {
  •  
  • callback( null, user, context );
  •  
  • }
  • )
  • .catch(
  • function handleError( error ) {
  •  
  • callback( error );
  •  
  • }
  • )
  • ;
  •  
  • }

Notice that in the second rule, I have to make an explicit call to auth0.users.updateUserMetadata() in order to persist the user_metadata object back to the Auth0 server. This is because changes to the user object are only cached for the current login transaction (accessible via getProfile() or getUserInfo()) and won't be available to subsequent logins. If we persist the user_metadata back to Auth0, however, we can carry data across login transactions.

In retrospect, my first rule should not have used the user_metadata object to report the login-count. Doing so accidentally persisted the login count as part of the execution of the second rule, which used the same user_metadata as the first rule, but posted it back to Auth0. As rule of thumb (no pun intended), it seems you should only use the user_metadata object for data that you want to be available across login transactions.

Anyway, with these rules in place, if I load the app and then request a one-time-use password, Auth0 sends out the following email:


 
 
 

 
 The email that Auth0 sends out as the initiation of the passwordless authentication workflow. 
 
 
 

Then, if I authenticate against Auth0 using both the original email address and the one-time-use token (above), I get the following output:


 
 
 

 
 Auth0 passwordless authentication output in an Angular 2 application. 
 
 
 

As you can see the, the authentication step returns an idToken, which is the user's JWT (JSON Web Token); and, the idTokenPayload, which is the parsed JWT data. In this case, there is no refreshToken because I did not ask Auth0 to provide one (something that I have not yet tried). As such, the idToken will eventually expire (based on a duration that you can configured in your Auth0 dashboard).

All in all, working with the Auth0 API was painless. The Auth0 JavaScript library did most of the heavy lifting for me; and, the authorization Rules were quite easy to author. I updated them manually as I was learning, but you can also deploy them using GitHub (which I have not tried). There's a lot more to learn about Auth0; but, I'm starting to feel confident in using it for my offline-first application.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.