User Creation Flow and Hooks
This document describes the complete user creation flow in AuthHero and all hooks that are triggered during the process.
Overview
When a new user signs up or is created in AuthHero, whether through email/password, passwordless, social login, or the Management API, a series of validation checks and hooks are executed to ensure proper authorization and data integrity.
User Creation Methods
Users can be created through several methods:
- Email/Password Signup - Via
/dbconnections/signupendpoint - Passwordless (Email/SMS) - Via
/passwordless/startendpoint followed by verification - Social Login - Via OAuth connections (Google, Facebook, etc.)
- Management API - Direct user creation via
/api/v2/usersendpoint
Signup Validation Flow
1. Early Validation (Optional)
Before a user even attempts to sign up, you can validate their eligibility using the validateRegistrationUsername function. This is useful for:
- Identifier pages that check if signup is allowed before showing the signup form
- Providing early feedback to users
- Avoiding unnecessary user interactions when signup is disabled
Function: validateRegistrationUsername(ctx, client, data, email)
Returns: { allowed: boolean, reason?: string }
Checks:
- Whether
disable_sign_upsis set to"true"in client metadata - If
screen_hint=signupis present in authorization URL (overrides the disable setting) - If another user with the same email exists (allows linking)
2. Pre-Registration Hook (Right Before User Creation)
When user creation is attempted through any signup flow (email/password, social, passwordless email), the preUserRegistrationHook is executed.
Function: preUserRegistrationHook(ctx, client, data, email)
Executed for:
- ✅ Email/password signups
- ✅ Passwordless email signups
- ✅ Social login signups (Google, Facebook, etc.)
- ❌ Passwordless SMS signups (phone-based, no email validation needed)
- ❌ Management API user creation (no client_id context)
Note: SMS/phone-based signups are not subject to email-based signup validation since they don't have an email address. The disable_sign_ups client metadata only applies to email-based authentication methods.
Actions:
- Re-validates signup eligibility using
validateRegistrationUsername - Logs failed signup attempts (type:
fs) - Invokes pre-registration webhooks if configured
- Throws
HTTPException 400if signup should be blocked
Bypass Conditions:
screen_hint=signupis present in the authorization URL- A user with the same verified email already exists (account linking)
3. User Creation Hook
Once the pre-registration hook passes, the actual user creation begins through data.users.create. This triggers additional hooks through createUserHooks.
Actions:
- Validates client_id exists in context (auth flows only)
- Fetches client configuration
- Executes
preUserRegistrationHook(for auth flows with client_id) - Invokes
onExecutePreUserRegistration(programmatic hook) - Performs account linking if applicable via
linkUsersHook - Invokes
onExecutePostUserRegistration(programmatic hook) - Invokes post-user-registration webhooks
Complete Hook Execution Order
When a new user signs up through an authentication flow:
1. validateRegistrationUsername (optional, early check)
↓
2. preUserRegistrationHook
├── validateRegistrationUsername (re-validation)
├── Log failed signup (if blocked)
└── preUserRegistrationWebhook
↓
3. data.users.create (wrapped with hooks)
├── Validate client_id and fetch client
├── preUserRegistrationHook (redundant check, already done)
├── onExecutePreUserRegistration (programmatic)
├── linkUsersHook (automatic account linking)
├── onExecutePostUserRegistration (programmatic)
└── postUserRegistrationWebhookSignup Blocking with disable_sign_ups
Configuration
Set the disable_sign_ups client metadata to "true" to block public signups:
{
"client_metadata": {
"disable_sign_ups": "true"
}
}Behavior
When disable_sign_ups is enabled:
❌ Blocked:
- New signups via email/password
- New signups via passwordless email
- New signups via social logins (Google, Facebook, etc.)
✅ Allowed:
- Signups via SMS/phone (not email-based, so not restricted)
- Signups when
screen_hint=signupis in the authorization URL (e.g., for invited users) - Signups when a user with the same verified email already exists (account linking)
- User creation via Management API (admin operations bypass signup restrictions)
- Login attempts for existing users
Important: The disable_sign_ups setting only applies to email-based authentication methods. SMS/phone-based signups are not restricted because they don't use email addresses and follow a different validation flow.
Use Cases
Invite-Only Applications
- Block public signups but allow invited users via
screen_hint=signup
- Block public signups but allow invited users via
Account Linking
- User signs up with email/password
- Later tries to sign in with Google using the same email
- Google account is automatically linked to existing account even with
disable_sign_ups: true
Gradual Rollout
- Start with closed beta (signups disabled)
- Send invitation links with
screen_hint=signup - Open to public later by removing the flag
Hook Types
URL/Form Hooks (Management API)
Configured via the Management API and support the following triggers:
pre-user-registration- Before user creationpost-user-registration- After successful creationpost-user-login- After successful loginvalidate-registration-username- Validate registration eligibilitypre-user-deletion- Before user deletionpost-user-deletion- After user deletion
Programmatic Hooks (Application Config)
Defined in your application code when initializing AuthHero:
const hooks = {
onExecutePreUserRegistration: async (event, api) => {
// Modify user metadata before creation
api.user.setUserMetadata("source", "web");
},
onExecutePostUserRegistration: async (event, api) => {
// Perform actions after user is created
await sendWelcomeEmail(event.user.email);
},
};Management API User Creation
When creating users through the Management API (POST /api/v2/users):
Differences:
- No
client_idin context - Pre-signup hooks are skipped (no client-specific validation)
- Account linking hooks still execute
- Post-registration hooks still execute
- Intended for administrative operations
Rationale: Management API operations are admin-initiated and bypass client-specific signup restrictions.
Best Practices
- Use
validateRegistrationUsernameearly - Check eligibility before showing signup forms - Log blocked signups - Monitor failed signup attempts for security
- Test with different methods - Verify that all signup methods respect
disable_sign_ups - Use
screen_hint=signup- For invitation flows and onboarding - Monitor account linking - Ensure users with same email are properly linked
## Error Messages
When signup is blocked, users see:
- HTTP 400 status
- Message: "Public signup is disabled for this client" (or custom reason)
- Log entry with type `fs` (failed signup)
## Examples
### Example 1: Blocking Social Login Signups
```typescript
// 1. Configure client
await clients.update("tenant-id", "client-id", {
client_metadata: {
disable_sign_ups: "true",
},
});
// 2. User tries to sign up with Google
// Result: HTTP 400 - "Public signup is disabled for this client"
// 3. Existing user tries to log in with Google
// Result: Success - login proceeds normallyExample 2: Invite-Only Application
// 1. Disable public signups
await clients.update("tenant-id", "client-id", {
client_metadata: {
disable_sign_ups: "true",
},
});
// 2. Send invitation with screen_hint
const inviteUrl =
`https://your-domain/authorize?` +
`screen_hint=signup&` +
`client_id=client-id&` +
`redirect_uri=https://your-app/callback`;
// 3. User clicks invite link and signs up
// Result: Success - allowed via screen_hint=signupExample 3: Account Linking
// 1. User signs up with email/password
await fetch("/dbconnections/signup", {
method: "POST",
body: JSON.stringify({
email: "user@example.com",
password: "SecurePass123!",
connection: "Username-Password-Authentication",
client_id: "client-id",
}),
});
// 2. Later, disable public signups
await clients.update("tenant-id", "client-id", {
client_metadata: {
disable_sign_ups: "true",
},
});
// 3. User tries to log in with Google (same email)
// Result: Success - Google account is linked to existing account
// Reason: Account linking is allowed even when signups are disabled