Skip to content

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 created
  • onExecutePostUserRegistration - After a user is created
  • onExecutePreUserUpdate - Before a user is updated
  • onExecutePreUserDeletion - Before a user is deleted (AuthHero-specific)
  • onExecutePostUserDeletion - After a user is deleted (AuthHero-specific)
  • onExecutePostLogin - After successful authentication
  • onExecuteCredentialsExchange - Before tokens are issued
  • onExecuteValidateRegistrationUsername - Validate signup eligibility
  • onFetchUserInfo - When the /userinfo endpoint is accessed (AuthHero-specific)

All hooks can be configured at initialization time using the init() function:

Example:

typescript
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.

typescript
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.

typescript
{
  "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:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
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 user
  • event.tenant_id - The tenant ID
  • event.scopes - The scopes from the access token
  • event.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:

typescript
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:

typescript
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 →

API Reference

Released under the MIT License.