Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Zachary Brooks
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Zachary Brooks

Posting Additional Parameters During Authentication With Auth0 In Angular 2.4.1

By
Published in Comments (1)

The other day, I looked at how to provision new Cloudant / CouchDB databases as part of the Auth0 authentication and authorization process. But, after that post, I felt uneasy about trying to keep these two systems - Auth0 and Cloudant - in sync; and I wondered if there was a way I could conditionally change the behavior of the Auth0 Rules engine based on the current login transaction (such as forcing new Cloudant API Keys to be generated). Luckily, we can pass additional parameters through as part of the authentication request. And, these parameters are then made available to the logic of our Auth0 Rules engine on WebTask.io.

Run this demo in my JavaScript Demos project on GitHub.

When authenticating with a passwordless login, you can invoke the .verifyEmailCode() method in the Auth0 JavaScript client:

auth0.verifyEmailCode(
	{
		email: email,
		code: code
	},
	function() { /* ... callback ... */ }
);

Now, this doesn't appear to be documented anywhere - at least, nowhere that I could find; but, after experimenting with the authentication process, I discovered that you could actually pass additional, arbitrary key-value pairs in along side the "email" and "code" properties:

auth0.verifyEmailCode(
	{
		email: email,
		code: code,

		// Additional, arbitrary parameters.
		foo: "bar",
		hello: "world",
		things: [ "a", "b", "c" ],
		stuff: { a: 1 }
	},
	function() { /* ... callback ... */ }
);

These additional parameters are then made available in the Auth0 Rules engine as part of the context.request.body collection (alongside several other internal Auth0 properties). These parameters can contain any kind of basic JavaScript data type, be it Strings, Numbers, Booleans, Arrays, or Objects; but, be aware - all "simple" values are reported as Strings. So, for example:

  • ( "hello" ) comes through as ( "hello" ).
  • ( true ) comes through as ( "true" ).
  • ( 3 ) comes through as ( "3" ).
  • ( null ) comes through as ( "" ).
  • ( undefined ) comes through as ( "" ).

I don't know why this is - like I said, I couldn't find any documentation about this feature. But, as long as you're careful with your tripple-equals comparator, everything else should work as expected.

NOTE: If you look at the screenshot of the demo below, I believe all values are reported as "strings" because they are actually posted as individual form fields using a special name notation. Meaning, these values aren't just serialized as JSON (JavaScript Object Notation).

To build on top of my previous Cloudant exploration, I wanted to set up a demo in which I could pass-in an additional authentication value that would flush the app_metadata cache and force a Cloudant database access key to be regenerated. Of course, I don't want to include the entire Cloudant portion of this workflow; so, as a proof-of-concept, I just created an Auth0 Rule that looks for a "params" object in the context.request.body collection; and, injects a message into the User Profile that indicates whether or not a particular authentication flag was received.

function addRequestBodyAnalysis( user, context, callback ) {

	// Since every Rule runs during the Auth0 authentication / authorization workflow,
	// there's no implicit way to skip a Rule. But, each login transaction reports the
	// Client ID making the request. As such, we can explicitly bail out of any Rule
	// that is meant to be associated with a different Client ID.
	if ( context.clientID !== "K3zOzhAsrok9mx6yrrwDcbMggFC9QxjZ" ) {

		return( callback( null, user, context ) );

	}

	// If you pass additional key-value pairs in during authentication, the non-core
	// keys (ie, not the keys like "email", "code", "passcode", etc), are made available
	// in the Rules engine as part of the Request Body collection.
	var params = ( context.request.body.params || {} );

	// CAUTION: For some reason, all of the "simple values" in the request.context.body
	// come through as Strings. Arrays are still arrays and Objects are still objects;
	// but, all of the non-complex values come through as strings. So, for example:
	// --
	// - ( true ) comes through as ( "true" ).
	// - ( 3 ) comes through as ( "3" ).
	// - ( null ) comes through as ( "" ).
	// - ( undefined ) comes through as ( "" ).
	// --
	// So, just be careful about how you use your triple-equals comparison!
	if ( params.reconnectDatabase === "true" ) {

		user.bodyAnalysis = "Your database has been reconnected!";

	} else {

		user.bodyAnalysis = "No additional params were provided.";

	}

	// Move on to next Rule.
	callback( null, user, context );

}

I decided to wrap my custom authentication data in a "params" object, rather than having it live directly in the "body" collection since the "body" collection already contains a bunch of Auth0 data. And, by isolating my custom parameters, I believe it will cut down on my chance of accidentally colliding with core Auth0 post properties.

Now, to actually populate this "params" object, I had to update the .verifyEmailCode() method in my Angular 2 authentication service - my injectable Auth0 wrapper - to accept an optional "params" argument. This "params" object is then appended to the post data of the internal Auth0 request:

// Import the core angular services.
import * as Auth0 from "auth0";

// Import the application components and services.
import { IAppMetadata } from "./authentication.interfaces";
import { IAuthorization } from "./authentication.interfaces";
import { IIdentity } from "./authentication.interfaces";
import { IProfile } from "./authentication.interfaces";
import { IUserMetadata } from "./authentication.interfaces";

