Sonamu๋ better-auth๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ ์ธ์ฆ ์์คํ
์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ์ธ์ฆ, ์์
๋ก๊ทธ์ธ ๋ฑ ๋ค์ํ ์ธ์ฆ ๋ฐฉ์์ ์ง์ํ๋ฉฐ, /api/auth/* ๊ฒฝ๋ก๋ก ์ธ์ฆ API๊ฐ ์๋ ๋ฑ๋ก๋ฉ๋๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ
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 ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ค์ ํ์
)
export default defineConfig({
server: {
auth: {
// better-auth ์ค์ ์ต์
},
},
});
์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ์ธ์ฆ
export default defineConfig({
server: {
auth: {
emailAndPassword: {
enabled: true,
// ์ ํ: ๋น๋ฐ๋ฒํธ ์ต์ ๊ธธ์ด
minPasswordLength: 8,
},
},
},
});
์์
๋ก๊ทธ์ธ (Google)
export default defineConfig({
server: {
auth: {
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
},
},
});
์์
๋ก๊ทธ์ธ (GitHub)
export default defineConfig({
server: {
auth: {
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
},
},
});
basePath ์ปค์คํฐ๋ง์ด์ง
๊ธฐ๋ณธ ๊ฒฝ๋ก๋ /api/auth์
๋๋ค. ๋ณ๊ฒฝ์ด ํ์ํ ๊ฒฝ์ฐ:
export default defineConfig({
server: {
auth: {
basePath: "/auth", // /auth/* ๋ก ๋ณ๊ฒฝ
emailAndPassword: {
enabled: true,
},
},
},
});
์ํฐํฐ ์์ฑ
better-auth๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ํ์ํ ์ํฐํฐ๋ค์ ์์ฑํด์ผ ํฉ๋๋ค.
CLI๋ก ์ํฐํฐ ์์ฑ
์ด ๋ช
๋ น์ ๋ค์ ์ํฐํฐ๋ค์ ์์ฑํฉ๋๋ค:
| ์ํฐํฐ | ํ
์ด๋ธ | ์ค๋ช
|
|---|
| User | users | ์ฌ์ฉ์ ์ ๋ณด |
| Session | sessions | ์ธ์
์ ๋ณด |
| Account | accounts | OAuth ๊ณ์ ์ฐ๋ ์ ๋ณด |
| Verification | verifications | ์ด๋ฉ์ผ ์ธ์ฆ ๋ฑ |
์ด๋ฏธ User ์ํฐํฐ๊ฐ ์๋ ๊ฒฝ์ฐ, ๋ช
๋ น ์คํ ์ ๋๋ฝ๋ ํ๋๋ง ์ถ๊ฐ๋ฉ๋๋ค.
ํ๋ฌ๊ทธ์ธ๊ณผ ํจ๊ป ์ํฐํฐ ์์ฑ
better-auth ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ๋ ค๋ฉด --plugins ์ต์
์ผ๋ก ํ์ํ ํ๋ฌ๊ทธ์ธ๋ค์ ์ง์ ํฉ๋๋ค:
# ๋จ์ผ ํ๋ฌ๊ทธ์ธ
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 |
ํ๋ฌ๊ทธ์ธ ์ฌ์ฉ ์ sonamu.config.ts์์๋ ํด๋น ํ๋ฌ๊ทธ์ธ์ ํ์ฑํํด์ผ ํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ ์๋ ํ๋ฌ๊ทธ์ธ ์ค์ ์น์
์ ์ฐธ์กฐํ์ธ์.
ํ๋ ๋งคํ
Sonamu๋ snake_case ์ปฌ๋ผ๋ช
์ ์ฌ์ฉํ๋ฏ๋ก, better-auth์ camelCase ํ๋๋ช
์ด ์๋์ผ๋ก ๋งคํ๋ฉ๋๋ค:
// 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"
}
๋ก๊ทธ์์
ํ์ฌ ์ธ์
GET /api/auth/get-session
์์
๋ก๊ทธ์ธ (Google)
GET /api/auth/sign-in/social?provider=google
Context์์ ์ฌ์ฉ์ ์ ๋ณด ์ ๊ทผ
์ธ์ฆ๋ ์์ฒญ์์๋ Context๋ฅผ ํตํด ์ฌ์ฉ์ ์ ๋ณด์ ์ ๊ทผํ ์ ์์ต๋๋ค.
import { api, getContext } from "sonamu";
export class MyModel {
@api()
static async myApi() {
const ctx = 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 ํ์
type User = {
id: string;
name: string;
email: string;
emailVerified: boolean;
image: string | null;
createdAt: Date;
updatedAt: Date;
};
Session ํ์
type Session = {
id: string;
expiresAt: Date;
token: string;
createdAt: Date;
updatedAt: Date;
ipAddress: string | null;
userAgent: string | null;
userId: string;
};
Guard๋ฅผ ์ด์ฉํ ์ ๊ทผ ์ ์ด
import { api } from "sonamu";
export class AdminModel {
@api({ guards: ["admin"] })
static async adminOnly() {
// admin guard๊ฐ ํต๊ณผํ ๊ฒฝ์ฐ์๋ง ์คํ
return { message: "๊ด๋ฆฌ์ ์ ์ฉ API" };
}
}
guardHandler ์ค์ :
export default defineConfig({
server: {
auth: {
emailAndPassword: { enabled: true },
},
apiConfig: {
guardHandler: async (guard, request) => {
// Context์์ user ๊ฐ์ ธ์ค๊ธฐ
const { user } = 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์์ ์ฌ์ฉ
// 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;
}
// ๋ก๊ทธ์ธ ์ฑ๊ณต
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}>
Google๋ก ๋ก๊ทธ์ธ
</button>
);
}
ํ๋ฌ๊ทธ์ธ ์ค์
better-auth ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ๋ ค๋ฉด sonamu.config.ts์ auth.plugins ๋ฐฐ์ด์ ํ๋ฌ๊ทธ์ธ์ ์ถ๊ฐํฉ๋๋ค.
ํ๋ฌ๊ทธ์ธ์ ์ค์ ํ๊ธฐ ์ ์ ๋จผ์ pnpm sonamu better-auth --plugins=... ๋ช
๋ น์ผ๋ก ํ์ํ ์ํฐํฐ์ ํ๋๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
2๋จ๊ณ ์ธ์ฆ (2FA)
TOTP ๊ธฐ๋ฐ 2๋จ๊ณ ์ธ์ฆ์ ํ์ฑํํฉ๋๋ค:
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)์ ์ง์ํฉ๋๋ค:
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)
์ด๋ฉ์ผ ๋์ ์ฌ์ฉ์๋ช
์ผ๋ก ๋ก๊ทธ์ธํ ์ ์์ต๋๋ค:
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)
์ ํ๋ฒํธ ์ธ์ฆ์ ์ง์ํฉ๋๋ค:
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 ๊ธฐ๋ฐ ํจ์คํค ์ธ์ฆ์ ์ง์ํฉ๋๋ค:
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 - ํจ์คํค ์ธ์ฆ ๊ฒ์ฆ
Passkey ํ๋ฌ๊ทธ์ธ ์ฌ์ฉ ์ @better-auth/passkey ํจํค์ง๊ฐ ํ์ํฉ๋๋ค.
SSO ํ๋ฌ๊ทธ์ธ
์ธ๋ถ IdP(OIDC, SAML)๋ฅผ ํตํ SSO ๋ก๊ทธ์ธ์ ์ง์ํฉ๋๋ค:
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 ์ค์ ํฌํจ)
SSO ํ๋ฌ๊ทธ์ธ ์ฌ์ฉ ์ @better-auth/sso ํจํค์ง๊ฐ ํ์ํฉ๋๋ค.
API ํค ํ๋ฌ๊ทธ์ธ (API Key)
API ํค ๊ธฐ๋ฐ ์ธ์ฆ์ ์ง์ํฉ๋๋ค:
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 ํค ๊ด๋ฆฌ๋ฅผ ์ง์ํฉ๋๋ค:
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)
์กฐ์ง, ๋ฉค๋ฒ, ์ด๋, ํ ๊ด๋ฆฌ๋ฅผ ์ง์ํฉ๋๋ค:
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)
์ต๋ช
์ฌ์ฉ์ ์ธ์ฆ์ ์ง์ํฉ๋๋ค. ํ์๊ฐ์
์์ด ์์ ์ฌ์ฉ์๋ฅผ ์์ฑํ ์ ์์ต๋๋ค:
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 - ์ต๋ช
๊ณ์ ์ ์ ์ ๊ณ์ ์ผ๋ก ์ฐ๊ฒฐ
์ฌ๋ฌ ํ๋ฌ๊ทธ์ธ ํจ๊ป ์ฌ์ฉ
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 }),
],
},
},
});
๊ฐ ํ๋ฌ๊ทธ์ธ์ ์คํค๋ง(*_SCHEMA)๋ Sonamu์ snake_case ์ปฌ๋ผ๋ช
๊ณผ better-auth์ camelCase ํ๋๋ช
์ ๋งคํํ๋ ์ญํ ์ ํฉ๋๋ค. ๋ฐ๋์ ํด๋น ํ๋ฌ๊ทธ์ธ๊ณผ ํจ๊ป ์ ๋ฌํด์ผ ํฉ๋๋ค.
์ค์ ์์
๊ธฐ๋ณธ ์ค์
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("๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค");
}
},
},
},
});
์์
๋ก๊ทธ์ธ + ์ด๋ฉ์ผ ์ธ์ฆ
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. ์ํฐํฐ ์์ฑ ํ์
# auth ์ค์ ์ ์ ๋จผ์ ์ํฐํฐ ์์ฑ
pnpm sonamu better-auth
# ๋ง์ด๊ทธ๋ ์ด์
์คํ
pnpm sonamu migrate run
2. ํ๊ฒฝ ๋ณ์ ์ค์
# .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 ์ค์
ํด๋ผ์ด์ธํธ๊ฐ ๋ค๋ฅธ ๋๋ฉ์ธ์์ ์คํ๋๋ ๊ฒฝ์ฐ:
export default defineConfig({
server: {
plugins: {
cors: {
origin: ["http://localhost:3000"],
credentials: true,
},
},
auth: {
emailAndPassword: { enabled: true },
},
},
});
4. ๊ธฐ์กด User ์ํฐํฐ์์ ํธํ์ฑ
์ด๋ฏธ User ์ํฐํฐ๊ฐ ์๋ ๊ฒฝ์ฐ, pnpm sonamu better-auth ์คํ ์ ๋๋ฝ๋ ํ๋๋ง ์ถ๊ฐ๋ฉ๋๋ค. ๊ธฐ์กด ๋ฐ์ดํฐ๋ ์ ์ง๋ฉ๋๋ค.
๋ค์ ๋จ๊ณ
์ธ์ฆ ์ค์ ์ ์๋ฃํ๋ค๋ฉด:
- Context - Context์์ ์ฌ์ฉ์ ์ ๋ณด ์ ๊ทผ
- Guards - API ์ ๊ทผ ์ ์ด