Auth0 Actions are serverless functions that run at specific points in the authentication pipeline — on login, after registration, before token issuance, and more. They replace the older Rules and Hooks systems and are the correct way to customise Auth0's behaviour in 2026.
What Actions Can Do
- Enrich the access token with custom claims (user roles, organisation data, feature flags)
- Block logins based on IP address, device fingerprint, or risk score
- Send a welcome email after a user registers for the first time
- Sync user data to your database on login
- Enforce multi-factor authentication for specific user groups
Creating an Action
- Go to Auth0 Dashboard > Actions > Library > Build Custom
- Choose a Trigger (e.g. Login / Post Login)
- Write your Action code in the editor
- Test the Action with a simulated event
- Deploy the Action
- Add it to a Flow: Actions > Flows > Login, drag your Action into the flow
Recipe: Add Custom Claims to the Access Token
The most common Action adds user roles or subscription data to the JWT so your API can authorise requests without a database lookup.
// Action: Add user roles to access token
// Trigger: Login / Post Login
exports.onExecutePostLogin = async (event, api) => {
// event.user contains the user's Auth0 profile
const namespace = 'https://myapp.com';
// Add roles from Auth0 user metadata
const roles = event.authorization?.roles ?? [];
api.accessToken.setCustomClaim(`${namespace}/roles`, roles);
// Add subscription tier from app_metadata
const tier = event.user.app_metadata?.subscription_tier ?? 'free';
api.accessToken.setCustomClaim(`${namespace}/tier`, tier);
};Always namespace your custom claims with a URL you control (e.g. https://yourdomain.com/claim-name). Auth0 rejects reserved claim names (sub, iss, aud, etc.) without a namespace.Recipe: Block Logins by Domain
// Action: Only allow corporate email addresses
exports.onExecutePostLogin = async (event, api) => {
const allowedDomains = ['yourcompany.com', 'partnercompany.com'];
const email = event.user.email ?? '';
const domain = email.split('@')[1];
if (!allowedDomains.includes(domain)) {
api.access.deny(`Access restricted to corporate email addresses.`);
}
};Recipe: Sync User to Your Database on Login
// Action: Upsert user in your database on every login
const { Client } = require('pg');
exports.onExecutePostLogin = async (event, api) => {
// Only sync on new users or first login
if (event.stats.logins_count > 1) return;
const client = new Client({ connectionString: event.secrets.DATABASE_URL });
await client.connect();
await client.query(
`INSERT INTO users (auth0_id, email, name)
VALUES ($1, $2, $3)
ON CONFLICT (auth0_id) DO UPDATE SET email = $2, name = $3`,
[event.user.user_id, event.user.email, event.user.name]
);
await client.end();
};Store database credentials as Secrets in the Action editor, not hardcoded in the code. Secrets are encrypted and accessible via event.secrets.SECRET_NAME.Debugging Actions
Use the Real-time Webtask Logs feature in the Auth0 dashboard to see console.log output from your Actions during testing. In production, Auth0 streams logs to the Auth0 Log Stream — configure this to send to Datadog, Splunk, or a webhook for alerting.
| Metadata | Value |
|---|---|
| Title | Auth0 Actions: Customising the Login Flow with Serverless Logic |
| Tool | Auth0 |
| Primary SEO keyword | auth0 actions login flow |
| Secondary keywords | auth0 actions tutorial, auth0 custom claims, auth0 block login, auth0 post login action |
| Estimated read time | 8 minutes |
| Research date | 2026-04-14 |