Skip to content

Getting Started

Create an Auth module and register it with the backend app.

Terminal window
import { backend } from "@layeron/core"
import { auth } from "@layeron/modules"
const app = backend()
const appAuth = auth({
session: {
mode: "both",
accessTokenTtl: "15m",
refreshTokenTtl: "30d",
refreshTokenRotation: true,
},
})
app.use(appAuth)

The default database mode is managed. Auth creates its own internal Database Product store for users and sessions.

Email sign-up uses an Email product to send the sign-up message. Configure the Email product first, then pass it to Auth.

Terminal window
import { auth, email } from "@layeron/modules"
const mail = email.send({
name: "main",
namespace: "mailer",
domain: "example.com",
})
const appAuth = auth({
email: {
product: mail,
sender: "auth@example.com",
template: "auth-sign-up",
otp: {
required: true,
length: 6,
ttl: "10m",
minSendInterval: "1m",
maxAttempts: 5,
},
},
password: {
minLength: 12,
requireNumber: true,
hash: {
algorithm: "argon2id",
level: "balanced",
argon2id: {
memoryKiB: 32768,
iterations: 2,
},
},
},
signInWithPassword: {
emailOtp: {
required: true,
template: "auth-sign-in",
ttl: "10m",
minSendInterval: "1m",
remember: {
enabled: true,
ttl: "30d",
},
},
},
updatePassword: {
requireCurrentPassword: true,
emailOtp: {
required: true,
template: "auth-password-update",
ttl: "10m",
minSendInterval: "1m",
maxAttempts: 5,
},
},
resetPasswordForEmail: {
template: "auth-password-reset",
minSendInterval: "1m",
token: {
ttl: "10m",
maxAttempts: 5,
},
},
})
app.use(mail)
app.use(appAuth)

Auth sends email through the Email product send API. The configured template name, OTP or token data, metadata, and idempotency key are passed in the email payload.

Auth uses minSendInterval to limit repeat email sends for the same active OTP or reset flow. The default is 1m. During that interval, Auth reuses the active challenge and skips another Email product call.

password.minLength must be at least 8. Auth rejects weaker configuration at startup and rejects weak passwords during sign-up. Password hashes use Argon2id by default with 32768 KiB of memory, 2 iterations, 1 parallelism, a 16-byte salt, and a 32-byte derived hash.

Set password.enabled to false only when the app uses another login method for all password-related flows. Auth then rejects password sign-up, password sign-in, password updates, and password reset requests.

Use PBKDF2-SHA256 when an application needs the older SHA-256 based hash format. Auth runs PBKDF2 through Workers Web Crypto.

Terminal window
const appAuth = auth({
password: {
hash: {
algorithm: "pbkdf2_sha256",
pbkdf2Sha256: {
iterations: 600000,
},
},
},
})

When email.otp.required is true, signUp creates the user and password credential, creates an email_password identity, stores a hashed OTP token, sends the OTP through Email, and returns without a session.

Terminal window
app.post("/signup", async (request) => {
const body = await request.json()
const result = await appAuth.signUp({
email: body.email,
password: body.password,
user: {
displayName: body.name,
},
})
return {
user: result.user,
verification: result.verification,
}
})

Complete OTP sign-up by verifying the code. Auth marks the email as verified, creates a session, and returns the session token.

Terminal window
app.post("/signup/verify", async (request) => {
const body = await request.json()
const result = await appAuth.verifyEmailOtp({
email: body.email,
verificationId: body.verificationId,
otp: body.otp,
})
return {
user: result.user,
accessToken: result.accessToken,
}
})

Set email.otp.required to false when the app should create a session during sign-up. Auth still sends the configured sign-up email through the Email product.

Call signInWithPassword after collecting the user’s email and password.

Terminal window
app.post("/login", async (request) => {
const body = await request.json()
const result = await appAuth.signInWithPassword({
email: body.email,
password: body.password,
rememberToken: body.rememberToken,
})
return result
})

When signInWithPassword.emailOtp.required is true, Auth verifies the password, stores a hashed OTP token, sends the OTP through Email, and returns without a session. A valid rememberToken skips the OTP step for the same email and creates the session after the password check. Repeat sign-in attempts inside signInWithPassword.emailOtp.minSendInterval reuse the active OTP challenge.

Terminal window
app.post("/login/verify", async (request) => {
const body = await request.json()
const result = await appAuth.verifySignInEmailOtp({
email: body.email,
verificationId: body.verificationId,
otp: body.otp,
remember: body.rememberDevice,
})
return {
user: result.user,
accessToken: result.accessToken,
rememberToken: result.rememberToken,
rememberTokenExpiresAt: result.rememberTokenExpiresAt,
}
})

Auth stores remember tokens as hashes and returns the plain token only from the successful verifySignInEmailOtp call. remember: true requires signInWithPassword.emailOtp.remember.enabled.

updatePassword requires an active session. By default, Auth also requires the current password. Set updatePassword.requireCurrentPassword to false when a logged-in user can set a new password directly.

Terminal window
app.post("/password", { auth: "user" }, async (request) => {
const body = await request.json()
const result = await appAuth.updatePassword({
currentPassword: body.currentPassword,
newPassword: body.newPassword,
})
return result
})

When updatePassword.emailOtp.required is true, Auth verifies the session and current password policy, stores a pending password hash, sends the OTP through Email, and returns without changing the credential. Repeat password update attempts inside updatePassword.emailOtp.minSendInterval reuse the active OTP challenge.

Terminal window
app.post("/password/verify", { auth: "user" }, async (request) => {
const body = await request.json()
const result = await appAuth.verifyPasswordUpdateEmailOtp({
verificationId: body.verificationId,
otp: body.otp,
})
return {
user: result.user,
updatedAt: result.updatedAt,
}
})

Auth stores the pending password as a hash while the OTP is active. A successful OTP verification writes the new password hash to the credential record.

resetPasswordForEmail sends a one-time reset token through the Email product. Auth stores only the token hash. The method returns the same sent: true result when the email has no password credential. Repeat reset requests inside resetPasswordForEmail.minSendInterval reuse the active reset flow and return the same neutral result.

Terminal window
app.post("/password/reset", async (request) => {
const body = await request.json()
return await appAuth.resetPasswordForEmail({
email: body.email,
})
})

Use verifyPasswordResetToken before showing a password reset form when the application needs a separate token check.

Terminal window
app.post("/password/reset/verify", async (request) => {
const body = await request.json()
return await appAuth.verifyPasswordResetToken({
verificationId: body.verificationId,
token: body.token,
})
})

Use setPasswordWithResetToken to write the new password. The new password uses the same password strength policy as sign-up and logged-in password updates.

Terminal window
app.post("/password/reset/complete", async (request) => {
const body = await request.json()
return await appAuth.setPasswordWithResetToken({
verificationId: body.verificationId,
token: body.token,
newPassword: body.newPassword,
})
})

Call createSession after your application has validated a login attempt.

Terminal window
app.post("/login", async () => {
const result = await appAuth.createSession({
user: {
email: "ada@example.com",
displayName: "Ada",
roles: ["member"],
},
})
const headers = new Headers({
"content-type": "application/json",
})
if (result.setCookie) {
headers.set("set-cookie", result.setCookie)
}
return new Response(JSON.stringify({ user: result.user }), {
headers,
})
})

In managed mode, Auth creates or updates the user row before it writes the session row. In mapped, custom, and external modes, pass userId or user.id so Auth can resolve the user before it writes the session row.

createSession, sign-up, password sign-in, OTP verification, and password reset completion can return a refreshToken. Store the refresh token in server-side session storage or in a secure client storage strategy chosen by the application.

Terminal window
app.post("/session/refresh", async (request) => {
const body = await request.json()
const result = await appAuth.refreshSession({
refreshToken: body.refreshToken,
})
return {
accessToken: result.accessToken,
refreshToken: result.refreshToken,
session: result.session,
}
})

By default, refreshSession rotates the refresh token and marks the old token as replaced. Reusing a replaced, revoked, expired, or reused token fails with auth_invalid_refresh_token and revokes the token family.

Use auth: "user" on any route that requires a valid session.

Terminal window
app.get("/me", { auth: "user" }, async () => {
const user = await appAuth.getUser()
return user
})

The Gateway reads the bearer token or session cookie, verifies it through the Auth Product Worker, and returns 401 when the token is missing, expired, or revoked.

Read the active user and session from the current request token.

Terminal window
app.get("/me", { auth: "user" }, async () => {
return {
user: await appAuth.getUser(),
userId: await appAuth.getUserId(),
session: await appAuth.getSession(),
}
})

Use signOut to revoke the active session. revokeSession accepts a session id or access token. revokeAllSessions revokes every active session for the current user, or for a specific userId in server-side code.

Terminal window
app.post("/logout", { auth: "user" }, async () => {
const session = await appAuth.signOut()
return {
revoked: Boolean(session?.revokedAt),
}
})
Terminal window
app.post("/logout-all", { auth: "user" }, async () => {
return await appAuth.revokeAllSessions()
})

Auth methods throw AuthError. The error has code, message, status, and details fields. Route error responses use the same code and status when an Auth error reaches the Gateway.

Terminal window
import { AuthError } from "@layeron/modules"
app.post("/password", { auth: "user" }, async (request) => {
try {
const body = await request.json()
return await appAuth.updatePassword({
currentPassword: body.currentPassword,
newPassword: body.newPassword,
})
} catch (error) {
if (error instanceof AuthError) {
return Response.json(error.toJSON(), { status: error.status })
}
throw error
}
})
  • User storage modes: Choose where Auth reads and writes user records.
  • GitHub login: Add built-in OAuth sign-in with Auth-managed sessions.
  • OIDC login: Connect an OpenID Connect provider with PKCE.
  • Passkeys: Add WebAuthn registration, login, MFA, and step-up checks.
  • API reference: Review Auth options, operations, and runtime result contracts.