Auth0 Rules and Actions — Custom Authentication Pipeline Logic
In this tutorial, you will learn about Auth0 Rules and Actions. We cover key concepts, practical examples, and best practices to help you master this topic.
Auth0 Rules (legacy) and Actions (modern) let you add custom logic to the authentication pipeline, enabling token enrichment, external API calls, risk assessment, and dynamic authorization decisions.
What You'll Learn
By the end of this lesson you will understand the difference between Rules and Actions, create Actions for token enrichment, call external APIs during login, implement allow/deny decisions, and deploy Actions to production.
Why It Matters
The authentication pipeline is where you can customize Auth0 beyond basic configuration. Actions let you add business logic to every login without separating authentication from authorization.
Real-World Use
DodaZIP uses an Action that enriches the access token with the user's subscription tier and file storage quota. The backend reads these values from the token, avoiding a database lookup on every request.
flowchart LR
Login[User Login] -->|Triggers| A[Auth0 Pipeline]
A -->|Action 1: Enrich| B[Add Claims to Token]
A -->|Action 2: Check| C[External API Call]
A -->|Action 3: Allow/Deny| D[Risk Assessment]
B --> E[Token Issued to App]
C --> E
D -->|Deny| F[Block Login]
style A fill:#eb5424,color:#fff
Rules vs Actions
Understand the Migration from Rules to Actions.
# rules_vs_actions.py
# Rules vs Actions comparison
def compare_rules_actions():
comparison = {
"Runtime": "Node.js 8", "Node.js 18 (Node-Auth0)",
"Trigger": "All login flows", "Appears before/after specific triggers",
"Deployment": "Inline editor", "Versioned, CI/CD compatible",
"Testing": "Manual only", "Built-in test runner",
"Secrets": "Configuration values", "Secrets management UI",
"Performance": "Slower startup", "Optimized cold starts",
"Status": "Deprecated", "Active development",
}
print("Rules vs Actions:")
print(f" {'Feature':25s} | {'Rules (Legacy)':30s} | {'Actions (Modern)'}")
print(" " + "-" * 85)
for feature, rules, actions in comparison.items():
print(f" {feature:25s} | {rules:30s} | {actions}")
compare_rules_actions()
Creating Your First Action
Create an Action that enriches the access token.
// Action: Add custom claims to token
// Trigger: Login / Post Login
exports.onExecutePostLogin = async (event, api) => {
// Get user metadata
const { user, secrets } = event;
// Add custom claims to the access token
api.accessToken.setCustomClaim("subscription_tier", "premium");
api.accessToken.setCustomClaim("storage_limit_mb", 500);
api.accessToken.setCustomClaim("can_upload", true);
// Add claims to ID token
api.idToken.setCustomClaim("department", user.app_metadata?.department);
// Log for debugging
console.log("Enriched token for user:", user.email);
};
# action_creation.py
# Creating and deploying Actions
def action_workflow():
print("Action Workflow:")
print()
print("1. Go to Auth0 Dashboard > Actions > Library")
print("2. Click 'Build Custom'")
print("3. Choose trigger: Login / Post Login")
print("4. Write Action code in TypeScript/JavaScript")
print("5. Test with simulated user")
print("6. Deploy as development version")
print("7. Test in your application")
print("8. Promote to production")
print()
print("Deployment levels:")
print(" Development - Test with specific users")
print(" Staging - Test in staging environment")
print(" Production - Active for all logins")
action_workflow()
Calling External APIs
Use Actions to enrich tokens with data from external services.
// Action: Fetch user data from external API
exports.onExecutePostLogin = async (event, api) => {
const { user, secrets } = event;
// Call external billing API
const response = await fetch(
`https://api.dodatech.com/users/${user.user_id}/subscription`,
{
headers: {
"Authorization": `Bearer ${secrets.BILLING_API_KEY}`,
"Content-Type": "application/json"
}
}
);
if (response.ok) {
const subscription = await response.json();
api.accessToken.setCustomClaim("subscription", subscription.tier);
api.accessToken.setCustomClaim("features", subscription.features);
} else {
// Fallback to default values
api.accessToken.setCustomClaim("subscription", "free");
console.warn("Failed to fetch subscription data");
}
};
# external_api_action.py
# Using external APIs in Actions
def external_api_pattern():
print("External API Integration Pattern:")
print()
print("Action code:")
print(" 1. Receive user event object")
print(" 2. Extract user_id from event.user")
print(" 3. Call external API with secret key")
print(" 4. Handle success: enrich token with response data")
print(" 5. Handle failure: set default values or deny login")
print()
print("Security considerations:")
print(" - Store API keys in Action secrets")
print(" - Keep external API calls fast (< 2 seconds)")
print(" - Handle timeouts and errors gracefully")
print(" - Cache responses if possible")
external_api_pattern()
Denying Access
Use Actions to block login based on custom logic.
// Action: Deny access based on security rules
exports.onExecutePostLogin = async (event, api) => {
// Deny access if user is in a banned country
const blockedCountries = ["XX", "YY"];
if (blockedCountries.includes(event.request.geoip?.countryCode)) {
api.access.deny("Access not available in your region");
return;
}
// Deny access if user account is disabled
if (event.user.app_metadata?.account_disabled) {
api.access.deny("Your account has been disabled");
return;
}
// Deny access if MFA is required but not completed
if (event.user.app_metadata?.require_mfa &&
event.authentication?.methods?.length === 1) {
api.multifactor.enable("any");
}
};
# deny_access.py
# Access denial patterns
def deny_patterns():
patterns = {
"Geographic restriction": "Deny access from blocked countries",
"Account disabled": "Check app_metadata for disabled flag",
"Banned IP address": "Check request IP against blocklist",
"Suspicious activity": "Rate-limit or block frequent logins",
"MFA enforcement": "Require MFA for specific users",
"Domain restriction": "Only allow specific email domains",
}
print("Access Denial Patterns:")
for pattern, desc in patterns.items():
print(f" {pattern:25s} | {desc}")
deny_patterns()
Common Mistakes
Writing blocking code: Actions should be asynchronous and non-blocking. Avoid synchronous HTTP calls or long-running operations.
Not handling errors: External API calls can fail. Always include error handling and fallback logic in your Actions.
Hardcoding secrets: Use Auth0's action secrets for API keys and passwords, never hardcode them.
Testing in production: Always test Actions in development/staging mode before promoting to production.
Ignoring performance: Actions run during login. Keep them fast -- aim for under 1 second total execution time.
Practice Questions
What is the difference between Rules and Actions? Rules are legacy (Node.js 8). Actions are modern (Node.js 18), versioned, and support CI/CD.
What triggers can Actions use? Common triggers: Login / Post Login, Pre User Registration, Post User Registration, Credential Exchange.
How do you add custom claims to a token? Use
api.accessToken.setCustomClaim("key", value)orapi.idToken.setCustomClaim("key", value).How do you deny a login in an Action? Call
api.access.deny("reason message").Challenge: Create an Action that enriches the access token with user attributes from an external database, handles API failures gracefully, and denies access for suspended accounts.
FAQ
Mini Project
Create Actions for the following scenario: after login, enrich the token with user's subscription data from an external API, add a unique session ID claim, and deny access if the account is over its usage limit.
def action_pipeline_plan():
print("Action Pipeline Plan:")
print()
print("1. Enrichment Action (Post Login):")
print(" - Fetch subscription data from billing API")
print(" - Add subscription_tier, storage_limit, can_upload claims")
print(" - Set session_id as UUID claim")
print()
print("2. Authorization Action (Post Login):")
print(" - Check if account is over limit")
print(" - Deny access with 'Account over usage limit'")
print(" - Log the denial event")
print()
print("Secrets needed:")
print(" - BILLING_API_KEY for external API calls")
action_pipeline_plan()
What's Next
Next: Custom Database for migrating existing users.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro