> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 인증

> better-auth 기반 인증 설정

Sonamu는 [better-auth](https://www.better-auth.com/)를 기반으로 하는 인증 시스템을 제공합니다. 이메일/비밀번호 인증, 소셜 로그인 등 다양한 인증 방식을 지원하며, `/api/auth/*` 경로로 인증 API가 자동 등록됩니다.

## 기본 구조

```typescript theme={null}
import { defineConfig } from "sonamu";

export default defineConfig({
  server: {
    // 기본 설정으로 인증 활성화
    auth: {
      emailAndPassword: {
        enabled: true,
      },
    },

    // 또는 상세 설정
    auth: {
      basePath: "/api/auth",
      emailAndPassword: {
        enabled: true,
      },
      socialProviders: {
        google: {
          clientId: process.env.GOOGLE_CLIENT_ID!,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        },
      },
    },
  },
  // ...
});
```

## auth 설정

### 타입

**타입**: `BetterAuthOptions` (better-auth 라이브러리의 설정 타입)

```typescript theme={null}
export default defineConfig({
  server: {
    auth: {
      // better-auth 설정 옵션
    },
  },
});
```

### 이메일/비밀번호 인증

```typescript theme={null}
export default defineConfig({
  server: {
    auth: {
      emailAndPassword: {
        enabled: true,
        // 선택: 비밀번호 최소 길이
        minPasswordLength: 8,
      },
    },
  },
});
```

### 소셜 로그인 (Google)

```typescript theme={null}
export default defineConfig({
  server: {
    auth: {
      emailAndPassword: {
        enabled: true,
      },
      socialProviders: {
        google: {
          clientId: process.env.GOOGLE_CLIENT_ID!,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        },
      },
    },
  },
});
```

### 소셜 로그인 (GitHub)

```typescript theme={null}
export default defineConfig({
  server: {
    auth: {
      socialProviders: {
        github: {
          clientId: process.env.GITHUB_CLIENT_ID!,
          clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        },
      },
    },
  },
});
```

### basePath 커스터마이징

기본 경로는 `/api/auth`입니다. 변경이 필요한 경우:

```typescript theme={null}
export default defineConfig({
  server: {
    auth: {
      basePath: "/auth", // /auth/* 로 변경
      emailAndPassword: {
        enabled: true,
      },
    },
  },
});
```

## 엔티티 생성

better-auth를 사용하려면 먼저 필요한 엔티티들을 생성해야 합니다.

### CLI로 엔티티 생성

```bash theme={null}
pnpm sonamu better-auth
```

이 명령은 다음 엔티티들을 생성합니다:

| 엔티티          | 테이블           | 설명             |
| ------------ | ------------- | -------------- |
| User         | users         | 사용자 정보         |
| Session      | sessions      | 세션 정보          |
| Account      | accounts      | OAuth 계정 연동 정보 |
| Verification | verifications | 이메일 인증 등       |

<Note>이미 User 엔티티가 있는 경우, 명령 실행 시 누락된 필드만 추가됩니다.</Note>

### 플러그인과 함께 엔티티 생성

better-auth 플러그인을 사용하려면 `--plugins` 옵션으로 필요한 플러그인들을 지정합니다:

```bash theme={null}
# 단일 플러그인
pnpm sonamu better-auth --plugins=2fa

# 여러 플러그인 (쉼표로 구분)
pnpm sonamu better-auth --plugins=2fa,admin,username
```

지원하는 플러그인:

| 플러그인 ID        | 설명                  | 추가되는 필드/테이블                                                                                                        |
| -------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `2fa`          | 2단계 인증 (TOTP)       | TwoFactor 테이블, User.two\_factor\_enabled                                                                           |
| `admin`        | 관리자 기능              | User.role, User.banned, User.ban\_reason, User.ban\_expires, Session.impersonated\_by                              |
| `username`     | 사용자명 로그인            | User.username (unique), User.display\_username                                                                     |
| `phone-number` | 전화번호 인증             | User.phone\_number (unique), User.phone\_number\_verified                                                          |
| `passkey`      | WebAuthn 패스키 인증     | Passkey 테이블                                                                                                        |
| `sso`          | SSO 로그인 (OIDC/SAML) | SsoProvider 테이블                                                                                                    |
| `api-key`      | API 키 인증            | ApiKey 테이블                                                                                                         |
| `jwt`          | JWT 토큰 발급           | Jwks 테이블                                                                                                           |
| `organization` | 조직/팀 관리             | Organization, Member, Invitation, Team, TeamMember 테이블, Session.active\_organization\_id, Session.active\_team\_id |
| `anonymous`    | 익명 사용자              | User.is\_anonymous                                                                                                 |

<Tip>
  플러그인 사용 시 `sonamu.config.ts`에서도 해당 플러그인을 활성화해야 합니다. 자세한 내용은 아래
  [플러그인 설정](#플러그인-설정) 섹션을 참조하세요.
</Tip>

### 필드 매핑

Sonamu는 snake\_case 컬럼명을 사용하므로, better-auth의 camelCase 필드명이 자동으로 매핑됩니다:

```typescript theme={null}
// better-auth → Sonamu
emailVerified → email_verified
createdAt → created_at
updatedAt → updated_at
ipAddress → ip_address
userAgent → user_agent
userId → user_id
// ...
```

## 인증 API

better-auth가 등록되면 다음 API들이 자동으로 사용 가능합니다:

### 회원가입

```
POST /api/auth/sign-up/email
Content-Type: application/json

{
  "name": "홍길동",
  "email": "user@example.com",
  "password": "password123"
}
```

### 로그인

```
POST /api/auth/sign-in/email
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "password123"
}
```

### 로그아웃

```
POST /api/auth/sign-out
```

### 현재 세션

```
GET /api/auth/get-session
```

### 소셜 로그인 (Google)

```
GET /api/auth/sign-in/social?provider=google
```

<Tip>
  전체 API 목록은 [better-auth 문서](https://www.better-auth.com/docs/api-reference)를 참조하세요.
</Tip>

## Context에서 사용자 정보 접근

인증된 요청에서는 Context를 통해 사용자 정보에 접근할 수 있습니다.

```typescript theme={null}
import { api, Sonamu } from "sonamu";

export class MyModel {
  @api()
  static async myApi() {
    const ctx = Sonamu.getContext();

    // 현재 로그인한 사용자
    const user = ctx.user; // User | null

    // 현재 세션 정보
    const session = ctx.session; // Session | null

    if (!user) {
      throw new UnauthorizedError("로그인이 필요합니다");
    }

    return { userId: user.id, userName: user.name };
  }
}
```

### User 타입

```typescript theme={null}
type User = {
  id: string;
  name: string;
  email: string;
  emailVerified: boolean;
  image: string | null;
  createdAt: Date;
  updatedAt: Date;
};
```

### Session 타입

```typescript theme={null}
type Session = {
  id: string;
  expiresAt: Date;
  token: string;
  createdAt: Date;
  updatedAt: Date;
  ipAddress: string | null;
  userAgent: string | null;
  userId: string;
};
```

## Guard를 이용한 접근 제어

```typescript theme={null}
import { api } from "sonamu";

export class AdminModel {
  @api({ guards: ["admin"] })
  static async adminOnly() {
    // admin guard가 통과한 경우에만 실행
    return { message: "관리자 전용 API" };
  }
}
```

guardHandler 설정:

```typescript theme={null}
export default defineConfig({
  server: {
    auth: {
      emailAndPassword: { enabled: true },
    },

    apiConfig: {
      guardHandler: async (guard, request) => {
        // Context에서 user 가져오기
        const { user } = Sonamu.getContext();

        if (guard === "auth" && !user) {
          throw new UnauthorizedError("로그인이 필요합니다");
        }

        if (guard === "admin") {
          if (!user) {
            throw new UnauthorizedError("로그인이 필요합니다");
          }
          // User 엔티티에 role 필드가 있다고 가정
          const fullUser = await UserModel.findById("A", user.id);
          if (fullUser.role !== "admin") {
            throw new UnauthorizedError("관리자 권한이 필요합니다");
          }
        }
      },
    },
  },
});
```

## 클라이언트 측 연동

### React에서 사용

```typescript theme={null}
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";

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

```tsx theme={null}
// 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;
    }

    // 로그인 성공
    window.location.href = "/dashboard";
  };

  // ...
}
```

```tsx theme={null}
// 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}>Google로 로그인</button>;
}
```

## 플러그인 설정

better-auth 플러그인을 사용하려면 `sonamu.config.ts`의 `auth.plugins` 배열에 플러그인을 추가합니다.

<Warning>
  플러그인을 설정하기 전에 먼저 `pnpm sonamu better-auth --plugins=...` 명령으로 필요한 엔티티와
  필드를 생성해야 합니다.
</Warning>

### 2단계 인증 (2FA)

TOTP 기반 2단계 인증을 활성화합니다:

```typescript theme={null}
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", // OTP 앱에 표시될 이름
          schema: TWO_FACTOR_SCHEMA,
        }),
      ],
    },
  },
});
```

2FA 관련 API:

* `POST /api/auth/two-factor/enable` - 2FA 활성화 시작
* `POST /api/auth/two-factor/verify` - 2FA 코드 검증
* `POST /api/auth/two-factor/disable` - 2FA 비활성화

### 관리자 플러그인 (Admin)

사용자 역할, 차단 기능, 대리 로그인(impersonation)을 지원합니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { admin, ADMIN_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      emailAndPassword: { enabled: true },
      plugins: [
        admin({
          schema: ADMIN_SCHEMA,
        }),
      ],
    },
  },
});
```

