User accounts & auth

env.AUTH is a complete authentication system for your app's own end-users — email + password, sessions, email verification, password reset, and email-based two-factor. It manages its own tables and session cookie; you call methods and return the responses it hands back.

Sign up & sign in

signUp and signIn return a result object. On success it includes a response — a Response with the session cookie already set. Return it (or copy its Set-Cookie) so the browser is signed in:

// api/login.js
export default {
  async fetch(request, env, ctx) {
    const { email, password } = await request.json();
    const r = await env.AUTH.signIn({ email, password });

    if (!r.ok) return Response.json({ error: r.error }, { status: 401 });

    if (r.requires2fa) {
      // a code was emailed; collect it, then call verifyTwoFactorOtp
      await env.AUTH.sendTwoFactorOtp({ otpToken: r.otpToken });
      return Response.json({ requires2fa: true, otpToken: r.otpToken });
    }

    return r.response; // session cookie set
  },
};

signUp({ email, password, name? }) works the same way and returns { ok, user, response }.

Reading the current user

On any request, read who's signed in. getUser returns the user or null; requireUser throws a 401 response if there's no session:

const user = await env.AUTH.getUser(request);
// => { id, email, name, emailVerified, twoFactorEnabled, createdAt } | null

Scope your data queries to user.id.

Two-factor (email OTP)

When a user has 2FA enabled, signIn returns requires2fa: true and an otpToken instead of a session. Email the code, then verify it to complete sign-in:

  • sendTwoFactorOtp({ otpToken }) — email the code.
  • verifyTwoFactorOtp({ otpToken, code }) — returns { ok, user, response }; return the response to set the session.
  • enableTwoFactor(request) / disableTwoFactor(request, { password }).

Password reset

A two-step flow. First the user asks for a reset; env.AUTH emails them a code. Then they enter that code plus a new password on a page you build, and you call resetPassword.

env.AUTH emails a code, not a magic link — it can't know your app's URL. So you expose your own reset page (e.g. /auth/reset) where the user pastes the code. The code expires in 1 hour and is single-use.
  1. Request a reset. Your “forgot password” form posts the email here. requestPasswordReset always returns { ok: true } — even if no such account exists — so you can never leak which emails are registered. Show the same neutral “check your email” message either way.
    // api/forgot-password.js
    export default {
      async fetch(request, env) {
        const { email } = await request.json();
        await env.AUTH.requestPasswordReset(email);
        // Always identical — never reveal whether the account exists.
        return Response.json({ ok: true });
      },
    };
  2. Reset with the code. Your reset page collects the code + new password and posts them here. On success, every existing session for that user is revoked — a reset kicks any attacker off every device, and the user signs in fresh.
    // api/reset-password.js  (your /auth/reset page posts here)
    export default {
      async fetch(request, env) {
        const { token, newPassword } = await request.json();
        const r = await env.AUTH.resetPassword({ token, newPassword });
        if (!r.ok) {
          // "weak_password"  — newPassword under 8 characters
          // "invalid_token"  — wrong, expired, or already-used code
          return Response.json({ error: r.error }, { status: 400 });
        }
        return Response.json({ ok: true }); // old sessions gone; user signs in again
      },
    };

Other methods

  • signOut(request) → a Response that clears the session cookie.
  • updateUser(request, { name }) — update profile fields.
  • changePassword(request, { current, new }) — revokes other sessions on success.
  • sendVerificationEmail(request) · verifyEmail(token).
  • Password reset — requestPasswordReset(email) · resetPassword({ token, newPassword }). See Password reset above for the full flow.

Sessions & storage

Sessions use an HttpOnly; Secure; SameSite=Lax cookie with a 30-day lifetime. User and session data live in the reserved _cr_users, _cr_sessions, and _cr_email_tokens tables in your app's database — don't touch these directly (see Database).

Verification, reset, and 2FA emails are sent through env.EMAIL automatically — there's nothing to configure.