| title | Authentication Standard |
|---|---|
| description | The standard authentication protocol specification for the ObjectStack ecosystem |
The standard authentication protocol specification for the ObjectStack ecosystem.
This document defines the ObjectStack Authentication Standard, a comprehensive, framework-agnostic authentication protocol for ObjectStack applications. This standard supports multiple authentication strategies, session management, and comprehensive security features.
The specification is designed as an interface that can be implemented by any authentication library. better-auth serves as the Reference Implementation (default driver) for this standard.
The authentication standard can be implemented using various drivers:
- better-auth (default/reference implementation)
- Auth.js
- Passport
- Custom implementations
- Email/Password: Traditional email and password authentication with customizable password policies
- Magic Link: Passwordless email-based authentication
- OAuth: Social login with popular providers (Google, GitHub, Facebook, Twitter, LinkedIn, Microsoft, Apple, Discord, GitLab)
- Passkey: WebAuthn/FIDO2 biometric authentication
- OTP: One-time password authentication (SMS, Email)
- Anonymous: Guest/anonymous session support
- Enterprise SSO: SAML 2.0, LDAP/Active Directory, and OIDC for enterprise single sign-on
- Rate Limiting: Configurable rate limiting to prevent brute-force attacks
- CSRF Protection: Built-in CSRF token validation
- Session Fingerprinting: Enhanced session security with device fingerprinting
- Two-Factor Authentication (2FA): TOTP-based 2FA with backup codes
- Account Linking: Link multiple authentication methods to a single user account
- IP-based Rate Limiting: Prevent attacks from specific IP addresses
- Customizable session expiry and renewal
- Secure cookie configuration (HttpOnly, Secure, SameSite)
- Maximum concurrent sessions per user
- Session update intervals
- Lifecycle Hooks:
beforeSignIn,afterSignIn,beforeSignUp,afterSignUp,beforeSignOut,afterSignOut - Database Adapters: Support for Prisma, Drizzle, Kysely, and custom adapters
- Email Providers: Integration with SendGrid, Mailgun, AWS SES, Resend, SMTP
- Field Mapping: Map better-auth user fields to your custom user object
- Plugin System: Extend better-auth with custom plugins
pnpm add @objectstack/plugin-better-authimport type { AuthConfig } from '@objectstack/spec';
const authConfig: AuthConfig = {
name: 'main_auth',
label: 'Main Authentication',
driver: 'better-auth', // Optional, defaults to 'better-auth'
strategies: ['email_password'],
baseUrl: 'https://app.example.com',
secret: process.env.AUTH_SECRET!,
emailPassword: {
enabled: true,
requireEmailVerification: true,
minPasswordLength: 8,
},
session: {},
rateLimit: {},
csrf: {},
accountLinking: {},
};const oauthConfig: AuthConfig = {
name: 'social_auth',
label: 'Social Login',
strategies: ['oauth'],
baseUrl: 'https://app.example.com',
secret: process.env.AUTH_SECRET!,
oauth: {
providers: [
{
provider: 'google',
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ['openid', 'profile', 'email'],
},
],
},
session: {},
rateLimit: {},
csrf: {},
accountLinking: {},
};const multiAuthConfig: AuthConfig = {
name: 'multi_auth',
label: 'Multi-Strategy Auth',
strategies: ['email_password', 'oauth', 'magic_link'],
baseUrl: 'https://app.example.com',
secret: process.env.AUTH_SECRET!,
emailPassword: {
enabled: true,
minPasswordLength: 10,
},
oauth: {
providers: [
{ provider: 'google', clientId: '...', clientSecret: '...' },
{ provider: 'github', clientId: '...', clientSecret: '...' },
],
},
magicLink: {
enabled: true,
expiryTime: 900,
},
session: {
expiresIn: 604800, // 7 days
},
rateLimit: {
enabled: true,
maxAttempts: 5,
},
csrf: {
enabled: true,
},
accountLinking: {
enabled: true,
autoLink: false,
},
};const enterpriseAuthConfig: AuthConfig = {
name: 'enterprise_sso',
label: 'Enterprise SSO',
strategies: ['oauth'],
baseUrl: 'https://app.example.com',
secret: process.env.AUTH_SECRET!,
// Standard OAuth for consumer providers
oauth: {
providers: [
{ provider: 'google', clientId: '...', clientSecret: '...' },
],
},
// Enterprise authentication methods
enterprise: {
// OIDC (Modern standard for enterprise SSO)
oidc: {
enabled: true,
issuer: 'https://auth.company.com',
clientId: process.env.OIDC_CLIENT_ID!,
clientSecret: process.env.OIDC_CLIENT_SECRET!,
scopes: ['openid', 'profile', 'email', 'groups'],
attributeMapping: {
email: 'email',
name: 'name',
groups: 'roles',
},
displayName: 'Login with Corporate SSO',
},
// SAML 2.0 (Legacy enterprise SSO)
saml: {
enabled: true,
entryPoint: 'https://idp.company.com/saml/sso',
cert: process.env.SAML_CERT!,
issuer: 'https://idp.company.com',
signatureAlgorithm: 'sha256',
attributeMapping: {
email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
name: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name',
},
displayName: 'Login with SAML',
},
// LDAP/Active Directory (On-premise)
ldap: {
enabled: true,
url: 'ldaps://ldap.company.com:636',
bindDn: 'CN=Service Account,OU=Users,DC=company,DC=com',
bindCredentials: process.env.LDAP_PASSWORD!,
searchBase: 'OU=Users,DC=company,DC=com',
searchFilter: '(&(objectClass=user)(sAMAccountName={{username}}))',
groupSearchBase: 'OU=Groups,DC=company,DC=com',
displayName: 'Login with Active Directory',
},
},
session: {
expiresIn: 28800, // 8 hours for enterprise
},
rateLimit: {
enabled: true,
maxAttempts: 5,
},
csrf: {
enabled: true,
},
};| Property | Type | Required | Description |
|---|---|---|---|
name |
string |
✅ | Configuration identifier (snake_case) |
label |
string |
✅ | Human-readable label |
strategies |
AuthStrategy[] |
✅ | Enabled authentication strategies |
baseUrl |
string |
✅ | Application base URL |
secret |
string |
✅ | Secret key for signing (min 32 chars) |
emailPassword: {
enabled: boolean; // Enable email/password auth
requireEmailVerification: boolean; // Require email verification
minPasswordLength: number; // Minimum password length (6-128)
requirePasswordComplexity: boolean; // Require uppercase, lowercase, numbers, symbols
allowPasswordReset: boolean; // Enable password reset
passwordResetExpiry: number; // Reset token expiry in seconds
}magicLink: {
enabled: boolean; // Enable magic link auth
expiryTime: number; // Link expiry in seconds (default: 900)
sendEmail?: Function; // Custom email sending function
}passkey: {
enabled: boolean; // Enable passkey auth
rpName: string; // Relying Party name
rpId?: string; // Relying Party ID (defaults to domain)
allowedOrigins?: string[]; // Allowed origins
userVerification?: 'required' | 'preferred' | 'discouraged';
attestation?: 'none' | 'indirect' | 'direct' | 'enterprise';
}oauth: {
providers: [{
provider: 'google' | 'github' | 'facebook' | ...; // OAuth provider
clientId: string; // OAuth client ID
clientSecret: string; // OAuth client secret
scopes?: string[]; // Requested scopes
redirectUri?: string; // Callback URL
enabled?: boolean; // Enable/disable provider
displayName?: string; // Button label
icon?: string; // Icon URL
}]
}session: {
expiresIn: number; // Session expiry in seconds (default: 604800)
updateAge: number; // Update interval in seconds (default: 86400)
cookieName: string; // Cookie name
cookieSecure: boolean; // Use secure cookies (HTTPS only)
cookieSameSite: 'strict' | 'lax' | 'none'; // SameSite attribute
cookieDomain?: string; // Cookie domain
cookiePath: string; // Cookie path (default: '/')
cookieHttpOnly: boolean; // HttpOnly attribute
}rateLimit: {
enabled: boolean; // Enable rate limiting
maxAttempts: number; // Max login attempts (default: 5)
windowMs: number; // Time window in ms (default: 900000)
blockDuration: number; // Block duration in ms
skipSuccessfulRequests: boolean; // Only count failed requests
}
csrf: {
enabled: boolean; // Enable CSRF protection
tokenLength: number; // CSRF token length (default: 32)
cookieName: string; // CSRF cookie name
headerName: string; // CSRF header name
}
security: {
allowedOrigins?: string[]; // CORS allowed origins
trustProxy: boolean; // Trust proxy headers
ipRateLimiting: boolean; // Enable IP-based rate limiting
sessionFingerprinting: boolean; // Enable session fingerprinting
maxSessions: number; // Max concurrent sessions (default: 5)
}twoFactor: {
enabled: boolean; // Enable 2FA
issuer?: string; // TOTP issuer name
qrCodeSize: number; // QR code size in pixels (default: 200)
backupCodes?: {
enabled: boolean; // Enable backup codes
count: number; // Number of backup codes (default: 10)
}
}enterprise: {
// OpenID Connect (Modern enterprise SSO)
oidc?: {
enabled: boolean; // Enable OIDC
issuer: string; // OIDC Issuer URL
clientId: string; // OIDC client ID
clientSecret: string; // OIDC client secret
scopes?: string[]; // OIDC scopes (default: ['openid', 'profile', 'email'])
attributeMapping?: Record<string, string>; // Map IdP claims to user fields
displayName?: string; // Button label
icon?: string; // Icon URL
},
// SAML 2.0 (Legacy enterprise SSO)
saml?: {
enabled: boolean; // Enable SAML
entryPoint: string; // IdP SSO URL
cert: string; // IdP Public Certificate (PEM)
issuer: string; // Entity ID of the IdP
signatureAlgorithm?: 'sha256' | 'sha512'; // Signature algorithm
attributeMapping?: Record<string, string>; // Map SAML attributes to user fields
displayName?: string; // Button label
icon?: string; // Icon URL
},
// LDAP/Active Directory (On-premise)
ldap?: {
enabled: boolean; // Enable LDAP
url: string; // LDAP Server URL (ldap:// or ldaps://)
bindDn: string; // Bind DN
bindCredentials: string; // Bind credentials
searchBase: string; // Search base DN
searchFilter: string; // Search filter
groupSearchBase?: string; // Group search base DN
displayName?: string; // Button label
icon?: string; // Icon URL
}
}hooks: {
beforeSignIn?: ({ email }) => Promise<void>;
afterSignIn?: ({ user, session }) => Promise<void>;
beforeSignUp?: ({ email, name? }) => Promise<void>;
afterSignUp?: ({ user }) => Promise<void>;
beforeSignOut?: ({ sessionId }) => Promise<void>;
afterSignOut?: ({ sessionId }) => Promise<void>;
}The mapping configuration allows you to map ObjectStack standard field names (which follow Auth.js conventions) to driver-specific field names. This is particularly useful for ensuring compatibility with authentication libraries like better-auth that use different column naming conventions.
mapping: {
// User model field mapping (optional)
user?: {
emailVerified: 'email_verified', // Map to snake_case
createdAt: 'created_at',
updatedAt: 'updated_at',
},
// Session model field mapping (default better-auth compatible)
session: {
sessionToken: 'token', // better-auth uses 'token'
expires: 'expiresAt', // better-auth uses 'expiresAt'
},
// Account model field mapping (default better-auth compatible)
account: {
providerAccountId: 'accountId', // better-auth uses 'accountId'
provider: 'providerId', // better-auth uses 'providerId'
},
// Verification token field mapping (optional)
verificationToken?: {
identifier: 'email',
},
}Default Mappings for better-auth Compatibility:
By default, the session and account mappings are pre-configured for better-auth compatibility:
| ObjectStack Field | better-auth Field |
|---|---|
sessionToken |
token |
expires |
expiresAt |
providerAccountId |
accountId |
provider |
providerId |
You only need to specify the mapping configuration if you want to:
- Use a different authentication driver with non-standard field names
- Override the default better-auth mappings
- Add custom mappings for user or verification token fields
Example with custom mappings:
const authConfig: AuthConfig = {
name: 'custom_auth',
label: 'Custom Auth',
driver: 'custom-driver',
strategies: ['email_password'],
baseUrl: 'https://example.com',
secret: process.env.AUTH_SECRET!,
mapping: {
user: {
emailVerified: 'is_verified',
createdAt: 'created',
updatedAt: 'modified',
},
session: {
sessionToken: 'session_id',
expires: 'expires_at',
},
account: {
providerAccountId: 'external_id',
provider: 'auth_provider',
},
},
session: {},
rateLimit: {},
csrf: {},
accountLinking: {},
};- Google (
google) - GitHub (
github) - Facebook (
facebook) - Twitter (
twitter) - LinkedIn (
linkedin) - Microsoft (
microsoft) - Apple (
apple) - Discord (
discord) - GitLab (
gitlab) - Custom OAuth2 (
custom)
- Prisma (
prisma) - Drizzle (
drizzle) - Kysely (
kysely) - Custom (
custom)
- SMTP (
smtp) - SendGrid (
sendgrid) - Mailgun (
mailgun) - AWS SES (
ses) - Resend (
resend) - Custom (
custom)
See examples/auth-better-examples.ts for comprehensive usage examples including:
- Basic email/password authentication
- OAuth with Google and GitHub
- Multi-strategy authentication
- Production-ready configuration
- Plugin manifest integration
The authentication system is organized into three main files:
- Configuration (
packages/spec/src/system/auth.zod.ts): Defines how to login - OAuth, Email, SAML, LDAP, better-auth driver settings - Data Models (
packages/spec/src/system/identity.zod.ts): Defines who is logged in - User, Session, Account schemas - Wire Protocol (
packages/spec/src/system/auth-protocol.ts): Defines how to communicate - API constants, headers, error codes
Additional files:
- Tests:
packages/spec/src/system/auth.test.ts - JSON Schema:
packages/spec/json-schema/AuthConfig.json - Documentation:
content/docs/references/system/AuthConfig.mdx
All schemas are defined using Zod and TypeScript types are inferred automatically:
// Authentication configuration types
import type {
AuthConfig,
StandardAuthProvider,
AuthStrategy,
OAuthProvider,
SessionConfig,
EnterpriseAuthConfig,
OIDCConfig,
SAMLConfig,
LDAPConfig,
// ... and more
} from '@objectstack/spec';
// User and session data model types
import type {
User,
Account,
Session,
VerificationToken,
} from '@objectstack/spec';
// Wire protocol types
import type {
AuthHeaders,
AuthResponse,
AuthError,
TokenPayload,
} from '@objectstack/spec';
import { AUTH_CONSTANTS, AUTH_ERROR_CODES } from '@objectstack/spec';Following ObjectStack conventions:
- Configuration Keys (TypeScript properties):
camelCase(e.g.,maxAttempts,emailPassword) - Machine Names (Data values):
snake_case(e.g.,name: 'main_auth',strategy: 'email_password')
Apache 2.0