Admin 플러그인이 User 테이블에 추가하는 필드:

* `role` - 사용자 역할 (기본값: "user")
* `banned` - 차단 여부
* `ban_reason` - 차단 사유
* `ban_expires` - 차단 만료 시간 (Unix timestamp)

### 사용자명 플러그인 (Username)

이메일 대신 사용자명으로 로그인할 수 있습니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { username, USERNAME_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      plugins: [
        username({
          schema: USERNAME_SCHEMA,
        }),
      ],
    },
  },
});
```

Username 플러그인이 User 테이블에 추가하는 필드:

* `username` - 정규화된 사용자명 (소문자, unique 인덱스)
* `display_username` - 표시용 사용자명 (원본 케이스 유지)

### 전화번호 플러그인 (Phone Number)

전화번호 인증을 지원합니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { phoneNumber, PHONE_NUMBER_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      plugins: [
        phoneNumber({
          schema: PHONE_NUMBER_SCHEMA,
        }),
      ],
    },
  },
});
```

Phone Number 플러그인이 User 테이블에 추가하는 필드:

* `phone_number` - 전화번호 (unique 인덱스)
* `phone_number_verified` - 전화번호 인증 여부

### 패스키 플러그인 (Passkey)

WebAuthn/FIDO2 기반 패스키 인증을 지원합니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { passkey, PASSKEY_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      emailAndPassword: { enabled: true },
      plugins: [
        passkey({
          schema: PASSKEY_SCHEMA,
        }),
      ],
    },
  },
});
```

Passkey 플러그인이 생성하는 테이블:

* `passkeys` - 사용자의 패스키 정보 (공개키, 자격 증명 ID 등)

Passkey 관련 API:

* `POST /api/auth/passkey/generate-register-options` - 패스키 등록 옵션 생성
* `POST /api/auth/passkey/verify-registration` - 패스키 등록 검증
* `POST /api/auth/passkey/generate-authentication-options` - 패스키 인증 옵션 생성
* `POST /api/auth/passkey/verify-authentication` - 패스키 인증 검증

<Note>Passkey 플러그인 사용 시 `@better-auth/passkey` 패키지가 필요합니다.</Note>

### SSO 플러그인

외부 IdP(OIDC, SAML)를 통한 SSO 로그인을 지원합니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { sso, SSO_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      plugins: [
        sso({
          ...SSO_SCHEMA,
        }),
      ],
    },
  },
});
```

