Skip to content

The @cloudwerk/auth package provides a comprehensive authentication system with providers, sessions, RBAC, multi-tenancy, and rate limiting.

Terminal window
pnpm add @cloudwerk/auth

Main configuration function for the auth system.

import { defineAuthConfig } from '@cloudwerk/auth/convention'
export default defineAuthConfig({
basePath?: string,
session?: SessionConfig,
cookies?: CookieConfig,
pages?: PagesConfig,
secret?: string,
})
PropertyTypeDefaultDescription
basePathstring'/auth'Base path for auth endpoints
sessionSessionConfigSee belowSession configuration
cookiesCookieConfigSee belowCookie configuration
pagesPagesConfigSee belowCustom page paths
secretstringRequired for JWTSecret for signing tokens
interface SessionConfig {
strategy: 'database' | 'jwt' // Session storage strategy
maxAge?: number // Session lifetime in seconds (default: 30 days)
updateAge?: number // Refresh interval in seconds (default: 24 hours)
}
interface CookieConfig {
sessionToken?: {
name?: string // Cookie name (default: 'cloudwerk.session-token')
options?: CookieOptions // Cookie options
}
}
interface CookieOptions {
secure?: boolean // HTTPS only (default: true in production)
httpOnly?: boolean // No JavaScript access (default: true)
sameSite?: 'lax' | 'strict' | 'none' // (default: 'lax')
path?: string // Cookie path (default: '/')
domain?: string // Cookie domain
}
interface PagesConfig {
signIn?: string // Sign-in page (default: '/auth/signin')
signOut?: string // Sign-out page (default: '/auth/signout')
error?: string // Error page (default: '/auth/error')
verifyRequest?: string // Email verification page
newUser?: string // New user redirect
}

Wraps a provider configuration for use in Cloudwerk.

import { defineProvider } from '@cloudwerk/auth/convention'
export default defineProvider(providerConfig)
import { github } from '@cloudwerk/auth/convention'
github({
clientId: string,
clientSecret: string,
scope?: string, // Default: 'read:user user:email'
})
import { google } from '@cloudwerk/auth/convention'
google({
clientId: string,
clientSecret: string,
scope?: string, // Default: 'openid email profile'
})
import { discord } from '@cloudwerk/auth/convention'
discord({
clientId: string,
clientSecret: string,
scope?: string, // Default: 'identify email'
})
import { apple } from '@cloudwerk/auth/convention'
apple({
clientId: string,
clientSecret: string, // Generated from Apple private key
scope?: string,
})

Create a custom OAuth 2.0 provider.

import { createOAuth2Provider } from '@cloudwerk/auth'
createOAuth2Provider({
id: string, // Unique provider ID
name: string, // Display name
clientId: string,
clientSecret: string,
authorization: string, // Authorization endpoint URL
token: string, // Token endpoint URL
userinfo: string, // Userinfo endpoint URL
scope?: string, // OAuth scopes
profile: (profile: unknown) => UserProfile, // Profile mapping
})

UserProfile Interface:

interface UserProfile {
id: string
email?: string
name?: string
image?: string
emailVerified?: Date | null
}

Email/password authentication.

import { credentials } from '@cloudwerk/auth'
credentials({
credentials: {
[fieldName: string]: {
label: string,
type: 'text' | 'email' | 'password',
placeholder?: string,
required?: boolean,
}
},
authorize: (credentials: Record<string, string>, ctx: AuthContext) => Promise<User | null>,
})

Example:

credentials({
credentials: {
email: { label: 'Email', type: 'email', required: true },
password: { label: 'Password', type: 'password', required: true },
},
async authorize(creds, ctx) {
const user = await ctx.env.DB
.prepare('SELECT * FROM users WHERE email = ?')
.bind(creds.email)
.first()
if (!user) return null
const valid = await verifyPassword(creds.password, user.password_hash)
if (!valid) return null
return { id: user.id, email: user.email, name: user.name }
},
})

Magic link / passwordless authentication.

import { email } from '@cloudwerk/auth'
email({
from: string, // Sender email address
maxAge?: number, // Link validity in seconds (default: 86400)
sendVerificationRequest: (params: EmailParams) => Promise<void>,
})
interface EmailParams {
identifier: string // Recipient email
url: string // Magic link URL
token: string // Verification token
expires: Date // Expiration time
}

WebAuthn / passkey authentication.

import { passkey } from '@cloudwerk/auth'
passkey({
rpName: string, // Relying party name
rpId: string, // Relying party ID (domain)
origin: string, // Expected origin
authenticatorAttachment?: 'platform' | 'cross-platform',
userVerification?: 'required' | 'preferred' | 'discouraged',
residentKey?: 'required' | 'preferred' | 'discouraged',
})

Define lifecycle callbacks for the auth flow.

import { defineAuthCallbacks } from '@cloudwerk/auth/convention'
export default defineAuthCallbacks({
signIn?: SignInCallback,
redirect?: RedirectCallback,
session?: SessionCallback,
jwt?: JwtCallback,
})

Called when a user signs in.

