메인 콘텐츠로 건너뛰기
Sonamu에서 사용자 인증을 설정하는 방법을 알아봅니다. Sonamu는 better-auth를 기반으로 한 통합 인증 시스템을 제공합니다.

인증 시스템 개요

better-auth 통합

검증된 인증 라이브러리이메일/비밀번호, 소셜 로그인

자동 엔티티 생성

CLI로 엔티티 자동 생성User, Session, Account, Verification

Context 기반

요청마다 사용자 정보Sonamu.getContext()

Guards 시스템

선언적 권한 제어@api guards 옵션

빠른 시작

1. 인증 엔티티 생성

better-auth가 필요로 하는 엔티티들을 자동으로 생성합니다:
pnpm sonamu auth generate
이 명령어는 4개의 엔티티를 생성합니다:
엔티티테이블설명
Userusers사용자 정보 (id, name, email, email_verified, image)
Sessionsessions세션 정보 (id, token, expires_at, user_id)
Accountaccounts소셜 계정 연결 (id, provider_id, access_token, user_id)
Verificationverifications이메일 인증 등 (id, identifier, value, expires_at)
기존 엔티티가 있으면 누락된 필드만 추가됩니다. 필드 타입이 변경된 경우 자동으로 업데이트됩니다.

2. 마이그레이션 실행

엔티티 생성 후 마이그레이션을 실행합니다:
pnpm sonamu migrate generate
pnpm sonamu migrate run

3. 인증 설정

sonamu.config.ts에서 인증을 활성화합니다:
// sonamu.config.ts
import type { SonamuConfig } from "sonamu";

export default {
  // ... 다른 설정들
  server: {
    auth: {
      emailAndPassword: { enabled: true },
    },
  },
} satisfies SonamuConfig;
이것으로 기본적인 이메일/비밀번호 인증이 활성화됩니다.

API 엔드포인트

better-auth가 제공하는 API 엔드포인트들이 자동으로 등록됩니다:
경로메서드설명
/api/auth/sign-up/emailPOST이메일 회원가입
/api/auth/sign-in/emailPOST이메일 로그인
/api/auth/sign-outPOST로그아웃
/api/auth/get-sessionGET현재 세션 조회

Context에서 사용자 정보 접근

인증된 사용자 정보는 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;

    // user.id, user.email, user.name 등 접근 가능
    return this.findById("A", user.id);
  }
}

AuthContext 타입

type AuthContext = {
  /** 현재 로그인한 사용자 (null이면 미인증) */
  user: User | null;
  /** 현재 세션 정보 (null이면 미인증) */
  session: Session | null;
};
UserSession 타입은 better-auth에서 제공합니다.

Guards를 통한 접근 제어

기본 Guard 사용

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

  // 관리자 권한 필요
  @api({ httpMethod: "DELETE", guards: ["admin"] })
  async deleteUser(id: string) {
    // 관리자만 실행 가능
  }
}

guardHandler 구현

Guard 로직은 sonamu.config.tsguardHandler에서 구현합니다:
// 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("로그인이 필요합니다");
            }
            break;

          case "admin":
            // User 엔티티에 role 필드 추가 필요
            if (!user || (user as any).role !== "admin") {
              throw new Error("관리자만 접근 가능합니다");
            }
            break;

          case "query":
            // 모든 사용자 허용
            break;
        }
      },
    },
  },
} satisfies SonamuConfig;

소셜 로그인 설정

Google 로그인

// 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 로그인

// 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;

사용자 역할 추가

better-auth의 기본 User 엔티티에는 role 필드가 없습니다. 권한 기반 인증이 필요하면 User 엔티티에 직접 추가하세요:

1. Sonamu UI에서 필드 추가

User 엔티티에 role 필드를 추가합니다:
  • 이름: role
  • 타입: string
  • 기본값: "user"

2. Enum 추가 (선택사항)

{
  "enums": {
    "UserRole": {
      "user": "일반 사용자",
      "admin": "관리자",
      "manager": "매니저"
    }
  }
}

3. guardHandler에서 역할 확인

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

  if (guard === "admin") {
    if (!user || (user as any).role !== "admin") {
      throw new Error("관리자 권한이 필요합니다");
    }
  }
},

클라이언트 통합

React에서 사용

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

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

// 회원가입
await authClient.signUp.email({
  email: "user@example.com",
  password: "password123",
  name: "홍길동",
});

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

// 로그아웃
await authClient.signOut();

// 현재 세션 조회
const session = await authClient.getSession();

필드 매핑

better-auth는 camelCase를 사용하지만, Sonamu는 snake_case를 사용합니다. 다음 필드들이 자동으로 매핑됩니다:
better-authSonamu
emailVerifiedemail_verified
createdAtcreated_at
userIduser_id
expiresAtexpires_at

체크리스트

인증 설정 후 확인 사항:
  • pnpm sonamu auth generate 실행
  • 마이그레이션 생성 및 적용
  • sonamu.config.tsserver.auth 설정
  • guardHandler 구현
  • Context에서 user/session 접근 확인
  • 권한 기반 인증 필요 시 User 엔티티에 role 추가

다음 단계