Skip to main content

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:

VariableRead ByDescription
LUMENIZE_AUTH_REDIRECTAuth DO302 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.

VariableDefaultRead ByDescription
LUMENIZE_AUTH_ISSUER'https://lumenize.local'Auth DO, hooksJWT iss claim
LUMENIZE_AUTH_AUDIENCE'https://lumenize.local'Auth DO, hooksJWT aud claim
LUMENIZE_AUTH_ACCESS_TOKEN_TTL900 (15 min)Auth DOAccess token expiration in seconds
LUMENIZE_AUTH_REFRESH_TOKEN_TTL2592000 (30 days)Auth DORefresh token expiration in seconds
LUMENIZE_AUTH_MAGIC_LINK_TTL1800 (30 min)Auth DOOne-time login token expiration in seconds
LUMENIZE_AUTH_INVITE_TTL604800 (7 days)Auth DOInvite token expiration in seconds (reusable until expiry)
LUMENIZE_AUTH_PREFIX'/auth'Auth DO, createAuthRoutesURL 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 DOEmail 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 DOSet to "true" to enable test mode — never deploy to production

Secrets and Keys​

VariableTypeRequiredRead ByDescription
JWT_PRIVATE_KEY_BLUESecretYes¹Auth DOEd25519 private key (BLUE slot)
JWT_PUBLIC_KEY_BLUESecretYes¹Auth DO, hooksEd25519 public key (BLUE slot)
JWT_PRIVATE_KEY_GREENSecretNo²Auth DOEd25519 private key (GREEN slot)
JWT_PUBLIC_KEY_GREENSecretNo²Auth DO, hooksEd25519 public key (GREEN slot)
PRIMARY_JWT_KEYVariableYesAuth DOActive signing slot: "BLUE" or "GREEN"
TURNSTILE_SECRET_KEYSecretRecommendedcreateAuthRoutesCloudflare Turnstile server-side secret. Without it, the magic-link endpoint has no bot protection. See Get Started with Turnstile
RESEND_API_KEYSecretNo³ResendEmailSenderResend API key. Only needed if you chose Resend over the default CloudflareEmailSender. See Using Resend instead

¹ At least one key pair (BLUE or GREEN) is required. ² Second slot needed only during key rotation. ³ The default CloudflareEmailSender uses a Worker binding and needs no secret.

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.

BindingTypeRequiredRead ByDescription
LUMENIZE_AUTHDurable Object namespaceYescreateAuthRoutesNamespace binding for the LumenizeAuth DO
AUTH_EMAIL_SENDERService bindingRecommendedAuth DOSelf-referencing service binding pointing to your AuthEmailSender entrypoint. Without it, emails are not delivered (debug-logged instead). See Email Provider
EMAILEmail Sending binding (send_email)With CloudflareEmailSenderCloudflareEmailSenderCloudflare Email Sending binding used to deliver mail. Not needed for ResendEmailSender or bring-your-own-provider.
LUMENIZE_AUTH_RATE_LIMITERRate Limiting bindingRecommendedcreateRouteDORequestAuthHooksPer-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)
├─ CloudflareEmailSender (@lumenize/auth — sendEmail() via env.EMAIL.send — DEFAULT)
│ └─ AuthEmailSender (you — sets from, optional overrides)
└─ ResendEmailSender (@lumenize/auth — sendEmail() via Resend API — alternative)
└─ AuthEmailSender (you — only if using Resend)
ClassPackageWhat you do
AuthEmailSenderBase@lumenize/authAbstract 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.
CloudflareEmailSender@lumenize/authExtends AuthEmailSenderBase. Implements sendEmail() via env.EMAIL.send(...) (the send_email Worker binding). The default — no API key needed. Requires the Workers Paid plan.
ResendEmailSender@lumenize/authExtends AuthEmailSenderBase. Implements sendEmail() via fetch to https://api.resend.com/emails using RESEND_API_KEY. Alternative — see Using Resend instead.
AuthEmailSenderYour WorkerYour 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):

MethodDefault 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):

MethodDefault
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​

VariableRequiredDefaultDescription
fromYes—Bare sender email address (e.g., 'auth@myapp.com'). Both CloudflareEmailSender and ResendEmailSender use appName as the From: display name ("${appName} <${from}>").
replyToNono-reply@{domain}Reply-to address. Domain is extracted from from.
appNameNo'Lumenize'Used in default templates and the 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.