type SignInCallback = (params: {
user: User,
account: Account | null,
profile?: Profile,
email?: { verificationRequest?: boolean },
credentials?: Record<string, string>,
}) => Awaitable<boolean | string> // Return false to deny, string to redirect

Customize redirect URLs.

type RedirectCallback = (params: {
url: string,
baseUrl: string,
}) => Awaitable<string>

Customize session data.

type SessionCallback = (params: {
session: Session,
user: User,
token?: JWT,
}) => Awaitable<Session>

Example:

defineAuthCallbacks({
async session({ session, user }) {
session.user.id = user.id
session.user.role = user.role
return session
},
})

Customize JWT token (jwt strategy only).

type JwtCallback = (params: {
token: JWT,
user?: User,
account?: Account,
profile?: Profile,
trigger?: 'signIn' | 'signUp' | 'update',
}) => Awaitable<JWT>

Define roles and permissions.

import { defineRBAC } from '@cloudwerk/auth/convention'
export default defineRBAC({
roles: Role[],
defaultRole?: string,
hierarchy?: Record<string, string[]>,
})
interface Role {
id: string // Unique role identifier
name: string // Display name
permissions: string[] // Permission strings
description?: string // Optional description
}
PatternDescription
resource:actionStandard permission
resource:*All actions on resource
*Full access (admin)
resource:action:ownOnly own resources

Example:

defineRBAC({
roles: [
{
id: 'admin',
name: 'Administrator',
permissions: ['*'],
},
{
id: 'editor',
name: 'Editor',
permissions: [
'posts:create',
'posts:read',
'posts:update',
'posts:delete:own',
'media:*',
],
},
{
id: 'viewer',
name: 'Viewer',
permissions: ['posts:read', 'media:read'],
},
],
defaultRole: 'viewer',
hierarchy: {
editor: ['viewer'], // Editor inherits viewer permissions
},
})

Get the current authenticated user (returns null if not authenticated).

import { getUser } from '@cloudwerk/auth'
const user = getUser()
// Returns: User | null

Get the current session.

import { getSession } from '@cloudwerk/auth'
const session = getSession()
// Returns: Session | null

Check if the current request is authenticated.

import { isAuthenticated } from '@cloudwerk/auth'
if (isAuthenticated()) {
// User is logged in
}

Require authentication; redirects or throws if not authenticated.

import { requireAuth } from '@cloudwerk/auth'
// Redirects to sign-in page if not authenticated
const user = requireAuth()
// Throws UnauthenticatedError instead of redirecting
const user = requireAuth({ throwError: true })
// Custom redirect URL
const user = requireAuth({ redirectTo: '/custom-login' })

Check if the user has a specific role.

import { hasRole } from '@cloudwerk/auth'
if (hasRole('admin')) {
// User is admin
}
// Check for any of multiple roles
if (hasRole(['admin', 'moderator'])) {
// User has admin OR moderator role
}

Check if the user has a specific permission.

import { hasPermission } from '@cloudwerk/auth'
if (hasPermission('posts:delete')) {
// User can delete posts
}

Require a specific role; throws ForbiddenError if lacking.

import { requireRole } from '@cloudwerk/auth'
requireRole('admin') // Throws if not admin
requireRole(['admin', 'moderator']) // Any of these roles

Require a specific permission; throws ForbiddenError if lacking.

import { requirePermission } from '@cloudwerk/auth'
requirePermission('posts:create')

Create authentication middleware for route protection.

import { authMiddleware } from '@cloudwerk/auth/middleware'
export const middleware = authMiddleware(options)
interface AuthMiddlewareOptions {
// Authentication requirements
required?: boolean // Require authentication (default: true)
unauthenticatedRedirect?: string // Redirect URL for unauthenticated
// Role requirements
role?: string // Required role
roles?: string[] // Any of these roles
// Permission requirements
permission?: string // Required permission
permissions?: string[] // Any of these permissions
// Custom authorization
authorize?: (user: User, request: Request) => Awaitable<boolean>
// Forbidden handling
unauthorizedRedirect?: string // Redirect for unauthorized (403)
}

Examples:

// Require authentication
authMiddleware({
unauthenticatedRedirect: '/login',
})
// Require specific role
authMiddleware({
role: 'admin',
unauthorizedRedirect: '/forbidden',
})
// Custom authorization logic
authMiddleware({
async authorize(user, request) {
const url = new URL(request.url)
const resourceId = url.pathname.split('/').pop()
const resource = await getResource(resourceId)
return resource.ownerId === user.id
},
})

Create a tenant resolver for multi-tenant applications.

import { createTenantResolver, createD1TenantStorage } from '@cloudwerk/auth/tenant'
const storage = createD1TenantStorage(env.DB)
const resolver = createTenantResolver(storage, options)
interface TenantResolverOptions {
strategy: 'subdomain' | 'path' | 'header' | 'cookie'
baseDomain?: string // For subdomain strategy
pathPrefix?: string // For path strategy (default: '/t/')
headerName?: string // For header strategy (default: 'X-Tenant-ID')
cookieName?: string // For cookie strategy (default: 'tenant')
}
interface TenantResolver {
resolve(request: Request): Promise<Tenant | null>
require(request: Request): Promise<{ tenant: Tenant }> // Throws if not found
}
import {
createD1TenantStorage,
createKVTenantStorage,
createMemoryTenantStorage,
} from '@cloudwerk/auth/tenant'
// D1 storage
const storage = createD1TenantStorage(env.DB, {
tableName: 'tenants', // Default
})
// KV storage
const storage = createKVTenantStorage(env.TENANTS_KV, {
prefix: 'tenant:', // Default
})
// In-memory (for testing)
const storage = createMemoryTenantStorage()

Rate limiter for login attempts.

import { createLoginRateLimiter, createFixedWindowStorage } from '@cloudwerk/auth/rate-limit'
const storage = createFixedWindowStorage(env.RATE_LIMIT_KV)
const limiter = createLoginRateLimiter(storage, {
limit: 5, // Max attempts
window: 900, // Per 15 minutes (in seconds)
})

Rate limiter for password reset requests.

import { createPasswordResetRateLimiter } from '@cloudwerk/auth/rate-limit'
const limiter = createPasswordResetRateLimiter(storage, {
limit: 3, // Max attempts
window: 3600, // Per hour
})

Rate limiter for email verification requests.

import { createEmailVerificationRateLimiter } from '@cloudwerk/auth/rate-limit'
const limiter = createEmailVerificationRateLimiter(storage, {
limit: 5, // Max attempts
window: 3600, // Per hour
})
interface RateLimiter {
check(request: Request): Promise<RateLimitResult>
}
interface RateLimitResult {
success: boolean // Whether request is allowed
limit: number // Configured limit
remaining: number // Remaining requests
reset: number // Seconds until reset
response?: Response // 429 response if rate limited
}
import {
createFixedWindowStorage,
createSlidingWindowStorage,
} from '@cloudwerk/auth/rate-limit'
// Fixed window (simpler, less accurate)
const storage = createFixedWindowStorage(env.KV)
// Sliding window (more accurate, higher storage)
const storage = createSlidingWindowStorage(env.KV)

Initiate sign-in flow.

import { signIn } from '@cloudwerk/auth/client'
// OAuth provider
await signIn('github')
await signIn('google', { callbackUrl: '/dashboard' })
// Credentials
await signIn('credentials', {
password: 'password',
redirectTo: '/dashboard',
})
// Email/magic link
await signIn('email', { email: '[email protected]' })

Sign out the current user.

import { signOut } from '@cloudwerk/auth/client'
await signOut()
await signOut({ redirectTo: '/' })
await signOut({ redirect: false }) // Returns session data

Get the current session on the client.

import { getSession } from '@cloudwerk/auth/client'
const session = await getSession()
if (session) {
console.log('Logged in as', session.user.email)
}

Create a reactive auth store for frameworks.

import { createAuthStore } from '@cloudwerk/auth/client'
const store = createAuthStore()
// Subscribe to changes
store.subscribe((state) => {
console.log('Auth state:', state.status, state.user)
})
// Get current state
const { user, status } = store.getState()
// Status: 'loading' | 'authenticated' | 'unauthenticated'

Hash a password for storage.

import { hashPassword } from '@cloudwerk/auth'
const hash = await hashPassword('user_password')
// Store hash in database

Verify a password against a hash.

import { verifyPassword } from '@cloudwerk/auth'
const isValid = await verifyPassword('user_password', storedHash)

Generate a secure random token.

import { generateToken } from '@cloudwerk/auth'
const token = await generateToken() // 32 bytes, hex encoded
const token = await generateToken(64) // Custom length

Thrown when authentication is required but missing.

import { UnauthenticatedError } from '@cloudwerk/auth'
class UnauthenticatedError extends Error {
readonly code: 'UNAUTHENTICATED'
readonly status: 401
}

Thrown when user lacks required role/permission.

import { ForbiddenError } from '@cloudwerk/auth'
class ForbiddenError extends Error {
readonly code: 'FORBIDDEN'
readonly status: 403
readonly requiredRole?: string
readonly requiredPermission?: string
}

Thrown when credentials are invalid.

import { InvalidCredentialsError } from '@cloudwerk/auth'
class InvalidCredentialsError extends Error {
readonly code: 'INVALID_CREDENTIALS'
readonly status: 401
}

Thrown when session has expired.

import { SessionExpiredError } from '@cloudwerk/auth'
class SessionExpiredError extends Error {
readonly code: 'SESSION_EXPIRED'
readonly status: 401
}

interface User {
id: string
email?: string | null
name?: string | null
image?: string | null
emailVerified?: Date | null
role?: string
roles?: string[]
}
interface Session {
user: User
expires: Date
}
interface Account {
provider: string
providerAccountId: string
type: 'oauth' | 'oidc' | 'email' | 'credentials' | 'webauthn'
access_token?: string
refresh_token?: string
expires_at?: number
token_type?: string
scope?: string
}
interface JWT {
sub: string // Subject (user ID)
iat: number // Issued at
exp: number // Expires at
jti: string // JWT ID
[key: string]: unknown // Custom claims
}
interface Tenant {
id: string
slug: string
name: string
settings?: Record<string, unknown>
createdAt: Date
updatedAt: Date
}