Configuration
All auth configuration is read from environment variables and bindings — no constructor options or config files.
What Must/Might Be in env
Required Config
Only one variable is needed to get started:
| Variable | Read By | Description |
|---|---|---|
LUMENIZE_AUTH_REDIRECT | Auth DO | 302 Location after magic link / invite — relative (/app) or absolute (https://example.com/app) |
Optional Config
The Auth DO and Worker hooks read these from environment variables. Sensible defaults mean you can skip all of them initially.
| Variable | Default | Read By | Description |
|---|---|---|---|
LUMENIZE_AUTH_ISSUER | 'https://lumenize.local' | Auth DO, hooks | JWT iss claim |
LUMENIZE_AUTH_AUDIENCE | 'https://lumenize.local' | Auth DO, hooks | JWT aud claim |
LUMENIZE_AUTH_ACCESS_TOKEN_TTL | 900 (15 min) | Auth DO | Access token expiration in seconds |
LUMENIZE_AUTH_REFRESH_TOKEN_TTL | 2592000 (30 days) | Auth DO | Refresh token expiration in seconds |
LUMENIZE_AUTH_MAGIC_LINK_TTL | 1800 (30 min) | Auth DO | One-time login token expiration in seconds |
LUMENIZE_AUTH_INVITE_TTL | 604800 (7 days) | Auth DO | Invite token expiration in seconds (reusable until expiry) |
LUMENIZE_AUTH_PREFIX | '/auth' | Auth DO, createAuthRoutes | URL prefix for all auth endpoints. createAuthRoutes uses it for route matching; the Auth DO uses it to compose links in emails |
LUMENIZE_AUTH_BOOTSTRAP_EMAIL | — | Auth DO | Email idempotently promoted to admin on every login. Without this, no bootstrap admin is created — you'll need another mechanism to create the first admin |
LUMENIZE_AUTH_TEST_MODE | "false" | Auth DO | Set to "true" to enable test mode — never deploy to production |
Secrets and Keys
| Variable | Type | Required | Read By | Description |
|---|---|---|---|---|
JWT_PRIVATE_KEY_BLUE | Secret | Yes¹ | Auth DO | Ed25519 private key (BLUE slot) |
JWT_PUBLIC_KEY_BLUE | Secret | Yes¹ | Auth DO, hooks | Ed25519 public key (BLUE slot) |
JWT_PRIVATE_KEY_GREEN | Secret | No² | Auth DO | Ed25519 private key (GREEN slot) |
JWT_PUBLIC_KEY_GREEN | Secret | No² | Auth DO, hooks | Ed25519 public key (GREEN slot) |
PRIMARY_JWT_KEY | Variable | Yes | Auth DO | Active signing slot: "BLUE" or "GREEN" |
TURNSTILE_SECRET_KEY | Secret | Recommended | createAuthRoutes | Cloudflare Turnstile server-side secret. Without it, the magic-link endpoint has no bot protection. See Get Started with Turnstile |
RESEND_API_KEY | Secret | Yes³ | ResendEmailSender | Resend API key for email delivery. See Email Provider |
¹ At least one key pair (BLUE or GREEN) is required. ² Second slot needed only during key rotation. ³ Only when using ResendEmailSender (the default). Bring-your-own-provider implementations use their own credentials.
Naming convention: LUMENIZE_AUTH_* for Lumenize-specific config, JWT_* for industry-standard key material (shared across packages), vendor prefix (e.g., TURNSTILE_*) for third-party services.
Bindings
These are Lumenize conventions — define them in your wrangler.jsonc with these exact names.
| Binding | Type | Required | Read By | Description |
|---|---|---|---|---|
LUMENIZE_AUTH | Durable Object namespace | Yes | createAuthRoutes | Namespace binding for the LumenizeAuth DO |
AUTH_EMAIL_SENDER | Service binding | Recommended | Auth DO | Self-referencing service binding pointing to your AuthEmailSender entrypoint. Without it, emails are not delivered (debug-logged instead). See Email Provider |
LUMENIZE_AUTH_RATE_LIMITER | Rate Limiting binding | Recommended | createRouteDORequestAuthHooks | Per-subject rate limiting keyed on sub from the decoded JWT. Without it, authenticated routes have no per-user rate limiting |
Email Provider
LumenizeAuth requires outgoing email for magic links, admin notifications, approval confirmations, and invites. Email delivery is delegated to a WorkerEntrypoint that you define and export from your Worker. The Auth DO calls it via the AUTH_EMAIL_SENDER service binding.
Class Hierarchy
WorkerEntrypoint (Cloudflare)
└─ AuthEmailSenderBase (@lumenize/auth — templates, subjects, from/replyTo/appName)
└─ ResendEmailSender (@lumenize/auth — sendEmail() via Resend API)
└─ AuthEmailSender (you — sets from, optional overrides)
| Class | Package | What you do |
|---|---|---|
AuthEmailSenderBase | @lumenize/auth | Abstract base. Provides send(message) dispatch, 5 default HTML templates, 5 default subject methods, from (abstract), replyTo, appName. You extend this for bring-your-own-provider. |
ResendEmailSender | @lumenize/auth | Extends AuthEmailSenderBase. Implements sendEmail() via fetch to https://api.resend.com/emails using RESEND_API_KEY. You extend this for the default Resend path. |
AuthEmailSender | Your Worker | Your class. Set from, optionally override replyTo, appName, template methods, or subject methods. Export it from your Worker entry point. |
Overridable Methods
All methods receive the corresponding EmailMessage variant as their argument.
Template methods (return HTML string):
| Method | Default behavior |
|---|---|
magicLinkHtml(message) | Login link with sign-in button |
adminNotificationHtml(message) | New signup notification with approve button |
approvalConfirmationHtml(message) | Account approved with link to app |
inviteExistingHtml(message) | Notification to already-verified user |
inviteNewHtml(message) | Onboarding link for new user |
Subject methods (return string):
| Method | Default |
|---|---|
magicLinkSubject(message) | 'Your login link' |
adminNotificationSubject(message) | `New signup: ${message.subjectEmail}` |
approvalConfirmationSubject(message) | 'Your account has been approved' |
inviteExistingSubject(message) | "You've been invited" |
inviteNewSubject(message) | "You've been invited" |
Instance Variables
| Variable | Required | Default | Description |
|---|---|---|---|
from | Yes | — | Bare sender email address (e.g., 'auth@myapp.com'). ResendEmailSender constructs the display name as "${appName} <${from}>" for the Resend API. |
replyTo | No | no-reply@{domain} | Reply-to address. Domain is extracted from from. |
appName | No | 'Lumenize' | Used in default templates and the Resend From: display name. |
ResolvedEmail
The object passed to sendEmail() after template and subject resolution:
interface ResolvedEmail {
to: string; // recipient
subject: string; // resolved by subject method
html: string; // resolved by template method
from: string; // bare email address from instance variable
replyTo: string; // resolved default or override
appName: string; // for providers that use it (e.g., From: display name)
}
For setup instructions, see Getting Started: Email Provider.