Skip to main content

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.

The 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.
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:
PropertyType
emailstring
passwordstring
firstNamestring | undefined
lastNamestring | undefined
avatarUrlstring | undefined
handlestring | undefined
Returns: void | Promise<void>

onBeforeSignup

Available since modelence@0.17.0.
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.
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.
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:
PropertyType
emailstring
firstNamestring | undefined
lastNamestring | undefined
handlestring | undefined
provider'email'
connectionInfoConnectionInfo | 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.
startApp({
  auth: {
    validateProfileUpdate: ({ firstName, lastName, avatarUrl, handle }) => {
      if (handle && handle.length < 3) {
        throw new Error('Handle must be at least 3 characters');
      }
    },
  },
});
Props:
PropertyType
firstNamestring | undefined
lastNamestring | undefined
avatarUrlstring | undefined
handlestring | 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.
startApp({
  auth: {
    generateHandle: ({ email, firstName, lastName }) => {
      // Generate a custom handle
      if (firstName && lastName) {
        return `${firstName.toLowerCase()}-${lastName.toLowerCase()}`;
      }
      return email.split('@')[0];
    },
  },
});
Props:
PropertyType
emailstring
firstNamestring | undefined
lastNamestring | 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

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

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}`);
    },
  },
});