Secure User Accounts With Auth0 Actions And Custom MFA

Published on January 21, 2025
Learning GoalDiscover how to effectively use Auth0 Actions to enhance ID tokens and configure custom MFA for user accounts with specific roles.

"At Identiflix, we are committed to enhancing security by requiring instructors to use additional authentication factors, such as OTPs and security keys, instead of passwords. This approach will better safeguard user accounts, especially for instructors who need to protect restricted course content. To achieve this, we plan to implement Multi-Factor Authentication (MFA), which adds an extra layer of security. While standard MFA solutions offer solid protection, custom MFA workflows will allow us to tailor the authentication process to the specific needs of our instructors. We are excited to explore this approach!"

In this guide, we’ll walk you through setting up custom MFA workflows using Auth0 Actions, enabling a seamless and secure authentication experience for your instructors. Here’s what you’ll learn:

  • How to set up an Auth0 Tenant for a custom MFA
  • How to create and manage Auth0 Actions
  • How to enhance your ID Token with custom claims
  • How to use Actions to require WebAuthn for instructor users

Let’s dive in and get started! 🍿

Prerequisites

Welcome back, dear traveler! 👋 This lab is part of Dev_day, where we’ll explore all the exciting possibilities with Auth0.

Before diving in, make sure you’ve completed the Identiflix Setup lab. If you’re starting from scratch or haven’t finished yet, follow these initial steps to get set up with Auth0:

To work with Auth0 in your application, you’ll need to have users log in or test features. Let’s set up an Auth0 user together using the dashboard. Navigate to User Management > Users, where you’ll find a button to create a new user. Click it, and you’ll be directed to the user creation view:

image

Just fill out the required input fields; you can leave the connection settings as they are. And with that, we’re ready to officially dive into our Labs!

Step 1: The Instructor Role

Let’s jump into building custom MFA! 🔥 Before diving into the code, we need to lay some important groundwork.

Start by assigning an instructor role to the user who will act as your test instructor. To do this, go to User Management > Roles and click Create Role. This will open a modal window where you can set up the new role.

image

Name your first role "Instructor," or choose any descriptive name that fits your needs. Be sure to provide a clear description of the role's purpose. Creating the role alone isn’t enough; you also need to assign it to a user. To do this, go to User Management > Users, select the user who will be your first instructor, then navigate to the "Roles" tab and click "Assign Roles."

image

In this modal window, you can select and assign the newly created Instructor role. Simply choose the role and click "Assign" to get ready to start coding!

Step 2: Choosing the Login Flow

To get started, let’s create a new Auth0 Action: Actions are serverless functions that operate within a secure Auth0 environment. First, go to the left sidebar menu and select Actions, then choose Flows. This page displays the various flows you can customize. For our purposes, select the Login flow.

image

This will take you to the “Login” trigger page, where you can install, create, and add Actions to your flow.

image

The screen is divided into two main sections: the diagram or flow builder in the center and the Actions panel.

In the flow builder, you’ll see the default login flow, which includes two key states: Start: The user has completed the login form and is ready to proceed. Complete: The system issues the user’s credentials, including an ID token and, if needed, an access token.

You can drag and drop Actions from the Side menu between these states to add custom logic to the login flow or to deny the login request before the tokens are issued.

Step 3: Create Your First Action

On the right panel, you’ll see the Actions panel with two tabs: Installed: This tab shows pre-installed Actions from the Auth0 Actions Marketplace. Custom: Here, you’ll find the Actions that you’ve created for this tenant.

To create a new Action, click the plus icon on the Actions panel and select "Build from scratch." Next, a modal window will pop up where you’ll enter the details for your Action.

image

You’ll need to provide a name, and you can leave the trigger and runtime settings at their default values. For this Action, you might name it “Set Role in ID Token.”

image

After you click "Create," you’ll be directed to the Action’s code editor. Here, you can write your Action’s code, install packages, test your setup, and more. In this tutorial, we’ll focus on the onExecutePostLogin method, which determines what happens right after Auth0 verifies the user's login credentials.

