Skip to content

Email Code

Email Code sends a short-lived verification code to the user’s email address. The current browser flow is implemented through Direct Auth and the Web SDK.

sequenceDiagram
    participant User
    participant App
    participant Authrim
    participant Email as Email Notifier

    User->>App: Enter email address
    App->>Authrim: Send email code request with PKCE challenge
    Authrim->>Email: Send verification code
    Email->>User: Deliver code
    User->>App: Enter code
    App->>Authrim: Verify code with PKCE verifier
    Authrim->>App: Return Direct Auth artifact
    App->>Authrim: Exchange artifact for tokens/session
  • Passwordless: No password to remember or store
  • Direct Auth support: Browser clients can authenticate without a hosted redirect
  • PKCE-bound verification: The send and verify steps are bound by a S256 challenge
  • Single-use codes: Successful verification consumes the server-side challenge
  • User creation support: A new end user can be created from the verified email address
  • Invitation support: invite_token can route signup to the invitation tenant and assignments

Email Code delivery uses a notifier plugin with the notifier.email capability. Configure a built-in email notifier such as Cloudflare Email or Resend, or provide a custom notifier plugin.

Terminal window
# Sender metadata used by email notifier plugins
EMAIL_FROM=noreply@example.com
EMAIL_FROM_NAME=Authrim
# Optional HMAC secret used when hashing email codes
OTP_HMAC_SECRET=replace-with-random-secret

The Direct Auth implementation currently uses a 6-digit code with a 5-minute TTL. Send requests are rate limited per tenant and email address, and verify attempts are limited per code attempt.

POST /api/v1/auth/direct/email-code/send
Content-Type: application/json
{
"client_id": "your-client-id",
"email": "user@example.com",
"code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"code_challenge_method": "S256",
"channel": "browser",
"scope": "openid profile email",
"locale": "en"
}

Optional request fields include invite_token and custom_fields for invitation-based signup and registration fields.

Response:

{
"attempt_id": "7d6ad739-0d19-4e90-b352-8e8b6d7d81ac",
"expires_in": 300,
"masked_email": "u***r@example.com"
}
POST /api/v1/auth/direct/email-code/verify
Content-Type: application/json
{
"attempt_id": "7d6ad739-0d19-4e90-b352-8e8b6d7d81ac",
"code": "123456",
"code_verifier": "original-pkce-code-verifier",
"channel": "browser"
}

Response:

{
"direct_auth_artifact": "daa_...",
"expires_in": 300,
"is_new_user": false
}

The Direct Auth artifact is an intermediate credential. The Web SDK exchanges it through the Direct Auth token exchange and returns the canonical session, user, and token payload.

Authrim also exposes /api/auth/email-codes/send and /api/auth/email-codes/verify for hosted UI and legacy integrations. New browser applications should prefer the Direct Auth endpoints or the Web SDK.

import { createAuthrim } from '@authrim/web';
const authrim = createAuthrim({
issuer: 'https://auth.example.com',
clientId: 'your-client-id',
});
const sendResult = await authrim.emailCode.send('user@example.com', {
locale: 'en',
});
if (sendResult.ok) {
console.log(sendResult.data.maskedEmail);
}
const verifyResult = await authrim.emailCode.verify('user@example.com', '123456');
if (verifyResult.ok) {
console.log(verifyResult.data.session);
}

The SDK stores the pending attemptId and PKCE verifier in memory, exposes hasPendingVerification() and getRemainingTime(), and clears the pending state on success or expiry.

  • Codes are generated server-side and are not returned in API responses.
  • Code validation uses an HMAC-backed hash with tenant/email/attempt context.
  • Challenge state is stored server-side and consumed during verification.
  • Email addresses are normalized for lookup and routing before verification.
LimitCurrent behavior
Send requests3 requests per 15 minutes per tenant/email
Verify attempts5 attempts per code attempt within the code TTL
Code TTL5 minutes
Code format6 numeric digits
  • Require code_challenge_method: "S256" for Direct Auth sends.
  • Verify requests must include the matching code_verifier.
  • The channel submitted during verification must match the original send request.
  • Configure a real email notifier before enabling Email Code in production.
  • Set OTP_HMAC_SECRET to a high-entropy secret instead of relying on fallback values.

The built-in Direct Auth email uses the configured email notifier and sender metadata. For production branding, provide a custom email notifier plugin or configure the active notifier to render your preferred template.

MethodSecurityUsabilityRequirements
Email CodeHighHighEmail access
PasswordMediumMediumPassword storage and reset UX
TOTPHighMediumAuthenticator app
PasskeyVery HighHighCompatible device
SMS OTPMediumHighPhone number

Code not received:

  • Confirm an email notifier plugin is installed and active
  • Check EMAIL_FROM and provider-specific settings
  • Check provider delivery logs and spam folders
  • Verify rate limits have not been exceeded

Code expired or invalid:

  • Request a new code after expiry
  • Ensure the same browser session verifies the code so the SDK still has the PKCE verifier
  • Confirm the user entered the latest 6-digit code

Too many attempts:

  • Ask the user to request a new code
  • Review audit and diagnostic logs for repeated failures