Hooks
Hooks are a powerful extensibility mechanism that allows you to customize the authentication and user lifecycle at various points. AuthHero supports both URL-based webhooks and programmatic hooks defined in code.
Types of Hooks
Programmatic Hooks
Defined directly in your application code during initialization. These provide synchronous, server-side customization capabilities:
onExecutePreUserRegistration- Before a user is createdonExecutePostUserRegistration- After a user is createdonExecutePreUserUpdate- Before a user is updatedonExecutePreUserDeletion- Before a user is deleted (AuthHero-specific)onExecutePostUserDeletion- After a user is deleted (AuthHero-specific)onExecutePostLogin- After successful authenticationonExecuteCredentialsExchange- Before tokens are issuedonExecuteValidateRegistrationUsername- Validate signup eligibilityonFetchUserInfo- When the /userinfo endpoint is accessed (AuthHero-specific)
All hooks can be configured at initialization time using the init() function:
Example:
import { init } from "authhero";
const { app } = init({
dataAdapter: myDataAdapter,
hooks: {
onExecutePreUserRegistration: async (event, api) => {
// Validate user data
if (!event.user.email.endsWith("@company.com")) {
throw new Error("Only company emails allowed");
}
// Enrich user profile
api.user.setUserMetadata("department", "sales");
api.user.setUserMetadata("onboarding_completed", false);
},
onExecutePostLogin: async (event, api) => {
// Log authentication event
await analytics.track("user_login", {
user_id: event.user.user_id,
ip_address: event.request.ip,
});
},
onExecuteValidateRegistrationUsername: async (event, api) => {
// Check if email is allowed to sign up
const domain = event.user.email.split("@")[1];
if (!allowedDomains.includes(domain)) {
api.deny("Email domain not allowed");
}
},
},
});TIP
All hooks are now configured at initialization. The legacy pattern of passing hooks via runtime environment bindings is deprecated and will be removed in a future version.
URL Hooks
Configured through the Management API to call external webhooks at specific trigger points.
POST /api/v2/hooks
{
"name": "Post-Login Analytics",
"triggerId": "post-login",
"url": "https://api.example.com/auth/post-login",
"enabled": true,
"secrets": {
"API_KEY": "secret_key_123"
}
}When the hook triggers, AuthHero sends a POST request to your URL with event data.
Form Hooks
Unique to AuthHero, form hooks render custom forms directly in the authentication flow for progressive profiling or consent gathering.
{
"triggerId": "post-login",
"form": {
"title": "Complete Your Profile",
"fields": [
{
"name": "company",
"label": "Company Name",
"type": "text",
"required": true
},
{
"name": "role",
"label": "Job Role",
"type": "select",
"options": ["Developer", "Designer", "Manager"]
}
]
}
}Hook Context
Hooks receive contextual information about the authentication event:
interface HookContext {
tenant_id: string;
client_id: string;
connection: string;
ip: string;
user_agent: string;
request: {
query: Record<string, string>;
body: Record<string, any>;
};
}Use Cases
User Validation
Validate user data before account creation:
onExecutePreUserRegistration: async (event, api) => {
// Check email domain
const domain = event.user.email.split("@")[1];
if (!isAllowedDomain(domain)) {
throw new Error("Email domain not allowed");
}
// Check for existing account in external system
const exists = await externalSystem.checkUser(event.user.email);
if (exists) {
throw new Error("Account already exists in main system");
}
// Add custom metadata
api.user.setUserMetadata("verified_at", new Date().toISOString());
};You can also use onExecuteValidateRegistrationUsername to reject signups before the user is created:
onExecuteValidateRegistrationUsername: async (event, api) => {
const { email, connection } = event.user;
// Only allow specific domains for certain connections
if (connection === "enterprise-db") {
const domain = email.split("@")[1];
if (!enterpriseDomains.includes(domain)) {
api.deny("This connection is only for enterprise users");
}
}
// Check against a blocklist
if (await isBlocklisted(email)) {
api.deny("Account creation not allowed");
}
};User Enrichment
Add metadata from external systems:
onExecutePostUserRegistration: async (event, api) => {
const user = event.user;
// Fetch additional data from CRM
const crmData = await crm.getUserData(user.email);
// The user is already created at this point, so we update via the data adapter
await event.ctx.env.data.users.update(user.tenant_id, user.user_id, {
app_metadata: {
crm_id: crmData.id,
account_tier: crmData.tier,
},
});
};Custom Claims
Add custom claims to tokens:
onExecuteCredentialsExchange: async (event, api) => {
const user = event.user;
// Add custom claims to access token
api.accessToken.setCustomClaim(
"https://example.com/department",
user.app_metadata.department
);
api.accessToken.setCustomClaim(
"https://example.com/roles",
user.app_metadata.roles
);
// Add custom claims to ID token
api.idToken.setCustomClaim("tier", user.app_metadata.tier);
};Userinfo Customization
Add custom claims to the userinfo endpoint response:
onFetchUserInfo: async (event, api) => {
const user = event.user;
// Add subscription info from external service
const subscription = await getSubscription(user.user_id);
api.setCustomClaim("subscription_tier", subscription.tier);
api.setCustomClaim("subscription_expires_at", subscription.expiresAt);
// Add custom user data
api.setCustomClaim("preferences", user.user_metadata?.preferences);
// Add computed claims
api.setCustomClaim("is_premium", subscription.tier === "premium");
};The onFetchUserInfo hook is called when the /userinfo endpoint is accessed. Use it to:
- Add data from external systems (CRM, subscriptions, etc.)
- Include user metadata in a standardized format
- Add computed or derived claims
- Enrich the response based on the requested scopes
The hook receives:
event.user- The authenticated userevent.tenant_id- The tenant IDevent.scopes- The scopes from the access tokenevent.ctx- The Hono context
The api.setCustomClaim() method adds or overrides claims in the response. Custom claims are merged with the standard userinfo claims (sub, email, email_verified, etc.).
Analytics and Monitoring
Track authentication events:
onExecutePostLogin: async (event, api) => {
await analytics.track("user_login", {
user_id: event.user.user_id,
ip_address: event.request.ip,
user_agent: event.request.user_agent,
connection: event.connection?.name,
});
// Alert on suspicious activity
if (await isSuspicious(event.user, event.request)) {
await security.alert("Suspicious login detected", {
user: event.user,
request: event.request,
});
}
};User Deletion
Handle cleanup before or after user deletion:
onExecutePreUserDeletion: async (event, api) => {
// Check if deletion should be allowed
const user = await event.ctx.env.data.users.get(
event.tenant.id,
event.user_id
);
if (user.app_metadata.protected) {
// Cancel the deletion
api.cancel();
}
// Notify external systems
await externalSystem.prepareUserDeletion(event.user_id);
},
onExecutePostUserDeletion: async (event, api) => {
// Clean up related data in external systems
await externalSystem.deleteUserData(event.user_id);
// Log the deletion
await auditLog.record("user_deleted", {
user_id: event.user_id,
tenant_id: event.tenant.id,
});
};Key Differences from Auth0
- User Deletion Hooks: AuthHero provides both pre and post deletion hooks, which Auth0 doesn't offer natively
- Form Hooks: AuthHero can render custom forms directly in the authentication flow
- Continued Support: While Auth0 deprecated their legacy Hooks in 2024, AuthHero continues to support and expand this functionality
Learn more about Hooks implementation →