Step 4: First Action - ID Token Enriched

To ensure our instructors are properly recognized, we'll use the ID Token. ID tokens are essential in token-based authentication as they cache user profile information and provide it to client applications, enhancing performance and user experience. After a user successfully authenticates, the application receives an ID token, which it then uses to extract user information and personalize the user’s experience. In short, ID tokens are ideal for storing user roles and responding to them.

To prevent name collisions, we recommend using namespaced claims. While collisions won't cause transactions to fail, your custom claim might not be added to the tokens.

Let’s begin by adding a secret variable to store our namespace. Open the secrets section by clicking the key icon in the sidebar of Auth0’s code editor. This action will bring up the modal window for adding secrets.

image

People commonly use their domain name as a namespace when naming custom claims. This method helps prevent collisions and makes it easier to find custom claims in an ID token. In this step, you’ll use this secret variable directly in your code to add extra information to the ID token.

Let’s walk through the code you’ll write inside the onExecutePostLogin method:

action.js
exports.onExecutePostLogin = async (event, api) => {
// Set namespace name
let namespace = event.secrets.NAMESPACE || "";
try {
// Check if there are any roles available
if (event.authorization) {
// Add the roles as custom claims inside the ID token
api.idToken.setCustomClaim(
`${namespace}/roles`,
event.authorization.roles,
);
}
} catch (e) {
// Print any possible errors
console.log(e);
}
};

You start by retrieving the secret variable NAMESPACE from the environment and storing it in the namespace variable. This ensures that your custom claims are namespaced, reducing the risk of collisions with other claims.

Next, the code checks if the authorization object is available in the event data, which contains user roles. If roles are present, you use api.idToken.setCustomClaim to add a custom claim named roles (in addition to the namespace defined before) to the ID token, setting its value to the user’s roles. This enriches the ID token with useful information for your application.

Once you’ve added this code, you’ve successfully created your first action and enriched the ID token! 🎉

Step 5: Attach your Auth0 Action to the Login Flow

After coding and testing your Actions, it's time to deploy them and integrate them into your login flow. When you’re happy with your Auth0 action, click the Deploy button in the top right corner of the screen. This action will create a function snapshot from your current code and settings, making it ready to use in your login process.

image

You can save your Action as a draft if you want to keep it without deploying it right away. Any changes you make will be automatically saved as a new draft. To make your changes effective, you’ll need to deploy the latest version.

However, deploying your code doesn’t automatically add it to your login flow. To verify its inclusion, check the login flow screen where the diagram is displayed. If you don’t see your Action in the diagram, it hasn’t been activated yet, even if it’s deployed. To activate it, go to the “Custom” tab in the Actions panel, find your Action by name, and drag and drop it into the login diagram between the "Start" and "Complete" steps. Finally, click the “Apply” button at the top right corner of the screen to save your changes.

Step 6: Let’s Make the Application Recognize Our Instructor Role

Just adding the instructor's role isn't enough. You also need to make sure the application can recognize and respond to the user’s role properly. Inside the file app/instructor-dashboard/page.tsx the demo app, you can find a comment in line 19 on where to deny access:

app/instructor-dashboard/page.tsx
...
export default async function MyCoursesPage() {
const session = await getSession();
if (!session) return notFound();
//FIXME: complete the lab "Securing Instructor Accounts with Auth0 Actions and Custom MFA" to get the user's roles as part of the user claims and validate
// if the user is an
const courses = await DataAPI.getCourses();
...

Here’s a code snippet to help with that:

aapp/instructor-dashboard/page.ts
if (
!session ||
session.user["http://localhost/roles"]?.includes("Instructor") === false
)
return notFound();

This code ensures that if the user is not an Instructor, the application will display a "not found" page:

image

You might wonder why we use “NotFound” and nothing else. It might seem like a shortcut, but it’s intentional. We want to keep the instructor’s dashboard hidden from regular users. There’s no need for an average user to know it even exists. So, if a regular user tries to access this page, it will appear the same as if they weren’t logged in at all. Only users with the Instructor role will be able to access the dashboard.

image

Since any user who can access this page is guaranteed to be an Instructor, you can safely remove the Alert component on line 28.

Step 7: Prepare Your Tenant for MFA

Before you customize your MFA flows, let’s make sure everything is set up properly. Follow these steps to get started: Activate MFA: Head to your Auth0 Dashboard (sign up for a free account if you don’t have one yet) and go to Security > Multi-Factor Auth. Here, you can enable MFA for your tenant. Choose Your Factors: MFA offers several options. Decide which factors best meet your security needs and enable them on the same Multi-Factor Authentication page. You might select options like push notifications, SMS codes, or biometrics.

image

Step 8: Enabling Configuration of MFA Factors

To allow customization, toggle the "Customize MFA Factors using Actions" setting. This will enable your users to have personalized MFA experiences. To make this change, navigate to the Auth0 Dashboard and Security > Multi-Factor Auth > Additional Settings.

image
Actions using the enrollWith or enrollWithAny commands will override any existing policies or rules that control MFA settings in your tenant.

Step 9: Webauthn for Instructors as second Auth0 Action

In this step, you’ll write an Action that requires users in the Admin group to use WebAuthn as an additional authentication step. If a user isn’t already enrolled with WebAuthn, they’ll be prompted to enroll the first time they log in.

This Action will use the challengeWith and enrollWith APIs, along with the enrolledFactors property, to check if the user already has an MFA factor set up. You can create this action exactly as you did before. Just refer back to the paragraph to catch up if you need.

Here’s the code you’ll need to implement WebAuthn for Admin users:

action.js
exports.onExecutePostLogin = async (event, api) => {
// Check if the user is an Instructor, and enforce WebAuthn if so
if (event.authorization?.roles?.includes("Instructor")) {
// Verify if the user is already enrolled with WebAuthn
if (
event.user.enrolledFactors &&
event.user.enrolledFactors.some((m) => m.type === "webauthn-roaming")
) {
// Challenge the user to authenticate with WebAuthn
api.authentication.challengeWith({ type: "webauthn-roaming" });
} else {
// Prompt the user to enroll in WebAuthn
api.authentication.enrollWith({ type: "webauthn-roaming" });
}
}
};

This code ensures that Instructor users are required to use WebAuthn for additional security. It starts by checking if the user is an Instructor. If they are, the code then checks if the user is already enrolled in WebAuthn. If the user is enrolled, they are challenged to authenticate using WebAuthn. If not, they are prompted to enroll in WebAuthn during their login. This approach helps maintain a secure environment by enforcing WebAuthn for users with higher access privileges, as desired by Identiflix’s admins.

Step 10: Conclusion

“At Identiflix, we have improved security by implementing additional authentication measures for our instructors, such as OTPs and security keys. While standard MFA solutions provide good protection, our customized MFA workflows have allowed us to tailor the authentication process to fit the specific needs of our instructors. We are proud of this achievement, as it has significantly enhanced our security measures while providing a seamless user experience for our instructors.”

In this lab, you learned how to set up and customize Multi-Factor Authentication (MFA) using Auth0 Actions. This way, you discover how to enhance security for specific user roles, such as instructors at Identiflix. In detail, you’ve learned:

  • Setting up an Auth0 Tenant for custom MFA
  • Creating and managing Auth0 Actions
  • Enhancing ID Tokens with Custom Claims
  • Using Actions to require WebAuthn for instructor users

As you explore the possibilities of Auth0 Actions, remember that customization is key. By experimenting with different workflows and conditions, you can fine-tune your MFA implementation to perfectly align with your organization's unique security needs and user expectations.

To learn more about Auth0 Actions and extensibility check out the following sessions: