@kood/claude-code 0.1.5 → 0.1.7
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 +21 -243
- package/package.json +1 -1
- package/templates/hono/CLAUDE.md +10 -6
- package/templates/hono/docs/deployment/index.md +5 -0
- package/templates/hono/docs/library/hono/index.md +6 -0
- package/templates/hono/docs/library/prisma/index.md +3 -0
- package/templates/npx/CLAUDE.md +8 -2
- package/templates/tanstack-start/CLAUDE.md +105 -259
- package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
- package/templates/tanstack-start/docs/deployment/index.md +57 -286
- package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
- package/templates/tanstack-start/docs/deployment/railway.md +40 -409
- package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
- package/templates/tanstack-start/docs/design/accessibility.md +56 -326
- package/templates/tanstack-start/docs/design/color.md +37 -179
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +24 -87
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/spacing.md +57 -276
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/design/typography.md +40 -284
- package/templates/tanstack-start/docs/guides/best-practices.md +3 -8
- package/templates/tanstack-start/docs/guides/env-setup.md +3 -3
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
- package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
- package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
- package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
- package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
- package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
- package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
- package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
- package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
- package/templates/tanstack-start/docs/library/prisma/setup.md +13 -113
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +12 -99
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +11 -73
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +41 -172
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/basic-types.md +33 -145
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +22 -150
- package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
- package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
- package/templates/hono/docs/commands/git.md +0 -145
- package/templates/hono/docs/mcp/context7.md +0 -106
- package/templates/hono/docs/mcp/index.md +0 -176
- package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
- package/templates/hono/docs/mcp/serena.md +0 -269
- package/templates/hono/docs/mcp/sgrep.md +0 -105
- package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
- package/templates/npx/docs/commands/git.md +0 -145
- package/templates/npx/docs/mcp/index.md +0 -60
- package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
- package/templates/tanstack-start/docs/commands/git.md +0 -145
- package/templates/tanstack-start/docs/mcp/context7.md +0 -204
- package/templates/tanstack-start/docs/mcp/index.md +0 -177
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
- package/templates/tanstack-start/docs/mcp/serena.md +0 -269
- package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
|
@@ -1,138 +1,55 @@
|
|
|
1
1
|
# Better Auth - 고급 기능
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## CAPTCHA 보호
|
|
3
|
+
## CAPTCHA
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
|
-
|
|
6
|
+
// 서버
|
|
9
7
|
import { captcha } from 'better-auth/plugins'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
siteKey: process.env.RECAPTCHA_SITE_KEY!,
|
|
16
|
-
secretKey: process.env.RECAPTCHA_SECRET_KEY!,
|
|
17
|
-
protectEndpoints: ['/sign-up/email', '/sign-in/email'],
|
|
18
|
-
}),
|
|
19
|
-
],
|
|
20
|
-
})
|
|
8
|
+
plugins: [captcha({
|
|
9
|
+
provider: 'recaptcha',
|
|
10
|
+
siteKey: SITE_KEY, secretKey: SECRET_KEY,
|
|
11
|
+
protectEndpoints: ['/sign-up/email', '/sign-in/email'],
|
|
12
|
+
})]
|
|
21
13
|
|
|
22
14
|
// 클라이언트
|
|
23
15
|
import { captchaClient } from 'better-auth/client/plugins'
|
|
24
|
-
|
|
25
|
-
const authClient = createAuthClient({
|
|
26
|
-
plugins: [
|
|
27
|
-
captchaClient({
|
|
28
|
-
siteKey: process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!,
|
|
29
|
-
}),
|
|
30
|
-
],
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
// CAPTCHA는 보호된 요청에 자동 포함됨
|
|
34
|
-
await authClient.signUp.email({
|
|
35
|
-
email: 'user@example.com',
|
|
36
|
-
password: 'SecurePassword123!',
|
|
37
|
-
})
|
|
16
|
+
plugins: [captchaClient({ siteKey: SITE_KEY })]
|
|
38
17
|
```
|
|
39
18
|
|
|
40
|
-
## SSO
|
|
19
|
+
## SSO
|
|
41
20
|
|
|
42
21
|
```typescript
|
|
43
|
-
|
|
44
|
-
const res = await authClient.signIn.sso({
|
|
45
|
-
providerId: 'example-provider-id',
|
|
46
|
-
callbackURL: '/dashboard',
|
|
47
|
-
})
|
|
22
|
+
await authClient.signIn.sso({ providerId: 'provider-id', callbackURL: '/dashboard' })
|
|
48
23
|
```
|
|
49
24
|
|
|
50
|
-
## SIWE (
|
|
51
|
-
|
|
52
|
-
### 서버 설정
|
|
25
|
+
## SIWE (Ethereum)
|
|
53
26
|
|
|
54
27
|
```typescript
|
|
55
|
-
|
|
28
|
+
// 서버
|
|
56
29
|
import { siwe } from 'better-auth/plugins'
|
|
30
|
+
plugins: [siwe({ domain: 'example.com', uri: 'https://example.com' })]
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
plugins: [
|
|
60
|
-
siwe({
|
|
61
|
-
domain: 'example.com',
|
|
62
|
-
uri: 'https://example.com',
|
|
63
|
-
statement: 'Sign in with your Ethereum account',
|
|
64
|
-
}),
|
|
65
|
-
],
|
|
66
|
-
})
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 클라이언트 사용
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import { siweClient } from 'better-auth/client/plugins'
|
|
73
|
-
import { ethers } from 'ethers'
|
|
74
|
-
|
|
75
|
-
const authClient = createAuthClient({
|
|
76
|
-
plugins: [siweClient()],
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
// Nonce 가져오기
|
|
32
|
+
// 클라이언트
|
|
80
33
|
const { data: nonce } = await authClient.siwe.getNonce()
|
|
81
|
-
|
|
82
|
-
// 메시지 생성 및 서명
|
|
83
|
-
const provider = new ethers.BrowserProvider(window.ethereum)
|
|
84
|
-
const signer = await provider.getSigner()
|
|
85
|
-
const address = await signer.getAddress()
|
|
86
|
-
|
|
87
|
-
const message = await authClient.siwe.prepareMessage({
|
|
88
|
-
address,
|
|
89
|
-
nonce: nonce.nonce,
|
|
90
|
-
})
|
|
91
|
-
|
|
34
|
+
const message = await authClient.siwe.prepareMessage({ address, nonce: nonce.nonce })
|
|
92
35
|
const signature = await signer.signMessage(message)
|
|
93
|
-
|
|
94
|
-
// 검증 및 로그인
|
|
95
|
-
await authClient.siwe.signIn({
|
|
96
|
-
message,
|
|
97
|
-
signature,
|
|
98
|
-
})
|
|
36
|
+
await authClient.siwe.signIn({ message, signature })
|
|
99
37
|
```
|
|
100
38
|
|
|
101
39
|
## Stateless 모드
|
|
102
40
|
|
|
103
|
-
데이터베이스 없이 사용:
|
|
104
|
-
|
|
105
41
|
```typescript
|
|
106
|
-
|
|
107
|
-
|
|
42
|
+
// DB 없이 소셜 로그인만
|
|
108
43
|
export const auth = betterAuth({
|
|
109
|
-
|
|
110
|
-
socialProviders: {
|
|
111
|
-
google: {
|
|
112
|
-
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
113
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
114
|
-
},
|
|
115
|
-
},
|
|
44
|
+
socialProviders: { google: { clientId, clientSecret } },
|
|
116
45
|
})
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Redis와 함께 하이브리드 모드
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
import { betterAuth } from 'better-auth'
|
|
123
|
-
import { redis } from './redis'
|
|
124
46
|
|
|
47
|
+
// Redis 하이브리드
|
|
125
48
|
export const auth = betterAuth({
|
|
126
49
|
secondaryStorage: {
|
|
127
|
-
get:
|
|
128
|
-
set:
|
|
129
|
-
delete:
|
|
130
|
-
},
|
|
131
|
-
session: {
|
|
132
|
-
cookieCache: {
|
|
133
|
-
maxAge: 5 * 60, // 5분
|
|
134
|
-
refreshCache: false,
|
|
135
|
-
},
|
|
50
|
+
get: (key) => redis.get(key),
|
|
51
|
+
set: (key, value, ttl) => redis.set(key, value, 'EX', ttl),
|
|
52
|
+
delete: (key) => redis.del(key),
|
|
136
53
|
},
|
|
137
54
|
})
|
|
138
55
|
```
|
|
@@ -1,83 +1,34 @@
|
|
|
1
1
|
# Better Auth
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> **Framework**: TypeScript Authentication Library
|
|
5
|
-
> **Source**: [Better Auth Documentation](https://www.better-auth.com)
|
|
3
|
+
> TypeScript Authentication Library
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
@setup.md
|
|
6
|
+
@session.md
|
|
7
|
+
@plugins.md
|
|
8
|
+
@2fa.md
|
|
9
|
+
@advanced.md
|
|
8
10
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
- [설치 및 설정](./setup.md) - 서버/클라이언트 설정, 소셜 로그인
|
|
12
|
-
- [세션 관리](./session.md) - 세션 설정, 쿠키 캐시
|
|
13
|
-
- [플러그인](./plugins.md) - Multi-Session, Custom Session
|
|
14
|
-
- [2단계 인증](./2fa.md) - TOTP, OTP, 백업 코드
|
|
15
|
-
- [고급 기능](./advanced.md) - CAPTCHA, SSO, SIWE, Stateless 모드
|
|
16
|
-
|
|
17
|
-
## 빠른 시작
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
yarn add better-auth
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### 기본 서버 설정
|
|
11
|
+
## Quick Reference
|
|
24
12
|
|
|
25
13
|
```typescript
|
|
26
|
-
//
|
|
14
|
+
// 서버
|
|
27
15
|
import { betterAuth } from 'better-auth'
|
|
28
16
|
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
29
|
-
import { prisma } from './prisma'
|
|
30
17
|
|
|
31
18
|
export const auth = betterAuth({
|
|
32
19
|
database: prismaAdapter(prisma),
|
|
33
|
-
emailAndPassword: {
|
|
34
|
-
enabled: true,
|
|
35
|
-
requireEmailVerification: false,
|
|
36
|
-
},
|
|
20
|
+
emailAndPassword: { enabled: true },
|
|
37
21
|
})
|
|
38
|
-
```
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
// lib/auth-client.ts
|
|
23
|
+
// 클라이언트
|
|
44
24
|
import { createAuthClient } from 'better-auth/react'
|
|
25
|
+
export const authClient = createAuthClient({})
|
|
45
26
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## 핵심 개념
|
|
27
|
+
// 로그인/가입
|
|
28
|
+
await authClient.signIn.email({ email, password })
|
|
29
|
+
await authClient.signUp.email({ email, password, name })
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
await authClient.signIn.email({
|
|
57
|
-
email: 'user@example.com',
|
|
58
|
-
password: 'password123',
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// 회원가입
|
|
62
|
-
await authClient.signUp.email({
|
|
63
|
-
email: 'user@example.com',
|
|
64
|
-
password: 'password123',
|
|
65
|
-
name: 'John Doe',
|
|
66
|
-
})
|
|
31
|
+
// 세션
|
|
32
|
+
const session = await authClient.getSession() // 클라이언트
|
|
33
|
+
const session = await auth.api.getSession({ headers }) // 서버
|
|
67
34
|
```
|
|
68
|
-
|
|
69
|
-
### 세션 조회
|
|
70
|
-
```typescript
|
|
71
|
-
// 클라이언트
|
|
72
|
-
const session = await authClient.getSession()
|
|
73
|
-
|
|
74
|
-
// 서버
|
|
75
|
-
const session = await auth.api.getSession({
|
|
76
|
-
headers: request.headers,
|
|
77
|
-
})
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## 참고 자료
|
|
81
|
-
|
|
82
|
-
- [Better Auth 공식 문서](https://www.better-auth.com)
|
|
83
|
-
- [Better Auth GitHub](https://github.com/better-auth/better-auth)
|
|
@@ -1,111 +1,34 @@
|
|
|
1
1
|
# Better Auth - 플러그인
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Multi-Session Plugin
|
|
6
|
-
|
|
7
|
-
여러 계정으로 동시 로그인을 지원합니다.
|
|
8
|
-
|
|
9
|
-
### 서버 설정
|
|
3
|
+
## Multi-Session
|
|
10
4
|
|
|
11
5
|
```typescript
|
|
12
|
-
|
|
6
|
+
// 서버
|
|
13
7
|
import { multiSession } from 'better-auth/plugins'
|
|
8
|
+
plugins: [multiSession({ maximumSessions: 5 })]
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
plugins: [
|
|
17
|
-
multiSession({
|
|
18
|
-
maximumSessions: 5, // 최대 동시 세션 수
|
|
19
|
-
}),
|
|
20
|
-
],
|
|
21
|
-
})
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### 클라이언트 사용
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import { createAuthClient } from 'better-auth/client'
|
|
10
|
+
// 클라이언트
|
|
28
11
|
import { multiSessionClient } from 'better-auth/client/plugins'
|
|
12
|
+
plugins: [multiSessionClient()]
|
|
29
13
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
// 활성 세션 목록 조회
|
|
35
|
-
const { data: sessions } = await authClient.multiSession.listSessions()
|
|
36
|
-
|
|
37
|
-
sessions?.forEach((session) => {
|
|
38
|
-
console.log(`Device: ${session.userAgent}, Last active: ${session.updatedAt}`)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// 특정 세션 취소
|
|
42
|
-
await authClient.multiSession.revokeSession({
|
|
43
|
-
sessionId: 'session_456',
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
// 다른 모든 세션 취소
|
|
14
|
+
// 사용
|
|
15
|
+
await authClient.multiSession.listSessions()
|
|
16
|
+
await authClient.multiSession.revokeSession({ sessionId })
|
|
47
17
|
await authClient.multiSession.revokeOtherSessions()
|
|
48
18
|
```
|
|
49
19
|
|
|
50
|
-
## Polar Checkout Plugin
|
|
51
|
-
|
|
52
|
-
결제 통합을 위한 플러그인입니다.
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
import { polar, checkout } from '@polar-sh/better-auth'
|
|
56
|
-
|
|
57
|
-
const auth = betterAuth({
|
|
58
|
-
plugins: [
|
|
59
|
-
polar({
|
|
60
|
-
use: [
|
|
61
|
-
checkout({
|
|
62
|
-
products: [{ productId: '123-456-789', slug: 'pro' }],
|
|
63
|
-
successUrl: '/success?checkout_id={CHECKOUT_ID}',
|
|
64
|
-
authenticatedUsersOnly: true,
|
|
65
|
-
}),
|
|
66
|
-
],
|
|
67
|
-
}),
|
|
68
|
-
],
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// 클라이언트
|
|
72
|
-
import { polarClient } from '@polar-sh/better-auth'
|
|
73
|
-
|
|
74
|
-
export const authClient = createAuthClient({
|
|
75
|
-
plugins: [polarClient()],
|
|
76
|
-
})
|
|
77
|
-
```
|
|
78
|
-
|
|
79
20
|
## 플러그인 조합
|
|
80
21
|
|
|
81
22
|
```typescript
|
|
82
|
-
import {
|
|
83
|
-
import {
|
|
84
|
-
multiSession,
|
|
85
|
-
customSession,
|
|
86
|
-
twoFactor,
|
|
87
|
-
captcha
|
|
88
|
-
} from 'better-auth/plugins'
|
|
23
|
+
import { multiSession, customSession, twoFactor, captcha } from 'better-auth/plugins'
|
|
89
24
|
|
|
90
25
|
export const auth = betterAuth({
|
|
91
26
|
database: prismaAdapter(prisma),
|
|
92
27
|
plugins: [
|
|
93
28
|
multiSession({ maximumSessions: 5 }),
|
|
94
|
-
customSession({
|
|
95
|
-
schema: {
|
|
96
|
-
session: {
|
|
97
|
-
fields: {
|
|
98
|
-
ipAddress: { type: 'string', required: false },
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
29
|
+
customSession({ schema: { session: { fields: { ipAddress: { type: 'string' } } } } }),
|
|
103
30
|
twoFactor({ issuer: 'My App' }),
|
|
104
|
-
captcha({
|
|
105
|
-
provider: 'recaptcha',
|
|
106
|
-
siteKey: process.env.RECAPTCHA_SITE_KEY!,
|
|
107
|
-
secretKey: process.env.RECAPTCHA_SECRET_KEY!,
|
|
108
|
-
}),
|
|
31
|
+
captcha({ provider: 'recaptcha', siteKey: SITE_KEY, secretKey: SECRET_KEY }),
|
|
109
32
|
],
|
|
110
33
|
})
|
|
111
34
|
```
|
|
@@ -1,127 +1,47 @@
|
|
|
1
1
|
# Better Auth - 세션 관리
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## 세션 설정
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { betterAuth } from 'better-auth'
|
|
9
|
-
|
|
10
|
-
export const auth = betterAuth({
|
|
11
|
-
session: {
|
|
12
|
-
modelName: 'sessions',
|
|
13
|
-
fields: {
|
|
14
|
-
userId: 'user_id',
|
|
15
|
-
},
|
|
16
|
-
expiresIn: 604800, // 7일 (초 단위)
|
|
17
|
-
updateAge: 86400, // 1일마다 갱신
|
|
18
|
-
additionalFields: {
|
|
19
|
-
customField: {
|
|
20
|
-
type: 'string',
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
})
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## 쿠키 캐시
|
|
28
|
-
|
|
29
|
-
세션 조회 성능 최적화를 위한 쿠키 캐시:
|
|
3
|
+
## 설정
|
|
30
4
|
|
|
31
5
|
```typescript
|
|
32
6
|
export const auth = betterAuth({
|
|
33
7
|
session: {
|
|
8
|
+
expiresIn: 604800, // 7일 (초)
|
|
9
|
+
updateAge: 86400, // 1일마다 갱신
|
|
34
10
|
cookieCache: {
|
|
35
11
|
enabled: true,
|
|
36
|
-
maxAge: 5 * 60,
|
|
37
|
-
strategy: 'compact',
|
|
38
|
-
refreshCache: true,
|
|
12
|
+
maxAge: 5 * 60, // 5분
|
|
13
|
+
strategy: 'compact', // 'compact' | 'jwt' | 'jwe'
|
|
39
14
|
},
|
|
40
15
|
},
|
|
41
16
|
})
|
|
42
17
|
```
|
|
43
18
|
|
|
44
|
-
## 세션
|
|
45
|
-
|
|
46
|
-
### 클라이언트
|
|
19
|
+
## 세션 조회
|
|
47
20
|
|
|
48
21
|
```typescript
|
|
49
|
-
//
|
|
22
|
+
// 클라이언트
|
|
50
23
|
const session = await authClient.getSession()
|
|
24
|
+
const session = await authClient.getSession({ query: { disableCookieCache: true } })
|
|
51
25
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
query: {
|
|
55
|
-
disableCookieCache: true,
|
|
56
|
-
},
|
|
57
|
-
})
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### 서버
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
await auth.api.getSession({
|
|
64
|
-
query: {
|
|
65
|
-
disableCookieCache: true,
|
|
66
|
-
},
|
|
67
|
-
headers: req.headers,
|
|
68
|
-
})
|
|
26
|
+
// 서버
|
|
27
|
+
await auth.api.getSession({ headers: req.headers })
|
|
69
28
|
```
|
|
70
29
|
|
|
71
30
|
## Custom Session Plugin
|
|
72
31
|
|
|
73
|
-
세션 스키마 확장 및 커스텀 로직 구현:
|
|
74
|
-
|
|
75
32
|
```typescript
|
|
76
|
-
import { betterAuth } from 'better-auth'
|
|
77
33
|
import { customSession } from 'better-auth/plugins'
|
|
78
34
|
|
|
79
35
|
export const auth = betterAuth({
|
|
80
36
|
plugins: [
|
|
81
37
|
customSession({
|
|
82
|
-
// 세션 스키마 확장
|
|
83
38
|
schema: {
|
|
84
|
-
session: {
|
|
85
|
-
fields: {
|
|
86
|
-
ipAddress: {
|
|
87
|
-
type: 'string',
|
|
88
|
-
required: false,
|
|
89
|
-
},
|
|
90
|
-
userAgent: {
|
|
91
|
-
type: 'string',
|
|
92
|
-
required: false,
|
|
93
|
-
},
|
|
94
|
-
metadata: {
|
|
95
|
-
type: 'json',
|
|
96
|
-
required: false,
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
},
|
|
39
|
+
session: { fields: { ipAddress: { type: 'string' }, metadata: { type: 'json' } } },
|
|
100
40
|
},
|
|
101
|
-
|
|
102
|
-
// 세션 생성 시 커스터마이즈
|
|
103
41
|
async onSessionCreate(session, context) {
|
|
104
|
-
return {
|
|
105
|
-
...session,
|
|
106
|
-
ipAddress: context.request.headers.get('x-forwarded-for'),
|
|
107
|
-
userAgent: context.request.headers.get('user-agent'),
|
|
108
|
-
metadata: { loginMethod: 'email' },
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
// 세션 조회 시 커스터마이즈
|
|
113
|
-
async onSessionGet(session, context) {
|
|
114
|
-
return {
|
|
115
|
-
...session,
|
|
116
|
-
activeDevices: await getActiveDevices(session.userId),
|
|
117
|
-
}
|
|
42
|
+
return { ...session, ipAddress: context.request.headers.get('x-forwarded-for') }
|
|
118
43
|
},
|
|
119
44
|
}),
|
|
120
45
|
],
|
|
121
46
|
})
|
|
122
|
-
|
|
123
|
-
// 타입 안전한 커스텀 세션 데이터 접근
|
|
124
|
-
const session = await auth.api.getSession({ headers })
|
|
125
|
-
console.log('IP Address:', session?.ipAddress)
|
|
126
|
-
console.log('Metadata:', session?.metadata)
|
|
127
47
|
```
|
|
@@ -1,123 +1,41 @@
|
|
|
1
1
|
# Better Auth - 설치 및 설정
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [Better Auth](./index.md)
|
|
4
|
-
|
|
5
3
|
## 설치
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
6
|
yarn add better-auth
|
|
9
7
|
```
|
|
10
8
|
|
|
11
|
-
## 서버
|
|
12
|
-
|
|
13
|
-
### 기본 설정
|
|
9
|
+
## 서버
|
|
14
10
|
|
|
15
11
|
```typescript
|
|
16
|
-
// lib/auth.ts
|
|
17
12
|
import { betterAuth } from 'better-auth'
|
|
18
13
|
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
19
|
-
import { prisma } from './prisma'
|
|
20
|
-
|
|
21
|
-
export const auth = betterAuth({
|
|
22
|
-
database: prismaAdapter(prisma),
|
|
23
|
-
|
|
24
|
-
// 이메일/비밀번호 인증 활성화
|
|
25
|
-
emailAndPassword: {
|
|
26
|
-
enabled: true,
|
|
27
|
-
requireEmailVerification: false,
|
|
28
|
-
},
|
|
29
|
-
})
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### 소셜 로그인 설정
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
import { betterAuth } from 'better-auth'
|
|
36
14
|
|
|
37
15
|
export const auth = betterAuth({
|
|
38
16
|
database: prismaAdapter(prisma),
|
|
17
|
+
emailAndPassword: { enabled: true },
|
|
39
18
|
socialProviders: {
|
|
40
|
-
google: {
|
|
41
|
-
|
|
42
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
|
43
|
-
},
|
|
44
|
-
github: {
|
|
45
|
-
clientId: process.env.GITHUB_CLIENT_ID as string,
|
|
46
|
-
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
|
47
|
-
},
|
|
48
|
-
spotify: {
|
|
49
|
-
clientId: process.env.SPOTIFY_CLIENT_ID as string,
|
|
50
|
-
clientSecret: process.env.SPOTIFY_CLIENT_SECRET as string,
|
|
51
|
-
},
|
|
19
|
+
google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET },
|
|
20
|
+
github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET },
|
|
52
21
|
},
|
|
53
22
|
})
|
|
54
23
|
```
|
|
55
24
|
|
|
56
|
-
## 클라이언트
|
|
57
|
-
|
|
58
|
-
### 기본 클라이언트
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
// lib/auth-client.ts
|
|
62
|
-
import { createAuthClient } from 'better-auth/client'
|
|
63
|
-
|
|
64
|
-
export const authClient = createAuthClient({
|
|
65
|
-
// 옵션
|
|
66
|
-
})
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### React 클라이언트
|
|
25
|
+
## 클라이언트
|
|
70
26
|
|
|
71
27
|
```typescript
|
|
72
28
|
import { createAuthClient } from 'better-auth/react'
|
|
73
|
-
|
|
74
|
-
export const authClient = createAuthClient({
|
|
75
|
-
// 옵션
|
|
76
|
-
})
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### 플러그인 포함 클라이언트
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
import { createAuthClient } from 'better-auth/client'
|
|
83
29
|
import { twoFactorClient } from 'better-auth/client/plugins'
|
|
84
30
|
|
|
85
|
-
const authClient = createAuthClient({
|
|
86
|
-
plugins: [
|
|
87
|
-
twoFactorClient({
|
|
88
|
-
twoFactorPage: '/two-factor',
|
|
89
|
-
onTwoFactorRedirect() {
|
|
90
|
-
router.push('/two-factor')
|
|
91
|
-
},
|
|
92
|
-
}),
|
|
93
|
-
],
|
|
31
|
+
export const authClient = createAuthClient({
|
|
32
|
+
plugins: [twoFactorClient({ twoFactorPage: '/two-factor' })],
|
|
94
33
|
})
|
|
95
34
|
```
|
|
96
35
|
|
|
97
|
-
## TanStack Start
|
|
36
|
+
## TanStack Start 연동
|
|
98
37
|
|
|
99
38
|
```typescript
|
|
100
|
-
// lib/auth.ts
|
|
101
|
-
import { betterAuth } from 'better-auth'
|
|
102
|
-
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
103
|
-
import { prisma } from './prisma'
|
|
104
|
-
|
|
105
|
-
export const auth = betterAuth({
|
|
106
|
-
database: prismaAdapter(prisma),
|
|
107
|
-
emailAndPassword: {
|
|
108
|
-
enabled: true,
|
|
109
|
-
},
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
// Server Function에서 사용
|
|
113
|
-
import { createServerFn } from '@tanstack/react-start'
|
|
114
|
-
import { auth } from '~/lib/auth'
|
|
115
|
-
|
|
116
39
|
export const getSession = createServerFn({ method: 'GET' })
|
|
117
|
-
.handler(async ({ request }) => {
|
|
118
|
-
const session = await auth.api.getSession({
|
|
119
|
-
headers: request.headers,
|
|
120
|
-
})
|
|
121
|
-
return session
|
|
122
|
-
})
|
|
40
|
+
.handler(async ({ request }) => auth.api.getSession({ headers: request.headers }))
|
|
123
41
|
```
|