Skip to main content

Auth Flow Endpoints

EndpointMethodAuthDescription
{prefix}/email-magic-linkPOSTRequest magic link (self-signup)
{prefix}/magic-linkGETValidate magic link → emailVerified, notify admins
{prefix}/accept-inviteGETAccept invite → emailVerified
{prefix}/refresh-tokenPOSTCookieExchange refresh token for access token
{prefix}/logoutPOSTCookieClear refresh token cookie

Cookie = refresh token cookie (HttpOnly, Secure, SameSite=Strict).

Tokens

TokenQuery param / storageLifetimeReusable?Purpose
One-time login token?one_time_token=...30 minNo (deleted on use)Magic link self-signup
Invite token?invite_token=...7 daysYes (valid until expiry)Admin invite acceptance
Refresh tokenHttpOnly cookie30 daysNo (rotated on use)Obtain new access tokens
Access tokenMemory (JS)15 minN/A (stateless JWT)Authenticate requests, carries claims

Client-Side Token Management

With LumenizeClient: token management is automatic. LumenizeClient handles retrieving the access token using the refresh token cookie, transparent access token refresh, reconnection, and a callback for you to re-route to a login page when necessary. See LumenizeClient: Authentication.

Manual token management (for non-Lumenize Mesh use):

// On app load, get access token using the refresh token cookie
const response = await fetch('/auth/refresh-token', { method: 'POST' });
if (response.status === 401) {
window.location.href = '/login'; // Refresh token expired or missing
} else if (response.status === 403) {
window.location.href = '/pending'; // Authenticated but not yet approved
} else {
const { access_token } = await response.json();
// access_token contains claims: { sub, isAdmin, ... }
}

POST {prefix}/email-magic-link — No auth required

Sends a magic link email to the given address (requires AUTH_EMAIL_SENDER to be configured). Creates the subject record if it doesn't exist. Requires a valid Turnstile token in the body.

const response = await fetch('/auth/email-magic-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
'cf-turnstile-response': turnstileToken // from the Turnstile widget callback
})
});

Response (200):

{
message: "Check your email for the magic link",
expires_in: 1800
}

Returns 403 if Turnstile verification fails.

In test mode, Turnstile validation is skipped entirely. Append ?_test=true to get the magic link directly without email delivery. Response (200):

{
message: "Magic link generated (test mode)"
}

GET {prefix}/magic-link?one_time_token=... — No auth required

Called when the subject clicks the magic link. Validates the one-time login token (single use — deleted after validation), sets emailVerified: true, creates a refresh token, and redirects. If the subject is not yet admin-approved, sends a notification email to all admins (via AUTH_EMAIL_SENDER).

  • Valid token: 302 → LUMENIZE_AUTH_REDIRECT with Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Strict
  • Invalid/expired token: 302 → LUMENIZE_AUTH_REDIRECT?error=invalid_token

Accept Invite

GET {prefix}/accept-invite?invite_token=... — No auth required

Called when an invited subject clicks the invite link. Validates the invite token (reusable until expiry), sets emailVerified: true (subject already has adminApproved: true from the invite), creates a refresh token, and redirects. The subject has immediate access since both flags are now true.

  • Valid token: 302 → LUMENIZE_AUTH_REDIRECT with Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Strict
  • Invalid/expired token: 302 → LUMENIZE_AUTH_REDIRECT?error=invalid_token

Refresh Token

POST {prefix}/refresh-token — Auth required

Exchanges the refresh token cookie for a new access token. The access token JWT contains all claims including status flags and role.

const response = await fetch('/auth/refresh-token', { method: 'POST' });
// 200: { access_token: "eyJ..." }
// 401: Refresh token expired, revoked, or missing
// 403: Subject has emailVerified but not adminApproved (pending approval)

Logout

POST {prefix}/logout — Auth required

Revokes the refresh token and clears the cookie.

await fetch('/auth/logout', { method: 'POST' });

Response (200):

{
message: "Logged out"
}