Skip to main content
Learn how to set up user authentication in Sonamu. Sonamu provides an integrated authentication system based on better-auth.

Authentication System Overview

better-auth Integration

Proven authentication libraryEmail/password, Social login

Auto Entity Generation

Generate entities via CLIUser, Session, Account, Verification

Context Based

User info per requestSonamu.getContext()

Guards System

Declarative permission control@api guards option

Quick Start

1. Generate Auth Entities

Automatically generate entities required by better-auth:
pnpm sonamu auth generate
This command generates 4 entities:
EntityTableDescription
UserusersUser info (id, name, email, email_verified, image)
SessionsessionsSession info (id, token, expires_at, user_id)
AccountaccountsSocial account linking (id, provider_id, access_token, user_id)
VerificationverificationsEmail verification etc. (id, identifier, value, expires_at)
If entities already exist, only missing fields are added. Fields with changed types are automatically updated.

2. Run Migrations

After generating entities, run migrations:
pnpm sonamu migrate generate
pnpm sonamu migrate run

3. Configure Authentication

Enable authentication in sonamu.config.ts:
// sonamu.config.ts
import type { SonamuConfig } from "sonamu";

export default {
  // ... other settings
  server: {
    auth: {
      emailAndPassword: { enabled: true },
    },
  },
} satisfies SonamuConfig;
This enables basic email/password authentication.

API Endpoints

API endpoints provided by better-auth are automatically registered:
PathMethodDescription
/api/auth/sign-up/emailPOSTEmail signup
/api/auth/sign-in/emailPOSTEmail login
/api/auth/sign-outPOSTLogout
/api/auth/get-sessionGETGet current session

Accessing User Info in Context

Access authenticated user information through Context:
import { Sonamu } from "sonamu";

class UserModelClass extends BaseModel {
  @api({ httpMethod: "GET", guards: ["user"] })
  async me(): Promise<UserSubsetA | null> {
    const { user, session } = Sonamu.getContext();

    if (!user) return null;

    // Access user.id, user.email, user.name, etc.
    return this.findById("A", user.id);
  }
}

AuthContext Type

type AuthContext = {
  /** Currently logged in user (null if unauthenticated) */
  user: User | null;
  /** Current session info (null if unauthenticated) */
  session: Session | null;
};
User and Session types are provided by better-auth.

Access Control with Guards

Basic Guard Usage

class UserModelClass extends BaseModel {
  // Login required
  @api({ httpMethod: "GET", guards: ["user"] })
  async getProfile() {
    const { user } = Sonamu.getContext();
    return { userId: user.id };
  }

  // Admin permission required
  @api({ httpMethod: "DELETE", guards: ["admin"] })
  async deleteUser(id: string) {
    // Only admins can execute
  }
}

Implementing guardHandler

Guard logic is implemented in guardHandler in sonamu.config.ts:
// sonamu.config.ts
import { Sonamu } from "sonamu";

export default {
  server: {
    auth: {
      emailAndPassword: { enabled: true },
    },
    apiConfig: {
      guardHandler: (guard, request, api) => {
        const { user } = Sonamu.getContext();

        switch (guard) {
          case "user":
            if (!user) {
              throw new Error("Login required");
            }
            break;

          case "admin":
            // Need to add role field to User entity
            if (!user || (user as any).role !== "admin") {
              throw new Error("Admin access only");
            }
            break;

          case "query":
            // Allow all users
            break;
        }
      },
    },
  },
} satisfies SonamuConfig;

Social Login Setup

Google Login

// sonamu.config.ts
export default {
  server: {
    auth: {
      emailAndPassword: { enabled: true },
      socialProviders: {
        google: {
          clientId: process.env.GOOGLE_CLIENT_ID!,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        },
      },
    },
  },
} satisfies SonamuConfig;

GitHub Login

// sonamu.config.ts
export default {
  server: {
    auth: {
      emailAndPassword: { enabled: true },
      socialProviders: {
        github: {
          clientId: process.env.GITHUB_CLIENT_ID!,
          clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        },
      },
    },
  },
} satisfies SonamuConfig;

Adding User Roles

The default User entity in better-auth doesn’t have a role field. If you need role-based authentication, add it directly to the User entity:

1. Add Field in Sonamu UI

Add role field to User entity:
  • Name: role
  • Type: string
  • Default: "user"

2. Add Enum (Optional)

{
  "enums": {
    "UserRole": {
      "user": "Regular user",
      "admin": "Administrator",
      "manager": "Manager"
    }
  }
}

3. Check Role in guardHandler

guardHandler: (guard, request, api) => {
  const { user } = Sonamu.getContext();

  if (guard === "admin") {
    if (!user || (user as any).role !== "admin") {
      throw new Error("Admin permission required");
    }
  }
},

Client Integration

Using in React

import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient({
  baseURL: "http://localhost:4000/api/auth",
});

// Sign up
await authClient.signUp.email({
  email: "user@example.com",
  password: "password123",
  name: "John Doe",
});

// Sign in
await authClient.signIn.email({
  email: "user@example.com",
  password: "password123",
});

// Sign out
await authClient.signOut();

// Get current session
const session = await authClient.getSession();

Field Mapping

better-auth uses camelCase, but Sonamu uses snake_case. The following fields are automatically mapped:
better-authSonamu
emailVerifiedemail_verified
createdAtcreated_at
userIduser_id
expiresAtexpires_at

Checklist

Things to verify after setting up authentication:
  • Run pnpm sonamu auth generate
  • Generate and apply migrations
  • Configure server.auth in sonamu.config.ts
  • Implement guardHandler
  • Verify user/session access in Context
  • Add role to User entity if role-based auth is needed

Next Steps