Sonamu provides an authentication system based on better-auth. It supports various authentication methods including email/password authentication and social login, with authentication APIs automatically registered at /api/auth/*.
Basic Structure
import { defineConfig } from "sonamu";
export default defineConfig({
server: {
// Enable authentication with default settings
auth: {
emailAndPassword: {
enabled: true,
},
},
// Or detailed configuration
auth: {
basePath: "/api/auth",
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
},
},
// ...
});
auth Settings
Type
Type: BetterAuthOptions (configuration type from better-auth library)
export default defineConfig({
server: {
auth: {
// better-auth configuration options
},
},
});
Email/Password Authentication
export default defineConfig({
server: {
auth: {
emailAndPassword: {
enabled: true,
// Optional: minimum password length
minPasswordLength: 8,
},
},
},
});
Social Login (Google)
export default defineConfig({
server: {
auth: {
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
},
},
});
Social Login (GitHub)
export default defineConfig({
server: {
auth: {
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
},
},
});
Customizing basePath
The default path is /api/auth. To change it:
export default defineConfig({
server: {
auth: {
basePath: "/auth", // Change to /auth/*
emailAndPassword: {
enabled: true,
},
},
},
});
Entity Generation
You need to generate the required entities before using better-auth.
Generate Entities via CLI
This command generates the following entities:
| Entity | Table | Description |
|---|
| User | users | User information |
| Session | sessions | Session information |
| Account | accounts | OAuth account linking |
| Verification | verifications | Email verification, etc. |
If a User entity already exists, only missing fields will be added when running the command.
Generate Entities with Plugins
To use better-auth plugins, specify the required plugins with the --plugins option:
# Single plugin
pnpm sonamu better-auth --plugins=2fa
# Multiple plugins (comma-separated)
pnpm sonamu better-auth --plugins=2fa,admin,username
Supported plugins:
| Plugin ID | Description | Added Fields/Tables |
|---|
2fa | Two-Factor Authentication (TOTP) | TwoFactor table, User.two_factor_enabled |
admin | Admin features | User.role, User.banned, User.ban_reason, User.ban_expires, Session.impersonated_by |
username | Username login | User.username (unique), User.display_username |
phone-number | Phone number verification | User.phone_number (unique), User.phone_number_verified |
passkey | WebAuthn passkey authentication | Passkey table |
sso | SSO login (OIDC/SAML) | SsoProvider table |
api-key | API key authentication | ApiKey table |
jwt | JWT token issuance | Jwks table |
organization | Organization/team management | Organization, Member, Invitation, Team, TeamMember tables, Session.active_organization_id, Session.active_team_id |
anonymous | Anonymous users | User.is_anonymous |
When using plugins, you also need to enable them in sonamu.config.ts. See the Plugin Configuration section below for details.
Field Mapping
Since Sonamu uses snake_case column names, better-auth’s camelCase field names are automatically mapped:
// better-auth → Sonamu
emailVerified → email_verified
createdAt → created_at
updatedAt → updated_at
ipAddress → ip_address
userAgent → user_agent
userId → user_id
// ...
Authentication APIs
Once better-auth is registered, the following APIs become automatically available:
Sign Up
POST /api/auth/sign-up/email
Content-Type: application/json
{
"name": "John Doe",
"email": "user@example.com",
"password": "password123"
}
Sign In
POST /api/auth/sign-in/email
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
Sign Out
Current Session
GET /api/auth/get-session
Social Login (Google)
GET /api/auth/sign-in/social?provider=google
Accessing User Information from Context
In authenticated requests, you can access user information through Context.
import { api, getContext } from "sonamu";
export class MyModel {
@api()
static async myApi() {
const ctx = getContext();
// Currently logged-in user
const user = ctx.user; // User | null
// Current session information
const session = ctx.session; // Session | null
if (!user) {
throw new UnauthorizedError("Login required");
}
return { userId: user.id, userName: user.name };
}
}
User Type
type User = {
id: string;
name: string;
email: string;
emailVerified: boolean;
image: string | null;
createdAt: Date;
updatedAt: Date;
};
Session Type
type Session = {
id: string;
expiresAt: Date;
token: string;
createdAt: Date;
updatedAt: Date;
ipAddress: string | null;
userAgent: string | null;
userId: string;
};
Access Control with Guards
import { api } from "sonamu";
export class AdminModel {
@api({ guards: ["admin"] })
static async adminOnly() {
// Executes only if admin guard passes
return { message: "Admin-only API" };
}
}
guardHandler configuration:
export default defineConfig({
server: {
auth: {
emailAndPassword: { enabled: true },
},
apiConfig: {
guardHandler: async (guard, request) => {
// Get user from Context
const { user } = getContext();
if (guard === "auth" && !user) {
throw new UnauthorizedError("Login required");
}
if (guard === "admin") {
if (!user) {
throw new UnauthorizedError("Login required");
}
// Assuming User entity has a role field
const fullUser = await UserModel.findById("A", user.id);
if (fullUser.role !== "admin") {
throw new UnauthorizedError("Admin access required");
}
}
},
},
},
});
Client-Side Integration
Using with React
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: "http://localhost:4000",
});
// components/LoginForm.tsx
import { authClient } from "../lib/auth-client";
export function LoginForm() {
const handleLogin = async (email: string, password: string) => {
const result = await authClient.signIn.email({
email,
password,
});
if (result.error) {
alert(result.error.message);
return;
}
// Login successful
window.location.href = "/dashboard";
};
// ...
}
// components/GoogleLoginButton.tsx
import { authClient } from "../lib/auth-client";
export function GoogleLoginButton() {
const handleGoogleLogin = () => {
authClient.signIn.social({
provider: "google",
callbackURL: "/dashboard",
});
};
return (
<button onClick={handleGoogleLogin}>
Sign in with Google
</button>
);
}
Plugin Configuration
To use better-auth plugins, add them to the auth.plugins array in sonamu.config.ts.
Before configuring plugins, you must first generate the required entities and fields using pnpm sonamu better-auth --plugins=....
Two-Factor Authentication (2FA)
Enable TOTP-based two-factor authentication:
import { defineConfig } from "sonamu";
import { twoFactor, TWO_FACTOR_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
emailAndPassword: { enabled: true },
plugins: [
twoFactor({
issuer: "My App", // Name displayed in OTP apps
schema: TWO_FACTOR_SCHEMA,
}),
],
},
},
});
2FA-related APIs:
POST /api/auth/two-factor/enable - Start 2FA setup
POST /api/auth/two-factor/verify - Verify 2FA code
POST /api/auth/two-factor/disable - Disable 2FA
Admin Plugin
Supports user roles, banning, and impersonation:
import { defineConfig } from "sonamu";
import { admin, ADMIN_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
emailAndPassword: { enabled: true },
plugins: [
admin({
schema: ADMIN_SCHEMA,
}),
],
},
},
});
Fields added to User table by Admin plugin:
role - User role (default: “user”)
banned - Ban status
ban_reason - Ban reason
ban_expires - Ban expiration time (Unix timestamp)
Username Plugin
Allows login with username instead of email:
import { defineConfig } from "sonamu";
import { username, USERNAME_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
plugins: [
username({
schema: USERNAME_SCHEMA,
}),
],
},
},
});
Fields added to User table by Username plugin:
username - Normalized username (lowercase, unique index)
display_username - Display username (preserves original case)
Phone Number Plugin
Supports phone number verification:
import { defineConfig } from "sonamu";
import { phoneNumber, PHONE_NUMBER_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
plugins: [
phoneNumber({
schema: PHONE_NUMBER_SCHEMA,
}),
],
},
},
});
Fields added to User table by Phone Number plugin:
phone_number - Phone number (unique index)
phone_number_verified - Phone number verification status
Passkey Plugin
Supports WebAuthn/FIDO2 based passkey authentication:
import { defineConfig } from "sonamu";
import { passkey, PASSKEY_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
emailAndPassword: { enabled: true },
plugins: [
passkey({
schema: PASSKEY_SCHEMA,
}),
],
},
},
});
Tables created by Passkey plugin:
passkeys - User passkey information (public key, credential ID, etc.)
Passkey-related APIs:
POST /api/auth/passkey/generate-register-options - Generate passkey registration options
POST /api/auth/passkey/verify-registration - Verify passkey registration
POST /api/auth/passkey/generate-authentication-options - Generate passkey authentication options
POST /api/auth/passkey/verify-authentication - Verify passkey authentication
The @better-auth/passkey package is required to use the Passkey plugin.
SSO Plugin
Supports SSO login through external IdPs (OIDC, SAML):
import { defineConfig } from "sonamu";
import { sso, SSO_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
plugins: [
sso({
...SSO_SCHEMA,
}),
],
},
},
});
Tables created by SSO plugin:
sso_providers - SSO provider settings (including OIDC/SAML configuration)
The @better-auth/sso package is required to use the SSO plugin.
API Key Plugin
Supports API key based authentication:
import { defineConfig } from "sonamu";
import { apiKey, API_KEY_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
plugins: [
apiKey({
schema: API_KEY_SCHEMA,
}),
],
},
},
});
Tables created by API Key plugin:
api_keys - API key information (hashed key, rate limit settings, etc.)
API Key-related APIs:
POST /api/auth/api-key/create - Create API key
POST /api/auth/api-key/revoke - Revoke API key
GET /api/auth/api-key/list - List API keys
JWT Plugin
Supports JWT token issuance and JWKS key management:
import { defineConfig } from "sonamu";
import { jwt, JWT_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
plugins: [
jwt({
schema: JWT_SCHEMA,
}),
],
},
},
});
Tables created by JWT plugin:
jwks - JSON Web Key Set information (public key, private key)
JWT-related APIs:
GET /api/auth/.well-known/jwks.json - JWKS endpoint
POST /api/auth/jwt/generate - Generate JWT token
Organization Plugin
Supports organization, member, invitation, and team management:
import { defineConfig } from "sonamu";
import { organization, ORGANIZATION_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
plugins: [
organization({
schema: ORGANIZATION_SCHEMA,
}),
],
},
},
});
Tables created by Organization plugin:
organizations - Organization information
members - Organization members
invitations - Organization invitations
teams - Teams
team_members - Team members
Fields added to Session table by Organization plugin:
active_organization_id - Current active organization ID
active_team_id - Current active team ID
Organization-related APIs:
POST /api/auth/organization/create - Create organization
POST /api/auth/organization/invite - Invite member
POST /api/auth/organization/accept-invitation - Accept invitation
POST /api/auth/organization/set-active - Set active organization
Anonymous Plugin
Supports anonymous user authentication. Allows creating temporary users without sign-up:
import { defineConfig } from "sonamu";
import { anonymous, ANONYMOUS_SCHEMA } from "sonamu/auth";
export default defineConfig({
server: {
auth: {
plugins: [
anonymous({
schema: ANONYMOUS_SCHEMA,
}),
],
},
},
});
Fields added to User table by Anonymous plugin:
is_anonymous - Whether the user is anonymous
Anonymous-related APIs:
POST /api/auth/sign-in/anonymous - Anonymous login
POST /api/auth/anonymous/link - Link anonymous account to a regular account
Using Multiple Plugins Together
import { defineConfig } from "sonamu";
import {
admin,
ADMIN_SCHEMA,
twoFactor,
TWO_FACTOR_SCHEMA,
username,
USERNAME_SCHEMA,
passkey,
PASSKEY_SCHEMA,
organization,
ORGANIZATION_SCHEMA,
} from "sonamu/auth";
export default defineConfig({
server: {
auth: {
emailAndPassword: { enabled: true },
plugins: [
admin({ schema: ADMIN_SCHEMA }),
twoFactor({
issuer: "My App",
schema: TWO_FACTOR_SCHEMA,
}),
username({ schema: USERNAME_SCHEMA }),
passkey({ schema: PASSKEY_SCHEMA }),
organization({ schema: ORGANIZATION_SCHEMA }),
],
},
},
});
Each plugin’s schema (*_SCHEMA) maps Sonamu’s snake_case column names to better-auth’s camelCase field names. It must be passed along with the corresponding plugin.
Practical Examples
Basic Configuration
import { defineConfig } from "sonamu";
export default defineConfig({
server: {
auth: {
emailAndPassword: {
enabled: true,
},
},
apiConfig: {
guardHandler: async (guard) => {
const { user } = getContext();
if (guard === "auth" && !user) {
throw new UnauthorizedError("Login required");
}
},
},
},
});
Social Login + Email Verification
import { defineConfig } from "sonamu";
export default defineConfig({
server: {
auth: {
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
},
},
});
Important Notes
1. Entity Generation Required
# Generate entities before auth configuration
pnpm sonamu better-auth
# Run migrations
pnpm sonamu migrate run
2. Environment Variables Setup
# .env
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
3. CORS Configuration
When the client runs on a different domain:
export default defineConfig({
server: {
plugins: {
cors: {
origin: ["http://localhost:3000"],
credentials: true,
},
},
auth: {
emailAndPassword: { enabled: true },
},
},
});
4. Compatibility with Existing User Entity
If a User entity already exists, running pnpm sonamu better-auth will only add missing fields. Existing data is preserved.
Next Steps
After completing authentication setup:
- Context - Accessing user information from Context
- Guards - API access control