> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modelence.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Auth Hooks

The [AuthConfig](/api-reference/modelence/server/type-aliases/AuthConfig) type provides hooks for authentication events. Configure these in your `startApp` call under the **`auth:`** key.

## Validation Hooks

### `validateSignup`

Called before a new user is created during email/password signup. Use this to enforce custom validation rules on signup data. Throw an error to reject the signup.

```typescript theme={null}
import { startApp } from 'modelence/server';

startApp({
  auth: {
    validateSignup: ({ email, firstName, lastName, password, handle, avatarUrl }) => {
      // Custom validation logic
      if (password.length < 12) {
        throw new Error('Password must be at least 12 characters');
      }
      if (!firstName) {
        throw new Error('First name is required');
      }
    },
  },
});
```

**Props:**

| Property    | Type                  |
| ----------- | --------------------- |
| `email`     | `string`              |
| `password`  | `string`              |
| `firstName` | `string \| undefined` |
| `lastName`  | `string \| undefined` |
| `avatarUrl` | `string \| undefined` |
| `handle`    | `string \| undefined` |

**Returns:** `void | Promise<void>`

### `onBeforeSignup`

<Note>Available since `modelence@0.17.0`.</Note>

Called after `validateSignup` and the built-in disposable-email check, but before the user document is inserted. Use this to plug in a custom domain-policy check (e.g. a tenant-specific email-domain verification service) without disabling the built-in disposable-email check. Throw an error to reject the signup — the thrown error is re-thrown to the caller and `onSignupError` fires.

Currently only invoked for `'email'` provider signups. OAuth signups are not gated because OAuth providers (Google, GitHub, etc.) do not issue disposable accounts.

```typescript theme={null}
import { startApp } from 'modelence/server';

startApp({
  auth: {
    onBeforeSignup: async ({ email, firstName, lastName, handle, provider, connectionInfo }) => {
      const domain = email.split('@')[1];
      const allowed = await isAllowedDomain(domain);
      if (!allowed) {
        throw new Error(`Signups from ${domain} are not allowed`);
      }
    },
  },
});
```

To replace the built-in disposable-email check entirely with your own logic, set `allowDisposableEmails: true` (also available since `modelence@0.17.0`) and enforce your policy in `onBeforeSignup`.

```typescript theme={null}
startApp({
  auth: {
    allowDisposableEmails: true,
    onBeforeSignup: async ({ email }) => {
      const verdict = await classifyEmailDomain(email);
      if (verdict === 'disposable') {
        throw new Error('Disposable email addresses are not allowed');
      }
    },
  },
});
```

**Props:**

| Property         | Type                          |
| ---------------- | ----------------------------- |
| `email`          | `string`                      |
| `firstName`      | `string \| undefined`         |
| `lastName`       | `string \| undefined`         |
| `handle`         | `string \| undefined`         |
| `provider`       | `'email'`                     |
| `connectionInfo` | `ConnectionInfo \| undefined` |

**Returns:** `void | Promise<void>`

### `validateProfileUpdate`

Called before a user's profile is updated. Use this to enforce custom validation rules on profile updates. Throw an error to reject the update.

```typescript theme={null}
startApp({
  auth: {
    validateProfileUpdate: ({ firstName, lastName, avatarUrl, handle }) => {
      if (handle && handle.length < 3) {
        throw new Error('Handle must be at least 3 characters');
      }
    },
  },
});
```

**Props:**

| Property    | Type                  |
| ----------- | --------------------- |
| `firstName` | `string \| undefined` |
| `lastName`  | `string \| undefined` |
| `avatarUrl` | `string \| undefined` |
| `handle`    | `string \| undefined` |

**Returns:** `void | Promise<void>`

## Custom Handle Generation

### `generateHandle`

By default, handles are derived from the user's email address. This hook lets you generate custom handles based on the user's email and profile information.

```typescript theme={null}
startApp({
  auth: {
    generateHandle: ({ email, firstName, lastName }) => {
      // Generate a custom handle
      if (firstName && lastName) {
        return `${firstName.toLowerCase()}-${lastName.toLowerCase()}`;
      }
      return email.split('@')[0];
    },
  },
});
```

**Props:**

| Property    | Type                  |
| ----------- | --------------------- |
| `email`     | `string`              |
| `firstName` | `string \| undefined` |
| `lastName`  | `string \| undefined` |

**Returns:** `string | Promise<string>` — The generated handle. If the handle conflicts with an existing one, Modelence will automatically append a numeric suffix.

## Auth Events

```typescript theme={null}
startApp({
  auth: {
    onAfterLogin: ({ user, provider, session, connectionInfo }) => {
      // Called after successful login
      console.log(`${user.handle} logged in via ${provider} from ${connectionInfo.ip}`);
    },
    onLoginError: ({ error, provider, session, connectionInfo }) => {
      // Called when login fails
      console.error('Login error:', error.message);
    },
    onAfterSignup: ({ user, provider, session, connectionInfo }) => {
      // Called after successful signup
      // Perfect place to send welcome emails or analytics
    },
    onSignupError: ({ error, provider, session, connectionInfo }) => {
      // Called when signup fails
      console.error('Signup error:', error.message);
    },
    onAfterEmailVerification: ({ user, session, connectionInfo }) => {
      // Called after successful email verification
    },
    onEmailVerificationError: ({ error, session, connectionInfo }) => {
      // Called when email verification fails
    },
  },
});
```

## Error Rendering

### `errorComponent`

Use `errorComponent` to customize how OAuth authentication errors are rendered.

By default, OAuth errors are returned as JSON. Providing `errorComponent` allows you to return a custom HTML response instead.

This is useful when OAuth flows are triggered in a browser context.

```typescript theme={null}
startApp({
  auth: {
    errorComponent: ({ error, statusCode }) => {
      // Safely escape the error string to prevent XSS vulnerabilities
      const escapeHtml = (str: string | number) => String(str).replace(/[&<>"']/g, m => ({
        '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'
      })[m as any] || m);

      return `
        <html>
          <body>
            <h1>Error ${escapeHtml(statusCode)}</h1>
            <p>${escapeHtml(error)}</p>
          </body>
        </html>
      `;
    },
  },
});
```

## Full Example

```typescript theme={null}
import { startApp } from 'modelence/server';

startApp({
  auth: {
    validateSignup: ({ email, password, firstName }) => {
      if (!firstName) {
        throw new Error('First name is required');
      }
    },
    validateProfileUpdate: ({ handle }) => {
      if (handle && !/^[a-z0-9-]+$/.test(handle)) {
        throw new Error('Handle can only contain lowercase letters, numbers, and hyphens');
      }
    },
    onBeforeSignup: async ({ email }) => {
      const domain = email.split('@')[1];
      if (domain === 'blocked.example') {
        throw new Error(`Signups from ${domain} are not allowed`);
      }
    },
    generateHandle: ({ email, firstName, lastName }) => {
      if (firstName && lastName) {
        return `${firstName}-${lastName}`.toLowerCase();
      }
      return email.split('@')[0];
    },
    errorComponent: ({ error, statusCode }) => {
      const escapeHtml = (str: string | number) => String(str).replace(/[&<>"']/g, m => ({
        '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'
      })[m as any] || m);
      return `<html><body><h1>Error ${escapeHtml(statusCode)}</h1><p>${escapeHtml(error)}</p></body></html>`;
    },
    onAfterSignup: ({ user }) => {
      console.log('Welcome!', user.handle);
    },
    onAfterLogin: ({ user, provider }) => {
      console.log(`${user.handle} logged in via ${provider}`);
    },
  },
});
```