SSO 플러그인이 생성하는 테이블:

* `sso_providers` - SSO 제공자 설정 (OIDC/SAML 설정 포함)

<Note>SSO 플러그인 사용 시 `@better-auth/sso` 패키지가 필요합니다.</Note>

### API 키 플러그인 (API Key)

API 키 기반 인증을 지원합니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { apiKey, API_KEY_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      plugins: [
        apiKey({
          schema: API_KEY_SCHEMA,
        }),
      ],
    },
  },
});
```

API Key 플러그인이 생성하는 테이블:

* `api_keys` - API 키 정보 (해시된 키, Rate Limit 설정 등)

API Key 관련 API:

* `POST /api/auth/api-key/create` - API 키 생성
* `POST /api/auth/api-key/revoke` - API 키 폐기
* `GET /api/auth/api-key/list` - API 키 목록 조회

### JWT 플러그인

JWT 토큰 발급 및 JWKS 키 관리를 지원합니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { jwt, JWT_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      plugins: [
        jwt({
          schema: JWT_SCHEMA,
        }),
      ],
    },
  },
});
```

JWT 플러그인이 생성하는 테이블:

* `jwks` - JSON Web Key Set 정보 (공개키, 비밀키)

JWT 관련 API:

* `GET /api/auth/.well-known/jwks.json` - JWKS 엔드포인트
* `POST /api/auth/jwt/generate` - JWT 토큰 생성

### 조직 플러그인 (Organization)

조직, 멤버, 초대, 팀 관리를 지원합니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { organization, ORGANIZATION_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      plugins: [
        organization({
          schema: ORGANIZATION_SCHEMA,
        }),
      ],
    },
  },
});
```

Organization 플러그인이 생성하는 테이블:

* `organizations` - 조직 정보
* `members` - 조직 멤버
* `invitations` - 조직 초대
* `teams` - 팀
* `team_members` - 팀 멤버

Organization 플러그인이 Session 테이블에 추가하는 필드:

* `active_organization_id` - 현재 활성 조직 ID
* `active_team_id` - 현재 활성 팀 ID

Organization 관련 API:

* `POST /api/auth/organization/create` - 조직 생성
* `POST /api/auth/organization/invite` - 멤버 초대
* `POST /api/auth/organization/accept-invitation` - 초대 수락
* `POST /api/auth/organization/set-active` - 활성 조직 설정

### 익명 사용자 플러그인 (Anonymous)

익명 사용자 인증을 지원합니다. 회원가입 없이 임시 사용자를 생성할 수 있습니다:

```typescript theme={null}
import { defineConfig } from "sonamu";
import { anonymous, ANONYMOUS_SCHEMA } from "sonamu/auth";

export default defineConfig({
  server: {
    auth: {
      plugins: [
        anonymous({
          schema: ANONYMOUS_SCHEMA,
        }),
      ],
    },
  },
});
```

Anonymous 플러그인이 User 테이블에 추가하는 필드:

* `is_anonymous` - 익명 사용자 여부

Anonymous 관련 API:

* `POST /api/auth/sign-in/anonymous` - 익명 로그인
* `POST /api/auth/anonymous/link` - 익명 계정을 정식 계정으로 연결

### 여러 플러그인 함께 사용

```typescript theme={null}
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 }),
      ],
    },
  },
});
```

<Note>
  각 플러그인의 스키마(`*_SCHEMA`)는 Sonamu의 snake\_case 컬럼명과 better-auth의 camelCase 필드명을
  매핑하는 역할을 합니다. 반드시 해당 플러그인과 함께 전달해야 합니다.
</Note>

## 실전 예시

### 기본 설정

```typescript theme={null}
import { defineConfig } from "sonamu";

export default defineConfig({
  server: {
    auth: {
      emailAndPassword: {
        enabled: true,
      },
    },

    apiConfig: {
      guardHandler: async (guard) => {
        const { user } = Sonamu.getContext();

        if (guard === "auth" && !user) {
          throw new UnauthorizedError("로그인이 필요합니다");
        }
      },
    },
  },
});
```

### 소셜 로그인 + 이메일 인증

```typescript theme={null}
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!,
        },
      },
    },
  },
});
```

## 주의사항

### 1. 엔티티 생성 필수

```bash theme={null}
# auth 설정 전에 먼저 엔티티 생성
pnpm sonamu better-auth

# 마이그레이션 실행
pnpm sonamu migrate run
```

### 2. 환경 변수 설정

```bash theme={null}
# .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 설정

클라이언트가 다른 도메인에서 실행되는 경우:

```typescript theme={null}
export default defineConfig({
  server: {
    plugins: {
      cors: {
        origin: ["http://localhost:3000"],
        credentials: true,
      },
    },

    auth: {
      emailAndPassword: { enabled: true },
    },
  },
});
```

### 4. 기존 User 엔티티와의 호환성

이미 User 엔티티가 있는 경우, `pnpm sonamu better-auth` 실행 시 누락된 필드만 추가됩니다. 기존 데이터는 유지됩니다.

## 다음 단계

인증 설정을 완료했다면:

* [Context](/ko/api-development/context/sonamu-context) - Context에서 사용자 정보 접근
* [Guards](/ko/api-reference/decorators/api#guards) - API 접근 제어