export interface IParams {
	[ key: string ]: any;
}

export class AuthenticationService {

	private auth0: any;


	// I initialize the Authentication service.
	constructor() {

		this.auth0 = new Auth0({
			domain: "bennadel.auth0.com",
			clientID: "K3zOzhAsrok9mx6yrrwDcbMggFC9QxjZ",
			responseType: "token"
		});

	}


	// ---
	// PUBLIC METHODS.
	// ---


	// I get the user info / profile for the given, authenticated access token.
	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. Additionally, an optional
	// params object can be used to make additional data available in the Rules engine
	// (as the request.body.params object).
	public verifyEmailCode(
		email: string,
		code: string,
		params?: IParams
		) : Promise<IAuthorization> {

		var promise = new Promise<IAuthorization>(
			( resolve, reject ) : void => {

				this.auth0.verifyEmailCode(
					{
						email: email,
						code: code,
						params: params
					},
					( error: any, result: IAuthorization ) : void => {

						error
							? reject( error )
							: resolve( result )
						;

					}
				);

			}
		);

		return( promise );

	}

}

Then, in the root component of my Angular 2 application, I populate this "params" object based on a checkbox. The checkbox drives the value of the "reconnectDatabase" flag, which is the property that my new Auth0 Rule is inspecting:

// 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.interfaces";
import { IProfile } from "./authentication.interfaces";

@Component({
	moduleId: module.id,
	selector: "my-app",
	styleUrls: [ "./app.component.css" ],
	template:
	`
		<strong>Email:</strong>
		<input type="text" [(ngModel)]="email" />
		<input type="button" value="Send Email" (click)="sendEmail()" />

		<br /><br />

		<strong>Code:</strong>
		<input type="text" [(ngModel)]="code" />
		<input type="button" value="Verify Code" (click)="verifyCode()" />

		<br /><br />

		<label>
			<input type="checkbox" [(ngModel)]="reconnect" [value]="true" />
			Reconnect database?
		</label>

		<p *ngIf="bodyAnalysis">
			<strong>Body Analysis:</strong> {{ bodyAnalysis }}
		</p>
	`
})
export class AppComponent {

	public bodyAnalysis: string;
	public code: string;
	public email: string;
	public reconnect: boolean;

	private authencationService: AuthenticationService;


	// I initialize the component.
	constructor( authencationService: AuthenticationService ) {

		this.authencationService = authencationService;

		this.bodyAnalysis = "";
		this.code = "";
		this.email = "";
		this.reconnect = false;

	}


	// ---
	// 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.bodyAnalysis = "";

		this.authencationService.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 {

		// Setup the additional params that we want to POST through with the
		// authorization. This object will be made available in the Rules engine
		// as a key on the context.request.body object.
		var params = {
			reconnectDatabase: this.reconnect
		};

		// 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 which includes the "Message" property which is being
		// injected by each of the Auth0 Rules in this demo.
		this.authencationService.verifyEmailCode( this.email, this.code, params )
			.then(
				( authorization: IAuthorization ) : Promise<IProfile> => {

					console.group( "Verify Email Code / Authorization Result" );
					console.log( authorization );
					console.groupEnd();

					// Now that the user is logged-in, go back and get the Profile.
					return( this.authencationService.getUserInfo( authorization.accessToken ) );

				}
			)
			.then(
				( profile: IProfile ) : void => {

					console.group( "Profile Result" );
					console.log( profile );
					console.groupEnd();

					// Pull out the message that was injected by the Rules as part of the
					// inspection of the context.request.body POST collection.
					this.bodyAnalysis = profile.bodyAnalysis;

				}
			)
			.catch(
				( error: any ) : void => {

					console.warn( "Something went wrong!" );
					console.error( error );

				}
			)
		;

	}

}

The value of the "reconnectDatabase" in the Rules engine determines which "bodyAnalysis" message is injected into the User Profile for the current login transaction. And, if we run the app, toggle the checkbox, and invoke the passwordless authentication, we can see the outgoing "params" POST and the incoming message provided by the Auth0 Rule:

Passing additional POST parameters as part of the authentication workflow with Auth0.

As you can see, the "reconnectDatabase" flag went out as part of "params" object, which the AuthenticationService was appending to the authentication POST data. This "params" object was then inspected by our Auth0 Rule, and, as a result, the message, "Your database has been reconnected!", was dynamically injected into the User Profile.

This proof-of-concept demonstrates that we can provide additional authentication parameters as a means to drive dynamic behavior in our Auth0 Rules engine. Of course, I couldn't find any documentation about providing arbitrary data in the authentication methods of the Auth0 client-side library; so, use this approach with caution as the underlying behavior may change in the future.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Thanks Ben, excellent post. I was too sending custom params when doing authentication with an identity provider, but then I migrated to auth0-js v8 and everything broke. Then I discovered that I can no longer send objects only primitive values. They're found in `context.request.query` btw.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel