Skip to main content
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

pnpm sonamu better-auth
This command generates the following entities:
EntityTableDescription
UserusersUser information
SessionsessionsSession information
AccountaccountsOAuth account linking
VerificationverificationsEmail 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 IDDescriptionAdded Fields/Tables
2faTwo-Factor Authentication (TOTP)TwoFactor table, User.two_factor_enabled
adminAdmin featuresUser.role, User.banned, User.ban_reason, User.ban_expires, Session.impersonated_by
usernameUsername loginUser.username (unique), User.display_username
phone-numberPhone number verificationUser.phone_number (unique), User.phone_number_verified
passkeyWebAuthn passkey authenticationPasskey table
ssoSSO login (OIDC/SAML)SsoProvider table
api-keyAPI key authenticationApiKey table
jwtJWT token issuanceJwks table
organizationOrganization/team managementOrganization, Member, Invitation, Team, TeamMember tables, Session.active_organization_id, Session.active_team_id
anonymousAnonymous usersUser.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
emailVerifiedemail_verified
createdAtcreated_at
updatedAtupdated_at
ipAddressip_address
userAgentuser_agent
userIduser_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

POST /api/auth/sign-out

Current Session

GET /api/auth/get-session

Social Login (Google)

GET /api/auth/sign-in/social?provider=google
For the complete API list, refer to the better-auth documentation.

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