@kood/claude-code 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/.claude/agents/code-reviewer.md +16 -1
- package/templates/.claude/agents/dependency-manager.md +16 -1
- package/templates/.claude/agents/deployment-validator.md +16 -1
- package/templates/.claude/agents/git-operator.md +16 -1
- package/templates/.claude/agents/implementation-executor.md +16 -1
- package/templates/.claude/agents/lint-fixer.md +16 -1
- package/templates/.claude/agents/refactor-advisor.md +16 -1
- package/templates/.claude/commands/agent-creator.md +16 -1
- package/templates/.claude/commands/bug-fix.md +16 -1
- package/templates/.claude/commands/command-creator.md +17 -1
- package/templates/.claude/commands/docs-creator.md +17 -1
- package/templates/.claude/commands/docs-refactor.md +17 -1
- package/templates/.claude/commands/execute.md +17 -1
- package/templates/.claude/commands/git-all.md +16 -1
- package/templates/.claude/commands/git-session.md +17 -1
- package/templates/.claude/commands/git.md +17 -1
- package/templates/.claude/commands/lint-fix.md +17 -1
- package/templates/.claude/commands/lint-init.md +17 -1
- package/templates/.claude/commands/plan.md +17 -1
- package/templates/.claude/commands/prd.md +17 -1
- package/templates/.claude/commands/pre-deploy.md +17 -1
- package/templates/.claude/commands/refactor.md +17 -1
- package/templates/.claude/commands/version-update.md +17 -1
- package/templates/hono/CLAUDE.md +1 -0
- package/templates/nextjs/CLAUDE.md +12 -9
- package/templates/nextjs/docs/architecture.md +812 -0
- package/templates/npx/CLAUDE.md +1 -0
- package/templates/tanstack-start/CLAUDE.md +1 -0
- package/templates/tanstack-start/docs/library/better-auth/index.md +225 -185
- package/templates/tanstack-start/docs/library/prisma/index.md +1025 -41
- package/templates/tanstack-start/docs/library/t3-env/index.md +207 -40
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +878 -42
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +602 -54
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +1334 -33
- package/templates/tanstack-start/docs/library/zod/index.md +674 -31
package/templates/npx/CLAUDE.md
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
| Task | Required Actions |
|
|
49
49
|
|------|-----------------|
|
|
50
50
|
| **Before Starting** | Read relevant docs (Commander → commander, Files → fs-extra) |
|
|
51
|
+
| **Document Search** | Use serena mcp (document indexing/search, context length optimization) |
|
|
51
52
|
| **Console Output** | Use logger functions (info/success/error/warn) |
|
|
52
53
|
| **File Operations** | Async API (`fs-extra`), `path.join` for paths |
|
|
53
54
|
| **Error Handling** | try-catch + `process.exit(1)`, clear error messages to users |
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
| Task | Required Actions |
|
|
55
55
|
|------|-----------------|
|
|
56
56
|
| **Before Starting** | Read related docs (UI → design, API → tanstack-start, DB → prisma, Auth → better-auth) |
|
|
57
|
+
| **Document Search** | Use serena mcp (document indexing/search, context length optimization) |
|
|
57
58
|
| **Code Search** | Use ast-grep (function/component/pattern search) |
|
|
58
59
|
| **Complex Tasks** | Sequential Thinking MCP (5+ step tasks) |
|
|
59
60
|
| **Large Changes** | gemini-review (3+ file changes, architecture decisions) |
|
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
# Better Auth
|
|
2
2
|
|
|
3
|
-
> TypeScript Authentication
|
|
3
|
+
> v1.x | TypeScript Authentication Framework
|
|
4
|
+
|
|
5
|
+
<context>
|
|
6
|
+
|
|
7
|
+
**Purpose:** Framework-agnostic authentication and authorization for TypeScript
|
|
8
|
+
**Features:** Email/Password, Social OAuth, 2FA, Passkeys, Multi-session, SSO
|
|
9
|
+
|
|
10
|
+
</context>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<forbidden>
|
|
15
|
+
|
|
16
|
+
| 분류 | 금지 |
|
|
17
|
+
|------|------|
|
|
18
|
+
| **보안** | 하드코딩된 시크릿, HTTP 프로덕션 배포 |
|
|
19
|
+
| **클라이언트** | 직접 세션 조작, 토큰 localStorage 저장 |
|
|
20
|
+
| **서버** | `authClient` 사용 (서버는 `auth.api.*`) |
|
|
21
|
+
| **설정** | 절대 경로 하드코딩 (환경변수 사용) |
|
|
22
|
+
|
|
23
|
+
</forbidden>
|
|
4
24
|
|
|
5
25
|
---
|
|
6
26
|
|
|
7
|
-
<
|
|
27
|
+
<required>
|
|
28
|
+
|
|
29
|
+
| 분류 | 필수 |
|
|
30
|
+
|------|------|
|
|
31
|
+
| **환경변수** | `BETTER_AUTH_SECRET` (32+ chars), `BETTER_AUTH_URL` |
|
|
32
|
+
| **서버** | `auth.api.getSession()` 사용 |
|
|
33
|
+
| **클라이언트** | `authClient.*` 메서드 사용 |
|
|
34
|
+
| **프로덕션** | HTTPS, 환경별 `baseURL` 설정 |
|
|
35
|
+
|
|
36
|
+
</required>
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
<installation>
|
|
8
41
|
|
|
9
42
|
## Installation
|
|
10
43
|
|
|
@@ -12,6 +45,20 @@
|
|
|
12
45
|
npm install better-auth
|
|
13
46
|
```
|
|
14
47
|
|
|
48
|
+
## Environment Variables
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# .env
|
|
52
|
+
BETTER_AUTH_SECRET="$(openssl rand -base64 32)"
|
|
53
|
+
BETTER_AUTH_URL="http://localhost:3000"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
</installation>
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
<setup>
|
|
61
|
+
|
|
15
62
|
## Minimal Setup
|
|
16
63
|
|
|
17
64
|
```typescript
|
|
@@ -43,35 +90,71 @@ export const GET = async ({ request }: { request: Request }) => auth.handler(req
|
|
|
43
90
|
export const POST = async ({ request }: { request: Request }) => auth.handler(request)
|
|
44
91
|
```
|
|
45
92
|
|
|
46
|
-
|
|
93
|
+
## Database Adapters
|
|
94
|
+
|
|
95
|
+
| Adapter | Import | Provider |
|
|
96
|
+
|---------|--------|----------|
|
|
97
|
+
| **Prisma** | `better-auth/adapters/prisma` | `postgresql`, `mysql`, `sqlite` |
|
|
98
|
+
| **Drizzle** | `better-auth/adapters/drizzle` | `pg`, `mysql2`, `better-sqlite3` |
|
|
99
|
+
| **Kysely** | `better-auth/adapters/kysely` | dialect 기반 |
|
|
100
|
+
|
|
101
|
+
## Config Options
|
|
102
|
+
|
|
103
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
104
|
+
|------|------|--------|------|
|
|
105
|
+
| `database` | `Adapter` | 필수 | DB 어댑터 |
|
|
106
|
+
| `baseURL` | `string` | `http://localhost:3000` | 앱 URL |
|
|
107
|
+
| `basePath` | `string` | `/api/auth` | Auth 경로 |
|
|
108
|
+
| `secret` | `string` | 환경변수 | JWT 시크릿 |
|
|
109
|
+
| `trustedOrigins` | `string[]` | `[]` | CORS 허용 오리진 |
|
|
110
|
+
|
|
111
|
+
</setup>
|
|
47
112
|
|
|
48
113
|
---
|
|
49
114
|
|
|
50
|
-
<
|
|
115
|
+
<auth_methods>
|
|
51
116
|
|
|
52
|
-
##
|
|
117
|
+
## Email & Password
|
|
53
118
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
119
|
+
```typescript
|
|
120
|
+
// ✅ 서버 설정
|
|
121
|
+
export const auth = betterAuth({
|
|
122
|
+
emailAndPassword: {
|
|
123
|
+
enabled: true,
|
|
124
|
+
requireEmailVerification: false,
|
|
125
|
+
sendResetPassword: async ({ user, url }) => {
|
|
126
|
+
await sendEmail({
|
|
127
|
+
to: user.email,
|
|
128
|
+
subject: 'Reset Password',
|
|
129
|
+
html: `<a href="${url}">Reset</a>`,
|
|
130
|
+
})
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
})
|
|
59
134
|
|
|
60
|
-
|
|
135
|
+
// ✅ 회원가입
|
|
136
|
+
await authClient.signUp.email({
|
|
137
|
+
email: 'user@example.com',
|
|
138
|
+
password: 'password123',
|
|
139
|
+
name: 'User Name',
|
|
140
|
+
})
|
|
61
141
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
| `secret` | `string` | Environment variable | JWT secret |
|
|
68
|
-
| `trustedOrigins` | `string[]` | `[]` | CORS allowed origins |
|
|
142
|
+
// ✅ 로그인
|
|
143
|
+
await authClient.signIn.email({
|
|
144
|
+
email: 'user@example.com',
|
|
145
|
+
password: 'password123',
|
|
146
|
+
})
|
|
69
147
|
|
|
70
|
-
|
|
148
|
+
// ✅ 비밀번호 재설정
|
|
149
|
+
await authClient.forgetPassword({ email: 'user@example.com' })
|
|
150
|
+
await authClient.resetPassword({ token, password: 'newpassword' })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Social OAuth
|
|
71
154
|
|
|
72
155
|
```typescript
|
|
156
|
+
// ✅ 서버 설정
|
|
73
157
|
export const auth = betterAuth({
|
|
74
|
-
database: prismaAdapter(prisma),
|
|
75
158
|
socialProviders: {
|
|
76
159
|
google: {
|
|
77
160
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
@@ -83,23 +166,70 @@ export const auth = betterAuth({
|
|
|
83
166
|
},
|
|
84
167
|
},
|
|
85
168
|
})
|
|
169
|
+
|
|
170
|
+
// ✅ 클라이언트 로그인
|
|
171
|
+
await authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' })
|
|
172
|
+
await authClient.signIn.social({ provider: 'github', callbackURL: '/dashboard' })
|
|
86
173
|
```
|
|
87
174
|
|
|
88
|
-
##
|
|
175
|
+
## Two-Factor Authentication
|
|
89
176
|
|
|
90
177
|
```typescript
|
|
178
|
+
// ✅ 서버 플러그인
|
|
179
|
+
import { twoFactor } from 'better-auth/plugins'
|
|
180
|
+
|
|
91
181
|
export const auth = betterAuth({
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
182
|
+
plugins: [
|
|
183
|
+
twoFactor({
|
|
184
|
+
issuer: 'My App',
|
|
185
|
+
otpOptions: {
|
|
186
|
+
async sendOTP({ user, otp }) {
|
|
187
|
+
await sendEmail({
|
|
188
|
+
to: user.email,
|
|
189
|
+
subject: 'Your OTP Code',
|
|
190
|
+
html: `Your code: ${otp}`,
|
|
191
|
+
})
|
|
192
|
+
},
|
|
193
|
+
period: 300, // 5분
|
|
194
|
+
length: 6, // 6자리
|
|
195
|
+
},
|
|
196
|
+
backupCodeLength: 10,
|
|
197
|
+
backupCodeCount: 10,
|
|
198
|
+
}),
|
|
199
|
+
],
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// ✅ 클라이언트 플러그인
|
|
203
|
+
import { twoFactorClient } from 'better-auth/client/plugins'
|
|
204
|
+
|
|
205
|
+
export const authClient = createAuthClient({
|
|
206
|
+
plugins: [
|
|
207
|
+
twoFactorClient({
|
|
208
|
+
twoFactorPage: '/two-factor',
|
|
209
|
+
}),
|
|
210
|
+
],
|
|
99
211
|
})
|
|
212
|
+
|
|
213
|
+
// ✅ TOTP 활성화
|
|
214
|
+
const { data } = await authClient.twoFactor.enable({ password: 'current-password' })
|
|
215
|
+
// → { totpURI: 'otpauth://...', backupCodes: ['ABCD-1234', ...] }
|
|
216
|
+
|
|
217
|
+
// ✅ TOTP 검증
|
|
218
|
+
await authClient.twoFactor.verifyTotp({ code: '123456' })
|
|
219
|
+
|
|
220
|
+
// ✅ OTP 전송/검증
|
|
221
|
+
await authClient.twoFactor.sendOtp()
|
|
222
|
+
await authClient.twoFactor.verifyOtp({ code: '123456' })
|
|
223
|
+
|
|
224
|
+
// ✅ 백업 코드
|
|
225
|
+
await authClient.twoFactor.useBackupCode({ code: 'ABCD-1234' })
|
|
226
|
+
await authClient.twoFactor.regenerateBackupCodes({ password: 'current-password' })
|
|
227
|
+
|
|
228
|
+
// ✅ 2FA 비활성화
|
|
229
|
+
await authClient.twoFactor.disable({ password: 'current-password' })
|
|
100
230
|
```
|
|
101
231
|
|
|
102
|
-
</
|
|
232
|
+
</auth_methods>
|
|
103
233
|
|
|
104
234
|
---
|
|
105
235
|
|
|
@@ -107,22 +237,22 @@ export const auth = betterAuth({
|
|
|
107
237
|
|
|
108
238
|
## Session Config
|
|
109
239
|
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
| `expiresIn` | `number` | `604800` (7
|
|
113
|
-
| `updateAge` | `number` | `86400` (1
|
|
114
|
-
| `cookieCache.enabled` | `boolean` | `true` |
|
|
115
|
-
| `cookieCache.maxAge` | `number` | `300` (5
|
|
116
|
-
| `cookieCache.strategy` | `'compact' \| 'jwt' \| 'jwe'` | `'compact'` |
|
|
240
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
241
|
+
|------|------|--------|------|
|
|
242
|
+
| `expiresIn` | `number` | `604800` (7일) | 세션 만료 (초) |
|
|
243
|
+
| `updateAge` | `number` | `86400` (1일) | 세션 갱신 주기 (초) |
|
|
244
|
+
| `cookieCache.enabled` | `boolean` | `true` | 쿠키 캐시 활성화 |
|
|
245
|
+
| `cookieCache.maxAge` | `number` | `300` (5분) | 캐시 유효 시간 (초) |
|
|
246
|
+
| `cookieCache.strategy` | `'compact' \| 'jwt' \| 'jwe'` | `'compact'` | 캐시 전략 |
|
|
117
247
|
|
|
118
248
|
```typescript
|
|
119
249
|
export const auth = betterAuth({
|
|
120
250
|
session: {
|
|
121
|
-
expiresIn: 604800,
|
|
122
|
-
updateAge: 86400,
|
|
251
|
+
expiresIn: 604800,
|
|
252
|
+
updateAge: 86400,
|
|
123
253
|
cookieCache: {
|
|
124
254
|
enabled: true,
|
|
125
|
-
maxAge: 300,
|
|
255
|
+
maxAge: 300,
|
|
126
256
|
strategy: 'compact',
|
|
127
257
|
},
|
|
128
258
|
},
|
|
@@ -131,22 +261,27 @@ export const auth = betterAuth({
|
|
|
131
261
|
|
|
132
262
|
## Session Methods
|
|
133
263
|
|
|
264
|
+
| 환경 | 메서드 |
|
|
265
|
+
|------|--------|
|
|
266
|
+
| **클라이언트** | `authClient.getSession()` |
|
|
267
|
+
| **서버** | `auth.api.getSession({ headers })` |
|
|
268
|
+
|
|
134
269
|
```typescript
|
|
135
|
-
// ✅
|
|
270
|
+
// ✅ 클라이언트
|
|
136
271
|
const session = await authClient.getSession()
|
|
137
272
|
const session = await authClient.getSession({ query: { disableCookieCache: true } })
|
|
138
273
|
|
|
139
|
-
// ✅
|
|
274
|
+
// ✅ 서버 (TanStack Start)
|
|
140
275
|
export const getSession = createServerFn({ method: 'GET' })
|
|
141
276
|
.handler(async ({ request }) => {
|
|
142
277
|
const session = await auth.api.getSession({ headers: request.headers })
|
|
143
278
|
return session
|
|
144
279
|
})
|
|
145
280
|
|
|
146
|
-
// ✅
|
|
281
|
+
// ✅ 세션 업데이트
|
|
147
282
|
await authClient.updateUser({ name: 'New Name' })
|
|
148
283
|
|
|
149
|
-
// ✅
|
|
284
|
+
// ✅ 로그아웃
|
|
150
285
|
await authClient.signOut()
|
|
151
286
|
```
|
|
152
287
|
|
|
@@ -183,148 +318,39 @@ export const auth = betterAuth({
|
|
|
183
318
|
|
|
184
319
|
---
|
|
185
320
|
|
|
186
|
-
<auth_methods>
|
|
187
|
-
|
|
188
|
-
## Email/Password
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
// ✅ Sign up
|
|
192
|
-
await authClient.signUp.email({
|
|
193
|
-
email: 'user@example.com',
|
|
194
|
-
password: 'password123',
|
|
195
|
-
name: 'User Name',
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
// ✅ Sign in
|
|
199
|
-
await authClient.signIn.email({
|
|
200
|
-
email: 'user@example.com',
|
|
201
|
-
password: 'password123',
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
// ✅ Request password reset
|
|
205
|
-
await authClient.forgetPassword({ email: 'user@example.com' })
|
|
206
|
-
|
|
207
|
-
// ✅ Reset password
|
|
208
|
-
await authClient.resetPassword({ token, password: 'newpassword' })
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Social Login
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// ✅ Social sign in
|
|
215
|
-
await authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' })
|
|
216
|
-
await authClient.signIn.social({ provider: 'github', callbackURL: '/dashboard' })
|
|
217
|
-
|
|
218
|
-
// ✅ SSO
|
|
219
|
-
await authClient.signIn.sso({ providerId: 'provider-id', callbackURL: '/dashboard' })
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## Two-Factor Authentication
|
|
223
|
-
|
|
224
|
-
### Server Setup
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
import { twoFactor } from 'better-auth/plugins'
|
|
228
|
-
|
|
229
|
-
export const auth = betterAuth({
|
|
230
|
-
plugins: [
|
|
231
|
-
twoFactor({
|
|
232
|
-
issuer: 'My App',
|
|
233
|
-
otpOptions: {
|
|
234
|
-
async sendOTP({ user, otp }) {
|
|
235
|
-
await sendEmail({
|
|
236
|
-
to: user.email,
|
|
237
|
-
subject: 'Your OTP Code',
|
|
238
|
-
html: `Your code: ${otp}`,
|
|
239
|
-
})
|
|
240
|
-
},
|
|
241
|
-
period: 300, // 5분
|
|
242
|
-
length: 6, // 6자리
|
|
243
|
-
},
|
|
244
|
-
backupCodeLength: 10,
|
|
245
|
-
backupCodeCount: 10,
|
|
246
|
-
}),
|
|
247
|
-
],
|
|
248
|
-
})
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Client Setup
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
import { twoFactorClient } from 'better-auth/client/plugins'
|
|
255
|
-
|
|
256
|
-
export const authClient = createAuthClient({
|
|
257
|
-
plugins: [
|
|
258
|
-
twoFactorClient({
|
|
259
|
-
twoFactorPage: '/two-factor',
|
|
260
|
-
}),
|
|
261
|
-
],
|
|
262
|
-
})
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### 2FA Usage
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
// ✅ Enable TOTP
|
|
269
|
-
const { data } = await authClient.twoFactor.enable({ password: 'current-password' })
|
|
270
|
-
// data: { totpURI: 'otpauth://...', backupCodes: ['ABCD-1234', ...] }
|
|
271
|
-
|
|
272
|
-
// ✅ Verify TOTP
|
|
273
|
-
await authClient.twoFactor.verifyTotp({ code: '123456' })
|
|
274
|
-
|
|
275
|
-
// ✅ Send OTP
|
|
276
|
-
await authClient.twoFactor.sendOtp()
|
|
277
|
-
|
|
278
|
-
// ✅ Verify OTP
|
|
279
|
-
await authClient.twoFactor.verifyOtp({ code: '123456' })
|
|
280
|
-
|
|
281
|
-
// ✅ Use backup code
|
|
282
|
-
await authClient.twoFactor.useBackupCode({ code: 'ABCD-1234' })
|
|
283
|
-
|
|
284
|
-
// ✅ Regenerate backup codes
|
|
285
|
-
const { data } = await authClient.twoFactor.regenerateBackupCodes({ password: 'current-password' })
|
|
286
|
-
|
|
287
|
-
// ✅ Disable 2FA
|
|
288
|
-
await authClient.twoFactor.disable({ password: 'current-password' })
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
</auth_methods>
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
321
|
<plugins>
|
|
296
322
|
|
|
297
323
|
## Plugin System
|
|
298
324
|
|
|
299
|
-
| Plugin |
|
|
300
|
-
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
| `
|
|
304
|
-
| `captcha` |
|
|
325
|
+
| Plugin | 기능 |
|
|
326
|
+
|--------|------|
|
|
327
|
+
| `twoFactor` | TOTP, OTP, 백업 코드 |
|
|
328
|
+
| `multiSession` | 다중 세션 관리 |
|
|
329
|
+
| `customSession` | 세션 필드 확장 |
|
|
330
|
+
| `captcha` | reCAPTCHA 검증 |
|
|
305
331
|
|
|
306
332
|
## Multi-Session
|
|
307
333
|
|
|
308
334
|
```typescript
|
|
309
|
-
// ✅
|
|
335
|
+
// ✅ 서버
|
|
310
336
|
import { multiSession } from 'better-auth/plugins'
|
|
311
337
|
|
|
312
338
|
export const auth = betterAuth({
|
|
313
339
|
plugins: [
|
|
314
340
|
multiSession({
|
|
315
|
-
maximumSessions: 5,
|
|
341
|
+
maximumSessions: 5,
|
|
316
342
|
}),
|
|
317
343
|
],
|
|
318
344
|
})
|
|
319
345
|
|
|
320
|
-
// ✅
|
|
346
|
+
// ✅ 클라이언트
|
|
321
347
|
import { multiSessionClient } from 'better-auth/client/plugins'
|
|
322
348
|
|
|
323
349
|
export const authClient = createAuthClient({
|
|
324
350
|
plugins: [multiSessionClient()],
|
|
325
351
|
})
|
|
326
352
|
|
|
327
|
-
//
|
|
353
|
+
// ✅ 사용
|
|
328
354
|
const sessions = await authClient.multiSession.listSessions()
|
|
329
355
|
await authClient.multiSession.revokeSession({ sessionId: 'session-id' })
|
|
330
356
|
await authClient.multiSession.revokeOtherSessions()
|
|
@@ -333,7 +359,7 @@ await authClient.multiSession.revokeOtherSessions()
|
|
|
333
359
|
## CAPTCHA
|
|
334
360
|
|
|
335
361
|
```typescript
|
|
336
|
-
// ✅
|
|
362
|
+
// ✅ 서버
|
|
337
363
|
import { captcha } from 'better-auth/plugins'
|
|
338
364
|
|
|
339
365
|
export const auth = betterAuth({
|
|
@@ -347,7 +373,7 @@ export const auth = betterAuth({
|
|
|
347
373
|
],
|
|
348
374
|
})
|
|
349
375
|
|
|
350
|
-
// ✅
|
|
376
|
+
// ✅ 클라이언트
|
|
351
377
|
import { captchaClient } from 'better-auth/client/plugins'
|
|
352
378
|
|
|
353
379
|
export const authClient = createAuthClient({
|
|
@@ -368,10 +394,24 @@ export const authClient = createAuthClient({
|
|
|
368
394
|
## SIWE (Ethereum)
|
|
369
395
|
|
|
370
396
|
```typescript
|
|
371
|
-
//
|
|
372
|
-
|
|
397
|
+
// ✅ 서버
|
|
398
|
+
import { siwe } from 'better-auth/plugins'
|
|
399
|
+
|
|
400
|
+
export const auth = betterAuth({
|
|
401
|
+
plugins: [
|
|
402
|
+
siwe({
|
|
403
|
+
domain: 'example.com',
|
|
404
|
+
uri: 'https://example.com',
|
|
405
|
+
}),
|
|
406
|
+
],
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
// ✅ 클라이언트
|
|
373
410
|
const { data } = await authClient.siwe.getNonce()
|
|
374
|
-
const message = await authClient.siwe.prepareMessage({
|
|
411
|
+
const message = await authClient.siwe.prepareMessage({
|
|
412
|
+
address: '0x...',
|
|
413
|
+
nonce: data.nonce,
|
|
414
|
+
})
|
|
375
415
|
const signature = await signer.signMessage(message)
|
|
376
416
|
await authClient.siwe.signIn({ message, signature })
|
|
377
417
|
```
|
|
@@ -379,7 +419,7 @@ await authClient.siwe.signIn({ message, signature })
|
|
|
379
419
|
## Stateless Mode
|
|
380
420
|
|
|
381
421
|
```typescript
|
|
382
|
-
// ✅
|
|
422
|
+
// ✅ DB 없이 소셜 로그인만
|
|
383
423
|
export const auth = betterAuth({
|
|
384
424
|
socialProviders: {
|
|
385
425
|
google: {
|
|
@@ -422,8 +462,8 @@ export const auth = betterAuth({
|
|
|
422
462
|
|
|
423
463
|
## TanStack Start Integration
|
|
424
464
|
|
|
425
|
-
|
|
|
426
|
-
|
|
465
|
+
| 패턴 | 파일 |
|
|
466
|
+
|------|------|
|
|
427
467
|
| API Handler | `src/routes/api/auth/$.ts` |
|
|
428
468
|
| Server Function | `createServerFn` + `auth.api.getSession` |
|
|
429
469
|
| Middleware | `auth.api.getSession` + redirect |
|
|
@@ -431,7 +471,7 @@ export const auth = betterAuth({
|
|
|
431
471
|
### Server Function Pattern
|
|
432
472
|
|
|
433
473
|
```typescript
|
|
434
|
-
// ✅
|
|
474
|
+
// ✅ 인증 체크
|
|
435
475
|
export const requireAuth = createServerFn({ method: 'GET' })
|
|
436
476
|
.handler(async ({ request }) => {
|
|
437
477
|
const session = await auth.api.getSession({ headers: request.headers })
|
|
@@ -441,7 +481,7 @@ export const requireAuth = createServerFn({ method: 'GET' })
|
|
|
441
481
|
return session.user
|
|
442
482
|
})
|
|
443
483
|
|
|
444
|
-
// ✅
|
|
484
|
+
// ✅ 보호된 데이터 조회
|
|
445
485
|
export const getProtectedData = createServerFn({ method: 'GET' })
|
|
446
486
|
.handler(async ({ request }) => {
|
|
447
487
|
const user = await requireAuth()
|
|
@@ -465,23 +505,23 @@ export const Route = createFileRoute('/dashboard')({
|
|
|
465
505
|
|
|
466
506
|
## Common Patterns
|
|
467
507
|
|
|
468
|
-
|
|
|
469
|
-
|
|
470
|
-
|
|
|
471
|
-
|
|
|
472
|
-
|
|
|
473
|
-
|
|
|
474
|
-
|
|
|
475
|
-
|
|
|
508
|
+
| 작업 | 패턴 |
|
|
509
|
+
|------|------|
|
|
510
|
+
| 로그인 | `authClient.signIn.email({ email, password })` |
|
|
511
|
+
| 회원가입 | `authClient.signUp.email({ email, password, name })` |
|
|
512
|
+
| 로그아웃 | `authClient.signOut()` |
|
|
513
|
+
| 세션 조회 | `authClient.getSession()` |
|
|
514
|
+
| 사용자 업데이트 | `authClient.updateUser({ name })` |
|
|
515
|
+
| 비밀번호 재설정 | `authClient.forgetPassword({ email })` → `authClient.resetPassword({ token, password })` |
|
|
476
516
|
|
|
477
517
|
## Do's & Don'ts
|
|
478
518
|
|
|
479
519
|
| ✅ Do | ❌ Don't |
|
|
480
520
|
|-------|----------|
|
|
481
|
-
|
|
|
482
|
-
|
|
|
483
|
-
|
|
|
484
|
-
| HTTPS
|
|
485
|
-
|
|
|
521
|
+
| `auth.api.getSession()` 서버에서 사용 | 클라이언트에서 직접 세션 조작 |
|
|
522
|
+
| `authClient` 클라이언트에서 사용 | 하드코딩된 시크릿 |
|
|
523
|
+
| 환경변수로 시크릿 관리 | 세션 토큰 localStorage 저장 |
|
|
524
|
+
| HTTPS 프로덕션 필수 | HTTP 프로덕션 배포 |
|
|
525
|
+
| `baseURL` 환경별 설정 | 절대 경로 하드코딩 |
|
|
486
526
|
|
|
487
527
|
</patterns>
|