omgkit 2.2.0 → 2.3.1

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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,1090 +1,102 @@
1
1
  ---
2
- name: better-auth
3
- description: Better Auth library for TypeScript with email/password, OAuth, MFA, sessions, and security best practices
4
- category: security
5
- triggers:
6
- - better-auth
7
- - authentication
8
- - auth library
9
- - typescript auth
10
- - session management
2
+ name: Implementing Better Auth
3
+ description: Claude implements enterprise TypeScript authentication with Better Auth. Use when building auth systems, adding OAuth providers, enabling MFA, or managing sessions in TypeScript/Next.js projects.
11
4
  ---
12
5
 
13
- # Better Auth
6
+ # Implementing Better Auth
14
7
 
15
- Enterprise-grade **TypeScript authentication library** following industry best practices. This skill covers email/password auth, social OAuth, multi-factor authentication, session management, role-based access control, and security patterns used by top engineering teams.
16
-
17
- ## Purpose
18
-
19
- Build secure authentication systems:
20
-
21
- - Implement email/password authentication
22
- - Configure social OAuth providers
23
- - Set up multi-factor authentication (MFA)
24
- - Manage sessions securely
25
- - Implement role-based access control
26
- - Handle password reset flows
27
- - Integrate with popular frameworks
28
-
29
- ## Features
30
-
31
- ### 1. Core Configuration
8
+ ## Quick Start
32
9
 
33
10
  ```typescript
34
11
  // lib/auth.ts
35
12
  import { betterAuth } from "better-auth";
36
13
  import { prismaAdapter } from "better-auth/adapters/prisma";
37
- import { twoFactor } from "better-auth/plugins/two-factor";
38
- import { admin } from "better-auth/plugins/admin";
39
- import { organization } from "better-auth/plugins/organization";
40
- import { prisma } from "./prisma";
41
14
 
42
15
  export const auth = betterAuth({
43
- database: prismaAdapter(prisma, {
44
- provider: "postgresql",
45
- }),
46
-
47
- // Email and password authentication
48
- emailAndPassword: {
49
- enabled: true,
50
- requireEmailVerification: true,
51
- minPasswordLength: 12,
52
- maxPasswordLength: 128,
53
- passwordValidation: {
54
- requireUppercase: true,
55
- requireLowercase: true,
56
- requireNumber: true,
57
- requireSpecialChar: true,
58
- },
59
- },
60
-
61
- // Email verification settings
62
- emailVerification: {
63
- sendOnSignUp: true,
64
- autoSignInAfterVerification: true,
65
- expiresIn: 60 * 60 * 24, // 24 hours
66
- },
67
-
68
- // Session configuration
69
- session: {
70
- expiresIn: 60 * 60 * 24 * 7, // 7 days
71
- updateAge: 60 * 60 * 24, // Update session every 24 hours
72
- cookieCache: {
73
- enabled: true,
74
- maxAge: 5 * 60, // 5 minutes
75
- },
76
- },
77
-
78
- // Cookie settings
79
- cookie: {
80
- secure: process.env.NODE_ENV === "production",
81
- sameSite: "lax",
82
- httpOnly: true,
83
- path: "/",
84
- domain: process.env.COOKIE_DOMAIN,
85
- },
86
-
87
- // Rate limiting
88
- rateLimit: {
89
- enabled: true,
90
- window: 60, // 1 minute
91
- max: 10, // 10 requests per window
92
- customRules: {
93
- signIn: { window: 300, max: 5 }, // 5 attempts per 5 minutes
94
- signUp: { window: 3600, max: 3 }, // 3 signups per hour
95
- forgotPassword: { window: 3600, max: 3 }, // 3 requests per hour
96
- },
97
- },
98
-
99
- // Account settings
100
- account: {
101
- accountLinking: {
102
- enabled: true,
103
- trustedProviders: ["google", "github"],
104
- },
105
- },
106
-
107
- // Social OAuth providers
16
+ database: prismaAdapter(prisma, { provider: "postgresql" }),
17
+ emailAndPassword: { enabled: true, requireEmailVerification: true },
18
+ session: { expiresIn: 60 * 60 * 24 * 7 },
108
19
  socialProviders: {
109
- google: {
110
- clientId: process.env.GOOGLE_CLIENT_ID!,
111
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
112
- scope: ["openid", "email", "profile"],
113
- },
114
- github: {
115
- clientId: process.env.GITHUB_CLIENT_ID!,
116
- clientSecret: process.env.GITHUB_CLIENT_SECRET!,
117
- scope: ["read:user", "user:email"],
118
- },
119
- discord: {
120
- clientId: process.env.DISCORD_CLIENT_ID!,
121
- clientSecret: process.env.DISCORD_CLIENT_SECRET!,
122
- scope: ["identify", "email"],
123
- },
124
- },
125
-
126
- // Plugins
127
- plugins: [
128
- twoFactor({
129
- issuer: "MyApp",
130
- otpOptions: {
131
- digits: 6,
132
- period: 30,
133
- },
134
- backupCodes: {
135
- enabled: true,
136
- count: 10,
137
- length: 10,
138
- },
139
- }),
140
- admin({
141
- impersonationSessionDuration: 60 * 60, // 1 hour
142
- }),
143
- organization({
144
- allowUserToCreateOrganization: true,
145
- creatorRole: "owner",
146
- memberRole: "member",
147
- }),
148
- ],
149
-
150
- // User fields
151
- user: {
152
- additionalFields: {
153
- role: {
154
- type: "string",
155
- required: false,
156
- defaultValue: "user",
157
- },
158
- displayName: {
159
- type: "string",
160
- required: false,
161
- },
162
- },
20
+ google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! },
163
21
  },
22
+ });
23
+ ```
164
24
 
165
- // Advanced options
166
- advanced: {
167
- generateId: () => crypto.randomUUID(),
168
- crossSubDomainCookies: {
169
- enabled: process.env.NODE_ENV === "production",
170
- domain: process.env.COOKIE_DOMAIN,
171
- },
172
- },
25
+ ## Features
173
26
 
174
- // Trusted origins for CORS
175
- trustedOrigins: [
176
- process.env.FRONTEND_URL!,
177
- process.env.ADMIN_URL!,
178
- ].filter(Boolean),
179
- });
27
+ | Feature | Description | Reference |
28
+ |---------|-------------|-----------|
29
+ | Email/Password Auth | Secure registration, login, password validation | [Auth Guide](https://www.better-auth.com/docs/authentication/email-password) |
30
+ | Social OAuth | Google, GitHub, Discord provider integration | [Social Providers](https://www.better-auth.com/docs/authentication/social-providers) |
31
+ | Two-Factor Auth | TOTP-based MFA with backup codes | [2FA Plugin](https://www.better-auth.com/docs/plugins/two-factor) |
32
+ | Session Management | Secure cookie-based sessions with refresh | [Sessions](https://www.better-auth.com/docs/concepts/sessions) |
33
+ | Rate Limiting | Configurable limits per endpoint | [Rate Limiting](https://www.better-auth.com/docs/concepts/rate-limit) |
34
+ | Organizations | Multi-tenant support with roles | [Organizations Plugin](https://www.better-auth.com/docs/plugins/organization) |
180
35
 
181
- export type Auth = typeof auth;
182
- ```
36
+ ## Common Patterns
183
37
 
184
- ### 2. Client Setup
38
+ ### Client Setup with Plugins
185
39
 
186
40
  ```typescript
187
41
  // lib/auth-client.ts
188
42
  import { createAuthClient } from "better-auth/client";
189
- import { twoFactorClient } from "better-auth/client/plugins";
190
- import { organizationClient } from "better-auth/client/plugins";
43
+ import { twoFactorClient, organizationClient } from "better-auth/client/plugins";
191
44
 
192
45
  export const authClient = createAuthClient({
193
46
  baseURL: process.env.NEXT_PUBLIC_API_URL,
194
- plugins: [
195
- twoFactorClient(),
196
- organizationClient(),
197
- ],
47
+ plugins: [twoFactorClient(), organizationClient()],
198
48
  });
199
49
 
200
- // Type-safe hooks
201
- export const {
202
- signIn,
203
- signUp,
204
- signOut,
205
- useSession,
206
- getSession,
207
- // OAuth
208
- signIn: { social: socialSignIn },
209
- // Two-factor
210
- twoFactor,
211
- // Organizations
212
- organization,
213
- } = authClient;
214
- ```
215
-
216
- ### 3. Email/Password Authentication
217
-
218
- ```typescript
219
- // components/auth/SignUpForm.tsx
220
- "use client";
221
-
222
- import { useState } from "react";
223
- import { authClient } from "@/lib/auth-client";
224
- import { useRouter } from "next/navigation";
225
-
226
- interface SignUpFormData {
227
- name: string;
228
- email: string;
229
- password: string;
230
- confirmPassword: string;
231
- }
232
-
233
- export function SignUpForm() {
234
- const router = useRouter();
235
- const [formData, setFormData] = useState<SignUpFormData>({
236
- name: "",
237
- email: "",
238
- password: "",
239
- confirmPassword: "",
240
- });
241
- const [error, setError] = useState<string | null>(null);
242
- const [loading, setLoading] = useState(false);
243
-
244
- const handleSubmit = async (e: React.FormEvent) => {
245
- e.preventDefault();
246
- setError(null);
247
-
248
- if (formData.password !== formData.confirmPassword) {
249
- setError("Passwords do not match");
250
- return;
251
- }
252
-
253
- setLoading(true);
254
-
255
- try {
256
- const { data, error } = await authClient.signUp.email({
257
- email: formData.email,
258
- password: formData.password,
259
- name: formData.name,
260
- });
261
-
262
- if (error) {
263
- setError(error.message);
264
- return;
265
- }
266
-
267
- // Redirect to verification page
268
- router.push("/verify-email");
269
- } catch (err) {
270
- setError("An unexpected error occurred");
271
- } finally {
272
- setLoading(false);
273
- }
274
- };
275
-
276
- return (
277
- <form onSubmit={handleSubmit} className="space-y-4">
278
- <div>
279
- <label htmlFor="name">Name</label>
280
- <input
281
- id="name"
282
- type="text"
283
- value={formData.name}
284
- onChange={(e) => setFormData({ ...formData, name: e.target.value })}
285
- required
286
- />
287
- </div>
288
-
289
- <div>
290
- <label htmlFor="email">Email</label>
291
- <input
292
- id="email"
293
- type="email"
294
- value={formData.email}
295
- onChange={(e) => setFormData({ ...formData, email: e.target.value })}
296
- required
297
- />
298
- </div>
299
-
300
- <div>
301
- <label htmlFor="password">Password</label>
302
- <input
303
- id="password"
304
- type="password"
305
- value={formData.password}
306
- onChange={(e) =>
307
- setFormData({ ...formData, password: e.target.value })
308
- }
309
- minLength={12}
310
- required
311
- />
312
- <PasswordStrengthIndicator password={formData.password} />
313
- </div>
314
-
315
- <div>
316
- <label htmlFor="confirmPassword">Confirm Password</label>
317
- <input
318
- id="confirmPassword"
319
- type="password"
320
- value={formData.confirmPassword}
321
- onChange={(e) =>
322
- setFormData({ ...formData, confirmPassword: e.target.value })
323
- }
324
- required
325
- />
326
- </div>
327
-
328
- {error && <div className="text-red-500">{error}</div>}
329
-
330
- <button type="submit" disabled={loading}>
331
- {loading ? "Creating account..." : "Sign Up"}
332
- </button>
333
- </form>
334
- );
335
- }
336
-
337
- // components/auth/SignInForm.tsx
338
- "use client";
339
-
340
- import { useState } from "react";
341
- import { authClient } from "@/lib/auth-client";
342
- import { useRouter } from "next/navigation";
343
-
344
- export function SignInForm() {
345
- const router = useRouter();
346
- const [email, setEmail] = useState("");
347
- const [password, setPassword] = useState("");
348
- const [error, setError] = useState<string | null>(null);
349
- const [loading, setLoading] = useState(false);
350
- const [requiresTwoFactor, setRequiresTwoFactor] = useState(false);
351
- const [twoFactorCode, setTwoFactorCode] = useState("");
352
-
353
- const handleSubmit = async (e: React.FormEvent) => {
354
- e.preventDefault();
355
- setError(null);
356
- setLoading(true);
357
-
358
- try {
359
- const { data, error } = await authClient.signIn.email({
360
- email,
361
- password,
362
- });
363
-
364
- if (error) {
365
- if (error.code === "TWO_FACTOR_REQUIRED") {
366
- setRequiresTwoFactor(true);
367
- return;
368
- }
369
- setError(error.message);
370
- return;
371
- }
372
-
373
- router.push("/dashboard");
374
- } catch (err) {
375
- setError("An unexpected error occurred");
376
- } finally {
377
- setLoading(false);
378
- }
379
- };
380
-
381
- const handleTwoFactorSubmit = async (e: React.FormEvent) => {
382
- e.preventDefault();
383
- setLoading(true);
384
-
385
- try {
386
- const { data, error } = await authClient.twoFactor.verify({
387
- code: twoFactorCode,
388
- });
389
-
390
- if (error) {
391
- setError(error.message);
392
- return;
393
- }
394
-
395
- router.push("/dashboard");
396
- } finally {
397
- setLoading(false);
398
- }
399
- };
400
-
401
- if (requiresTwoFactor) {
402
- return (
403
- <form onSubmit={handleTwoFactorSubmit} className="space-y-4">
404
- <div>
405
- <label htmlFor="code">Two-Factor Code</label>
406
- <input
407
- id="code"
408
- type="text"
409
- inputMode="numeric"
410
- pattern="[0-9]*"
411
- maxLength={6}
412
- value={twoFactorCode}
413
- onChange={(e) => setTwoFactorCode(e.target.value)}
414
- placeholder="Enter 6-digit code"
415
- required
416
- />
417
- </div>
418
-
419
- {error && <div className="text-red-500">{error}</div>}
420
-
421
- <button type="submit" disabled={loading}>
422
- {loading ? "Verifying..." : "Verify"}
423
- </button>
424
-
425
- <button
426
- type="button"
427
- onClick={() => setRequiresTwoFactor(false)}
428
- className="text-sm text-gray-500"
429
- >
430
- Use a different account
431
- </button>
432
- </form>
433
- );
434
- }
435
-
436
- return (
437
- <form onSubmit={handleSubmit} className="space-y-4">
438
- <div>
439
- <label htmlFor="email">Email</label>
440
- <input
441
- id="email"
442
- type="email"
443
- value={email}
444
- onChange={(e) => setEmail(e.target.value)}
445
- required
446
- />
447
- </div>
448
-
449
- <div>
450
- <label htmlFor="password">Password</label>
451
- <input
452
- id="password"
453
- type="password"
454
- value={password}
455
- onChange={(e) => setPassword(e.target.value)}
456
- required
457
- />
458
- </div>
459
-
460
- {error && <div className="text-red-500">{error}</div>}
461
-
462
- <button type="submit" disabled={loading}>
463
- {loading ? "Signing in..." : "Sign In"}
464
- </button>
465
-
466
- <a href="/forgot-password" className="text-sm">
467
- Forgot password?
468
- </a>
469
- </form>
470
- );
471
- }
50
+ export const { signIn, signUp, signOut, useSession } = authClient;
472
51
  ```
473
52
 
474
- ### 4. Social OAuth Authentication
475
-
476
- ```typescript
477
- // components/auth/SocialAuth.tsx
478
- "use client";
479
-
480
- import { authClient } from "@/lib/auth-client";
481
-
482
- interface SocialAuthProps {
483
- mode: "signin" | "signup";
484
- }
485
-
486
- export function SocialAuth({ mode }: SocialAuthProps) {
487
- const handleGoogleAuth = async () => {
488
- await authClient.signIn.social({
489
- provider: "google",
490
- callbackURL: "/dashboard",
491
- errorCallbackURL: "/auth/error",
492
- });
493
- };
494
-
495
- const handleGitHubAuth = async () => {
496
- await authClient.signIn.social({
497
- provider: "github",
498
- callbackURL: "/dashboard",
499
- errorCallbackURL: "/auth/error",
500
- });
501
- };
502
-
503
- const handleDiscordAuth = async () => {
504
- await authClient.signIn.social({
505
- provider: "discord",
506
- callbackURL: "/dashboard",
507
- errorCallbackURL: "/auth/error",
508
- });
509
- };
510
-
511
- return (
512
- <div className="space-y-3">
513
- <button
514
- onClick={handleGoogleAuth}
515
- className="w-full flex items-center justify-center gap-2 btn-outline"
516
- >
517
- <GoogleIcon />
518
- {mode === "signin" ? "Sign in with Google" : "Sign up with Google"}
519
- </button>
520
-
521
- <button
522
- onClick={handleGitHubAuth}
523
- className="w-full flex items-center justify-center gap-2 btn-outline"
524
- >
525
- <GitHubIcon />
526
- {mode === "signin" ? "Sign in with GitHub" : "Sign up with GitHub"}
527
- </button>
528
-
529
- <button
530
- onClick={handleDiscordAuth}
531
- className="w-full flex items-center justify-center gap-2 btn-outline"
532
- >
533
- <DiscordIcon />
534
- {mode === "signin" ? "Sign in with Discord" : "Sign up with Discord"}
535
- </button>
536
- </div>
537
- );
538
- }
539
-
540
- // app/api/auth/callback/[provider]/route.ts
541
- import { auth } from "@/lib/auth";
542
- import { NextRequest } from "next/server";
543
-
544
- export async function GET(
545
- request: NextRequest,
546
- { params }: { params: { provider: string } }
547
- ) {
548
- return auth.handler(request);
549
- }
550
- ```
551
-
552
- ### 5. Two-Factor Authentication
553
-
554
- ```typescript
555
- // components/auth/TwoFactorSetup.tsx
556
- "use client";
557
-
558
- import { useState } from "react";
559
- import { authClient } from "@/lib/auth-client";
560
- import QRCode from "qrcode.react";
561
-
562
- export function TwoFactorSetup() {
563
- const [step, setStep] = useState<"start" | "verify" | "backup" | "complete">(
564
- "start"
565
- );
566
- const [totpUri, setTotpUri] = useState<string | null>(null);
567
- const [backupCodes, setBackupCodes] = useState<string[]>([]);
568
- const [verificationCode, setVerificationCode] = useState("");
569
- const [error, setError] = useState<string | null>(null);
570
-
571
- const handleEnable = async () => {
572
- try {
573
- const { data, error } = await authClient.twoFactor.enable();
574
-
575
- if (error) {
576
- setError(error.message);
577
- return;
578
- }
579
-
580
- setTotpUri(data.totpURI);
581
- setStep("verify");
582
- } catch (err) {
583
- setError("Failed to enable 2FA");
584
- }
585
- };
586
-
587
- const handleVerify = async (e: React.FormEvent) => {
588
- e.preventDefault();
589
-
590
- try {
591
- const { data, error } = await authClient.twoFactor.verifySetup({
592
- code: verificationCode,
593
- });
594
-
595
- if (error) {
596
- setError(error.message);
597
- return;
598
- }
599
-
600
- setBackupCodes(data.backupCodes);
601
- setStep("backup");
602
- } catch (err) {
603
- setError("Failed to verify code");
604
- }
605
- };
606
-
607
- const handleComplete = () => {
608
- setStep("complete");
609
- };
610
-
611
- if (step === "start") {
612
- return (
613
- <div className="space-y-4">
614
- <h2>Enable Two-Factor Authentication</h2>
615
- <p>Add an extra layer of security to your account.</p>
616
- <button onClick={handleEnable}>Enable 2FA</button>
617
- </div>
618
- );
619
- }
620
-
621
- if (step === "verify") {
622
- return (
623
- <div className="space-y-4">
624
- <h2>Scan QR Code</h2>
625
- <p>Scan this QR code with your authenticator app:</p>
626
-
627
- {totpUri && (
628
- <div className="flex justify-center">
629
- <QRCode value={totpUri} size={200} />
630
- </div>
631
- )}
632
-
633
- <form onSubmit={handleVerify} className="space-y-4">
634
- <div>
635
- <label htmlFor="code">Verification Code</label>
636
- <input
637
- id="code"
638
- type="text"
639
- inputMode="numeric"
640
- pattern="[0-9]*"
641
- maxLength={6}
642
- value={verificationCode}
643
- onChange={(e) => setVerificationCode(e.target.value)}
644
- placeholder="Enter 6-digit code"
645
- required
646
- />
647
- </div>
648
-
649
- {error && <div className="text-red-500">{error}</div>}
650
-
651
- <button type="submit">Verify</button>
652
- </form>
653
- </div>
654
- );
655
- }
656
-
657
- if (step === "backup") {
658
- return (
659
- <div className="space-y-4">
660
- <h2>Save Backup Codes</h2>
661
- <p>
662
- Save these backup codes in a secure place. You can use them to access
663
- your account if you lose your authenticator device.
664
- </p>
665
-
666
- <div className="grid grid-cols-2 gap-2 font-mono bg-gray-100 p-4 rounded">
667
- {backupCodes.map((code, index) => (
668
- <div key={index} className="text-center">
669
- {code}
670
- </div>
671
- ))}
672
- </div>
673
-
674
- <button onClick={handleComplete}>I've saved my backup codes</button>
675
- </div>
676
- );
677
- }
678
-
679
- return (
680
- <div className="space-y-4">
681
- <h2>Two-Factor Authentication Enabled</h2>
682
- <p>Your account is now protected with 2FA.</p>
683
- </div>
684
- );
685
- }
686
-
687
- // components/auth/TwoFactorDisable.tsx
688
- "use client";
689
-
690
- import { useState } from "react";
691
- import { authClient } from "@/lib/auth-client";
692
-
693
- export function TwoFactorDisable() {
694
- const [password, setPassword] = useState("");
695
- const [error, setError] = useState<string | null>(null);
696
- const [loading, setLoading] = useState(false);
697
-
698
- const handleDisable = async (e: React.FormEvent) => {
699
- e.preventDefault();
700
- setLoading(true);
701
-
702
- try {
703
- const { error } = await authClient.twoFactor.disable({
704
- password,
705
- });
706
-
707
- if (error) {
708
- setError(error.message);
709
- return;
710
- }
711
-
712
- // Refresh page or update state
713
- window.location.reload();
714
- } finally {
715
- setLoading(false);
716
- }
717
- };
718
-
719
- return (
720
- <form onSubmit={handleDisable} className="space-y-4">
721
- <h2>Disable Two-Factor Authentication</h2>
722
- <p className="text-yellow-600">
723
- Warning: This will make your account less secure.
724
- </p>
725
-
726
- <div>
727
- <label htmlFor="password">Confirm Password</label>
728
- <input
729
- id="password"
730
- type="password"
731
- value={password}
732
- onChange={(e) => setPassword(e.target.value)}
733
- required
734
- />
735
- </div>
736
-
737
- {error && <div className="text-red-500">{error}</div>}
738
-
739
- <button type="submit" disabled={loading} className="btn-danger">
740
- {loading ? "Disabling..." : "Disable 2FA"}
741
- </button>
742
- </form>
743
- );
744
- }
745
- ```
746
-
747
- ### 6. Session Management
53
+ ### Protected Routes Middleware
748
54
 
749
55
  ```typescript
750
56
  // middleware.ts
751
57
  import { auth } from "@/lib/auth";
752
58
  import { NextRequest, NextResponse } from "next/server";
753
59
 
754
- const publicRoutes = ["/", "/login", "/signup", "/forgot-password"];
755
- const authRoutes = ["/login", "/signup"];
756
-
757
60
  export async function middleware(request: NextRequest) {
758
- const { pathname } = request.nextUrl;
759
-
760
- // Get session
761
- const session = await auth.api.getSession({
762
- headers: request.headers,
763
- });
764
-
765
- // Redirect authenticated users away from auth pages
766
- if (authRoutes.includes(pathname) && session) {
767
- return NextResponse.redirect(new URL("/dashboard", request.url));
768
- }
61
+ const session = await auth.api.getSession({ headers: request.headers });
769
62
 
770
- // Protect private routes
771
- if (!publicRoutes.includes(pathname) && !session) {
772
- const loginUrl = new URL("/login", request.url);
773
- loginUrl.searchParams.set("callbackUrl", pathname);
774
- return NextResponse.redirect(loginUrl);
63
+ if (!session && !request.nextUrl.pathname.startsWith("/login")) {
64
+ return NextResponse.redirect(new URL("/login", request.url));
775
65
  }
776
-
777
66
  return NextResponse.next();
778
67
  }
779
-
780
- export const config = {
781
- matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
782
- };
783
-
784
- // hooks/useAuth.ts
785
- "use client";
786
-
787
- import { authClient } from "@/lib/auth-client";
788
- import { useRouter } from "next/navigation";
789
- import { useCallback } from "react";
790
-
791
- export function useAuth() {
792
- const router = useRouter();
793
- const { data: session, isPending, error, refetch } = authClient.useSession();
794
-
795
- const signOut = useCallback(async () => {
796
- await authClient.signOut();
797
- router.push("/login");
798
- }, [router]);
799
-
800
- const refreshSession = useCallback(async () => {
801
- await refetch();
802
- }, [refetch]);
803
-
804
- return {
805
- user: session?.user ?? null,
806
- session: session?.session ?? null,
807
- isAuthenticated: !!session?.user,
808
- isLoading: isPending,
809
- error,
810
- signOut,
811
- refreshSession,
812
- };
813
- }
814
-
815
- // components/auth/SessionInfo.tsx
816
- "use client";
817
-
818
- import { useAuth } from "@/hooks/useAuth";
819
- import { formatDistanceToNow } from "date-fns";
820
-
821
- export function SessionInfo() {
822
- const { session, user, signOut } = useAuth();
823
-
824
- if (!session || !user) return null;
825
-
826
- return (
827
- <div className="p-4 bg-gray-100 rounded">
828
- <h3>Session Information</h3>
829
- <dl className="space-y-2">
830
- <div>
831
- <dt className="text-sm text-gray-500">User</dt>
832
- <dd>{user.email}</dd>
833
- </div>
834
- <div>
835
- <dt className="text-sm text-gray-500">Session created</dt>
836
- <dd>
837
- {formatDistanceToNow(new Date(session.createdAt), {
838
- addSuffix: true,
839
- })}
840
- </dd>
841
- </div>
842
- <div>
843
- <dt className="text-sm text-gray-500">Expires</dt>
844
- <dd>
845
- {formatDistanceToNow(new Date(session.expiresAt), {
846
- addSuffix: true,
847
- })}
848
- </dd>
849
- </div>
850
- </dl>
851
- <button onClick={signOut} className="mt-4 btn-danger">
852
- Sign Out
853
- </button>
854
- </div>
855
- );
856
- }
857
68
  ```
858
69
 
859
- ### 7. Password Reset Flow
70
+ ### Two-Factor Authentication Flow
860
71
 
861
72
  ```typescript
862
- // components/auth/ForgotPassword.tsx
863
- "use client";
864
-
865
- import { useState } from "react";
866
- import { authClient } from "@/lib/auth-client";
867
-
868
- export function ForgotPassword() {
869
- const [email, setEmail] = useState("");
870
- const [submitted, setSubmitted] = useState(false);
871
- const [error, setError] = useState<string | null>(null);
872
- const [loading, setLoading] = useState(false);
873
-
874
- const handleSubmit = async (e: React.FormEvent) => {
875
- e.preventDefault();
876
- setLoading(true);
877
- setError(null);
878
-
879
- try {
880
- const { error } = await authClient.forgetPassword({
881
- email,
882
- redirectTo: "/reset-password",
883
- });
884
-
885
- if (error) {
886
- setError(error.message);
887
- return;
888
- }
889
-
890
- setSubmitted(true);
891
- } finally {
892
- setLoading(false);
893
- }
894
- };
895
-
896
- if (submitted) {
897
- return (
898
- <div className="text-center">
899
- <h2>Check your email</h2>
900
- <p>
901
- We've sent a password reset link to <strong>{email}</strong>
902
- </p>
903
- </div>
904
- );
905
- }
906
-
907
- return (
908
- <form onSubmit={handleSubmit} className="space-y-4">
909
- <h2>Forgot Password</h2>
910
- <p>Enter your email to receive a password reset link.</p>
911
-
912
- <div>
913
- <label htmlFor="email">Email</label>
914
- <input
915
- id="email"
916
- type="email"
917
- value={email}
918
- onChange={(e) => setEmail(e.target.value)}
919
- required
920
- />
921
- </div>
922
-
923
- {error && <div className="text-red-500">{error}</div>}
924
-
925
- <button type="submit" disabled={loading}>
926
- {loading ? "Sending..." : "Send Reset Link"}
927
- </button>
928
- </form>
929
- );
930
- }
931
-
932
- // components/auth/ResetPassword.tsx
933
- "use client";
934
-
935
- import { useState } from "react";
936
- import { authClient } from "@/lib/auth-client";
937
- import { useRouter, useSearchParams } from "next/navigation";
938
-
939
- export function ResetPassword() {
940
- const router = useRouter();
941
- const searchParams = useSearchParams();
942
- const token = searchParams.get("token");
943
-
944
- const [password, setPassword] = useState("");
945
- const [confirmPassword, setConfirmPassword] = useState("");
946
- const [error, setError] = useState<string | null>(null);
947
- const [loading, setLoading] = useState(false);
948
-
949
- const handleSubmit = async (e: React.FormEvent) => {
950
- e.preventDefault();
951
-
952
- if (password !== confirmPassword) {
953
- setError("Passwords do not match");
954
- return;
955
- }
956
-
957
- if (!token) {
958
- setError("Invalid reset token");
959
- return;
960
- }
961
-
962
- setLoading(true);
963
- setError(null);
964
-
965
- try {
966
- const { error } = await authClient.resetPassword({
967
- newPassword: password,
968
- token,
969
- });
970
-
971
- if (error) {
972
- setError(error.message);
973
- return;
974
- }
73
+ // Handle 2FA during sign-in
74
+ const { data, error } = await authClient.signIn.email({ email, password });
975
75
 
976
- router.push("/login?message=password-reset");
977
- } finally {
978
- setLoading(false);
979
- }
980
- };
981
-
982
- return (
983
- <form onSubmit={handleSubmit} className="space-y-4">
984
- <h2>Reset Password</h2>
985
-
986
- <div>
987
- <label htmlFor="password">New Password</label>
988
- <input
989
- id="password"
990
- type="password"
991
- value={password}
992
- onChange={(e) => setPassword(e.target.value)}
993
- minLength={12}
994
- required
995
- />
996
- </div>
997
-
998
- <div>
999
- <label htmlFor="confirmPassword">Confirm Password</label>
1000
- <input
1001
- id="confirmPassword"
1002
- type="password"
1003
- value={confirmPassword}
1004
- onChange={(e) => setConfirmPassword(e.target.value)}
1005
- required
1006
- />
1007
- </div>
1008
-
1009
- {error && <div className="text-red-500">{error}</div>}
1010
-
1011
- <button type="submit" disabled={loading}>
1012
- {loading ? "Resetting..." : "Reset Password"}
1013
- </button>
1014
- </form>
1015
- );
76
+ if (error?.code === "TWO_FACTOR_REQUIRED") {
77
+ // Show 2FA input
78
+ const { data: verified } = await authClient.twoFactor.verify({ code: totpCode });
1016
79
  }
1017
- ```
1018
-
1019
- ## Use Cases
1020
-
1021
- ### Next.js API Route Handler
1022
-
1023
- ```typescript
1024
- // app/api/[...auth]/route.ts
1025
- import { auth } from "@/lib/auth";
1026
- import { toNextJsHandler } from "better-auth/next-js";
1027
-
1028
- export const { GET, POST } = toNextJsHandler(auth.handler);
1029
- ```
1030
-
1031
- ### Server Component Auth Check
1032
-
1033
- ```typescript
1034
- // app/dashboard/page.tsx
1035
- import { auth } from "@/lib/auth";
1036
- import { headers } from "next/headers";
1037
- import { redirect } from "next/navigation";
1038
-
1039
- export default async function DashboardPage() {
1040
- const session = await auth.api.getSession({
1041
- headers: headers(),
1042
- });
1043
80
 
1044
- if (!session) {
1045
- redirect("/login");
1046
- }
1047
-
1048
- return (
1049
- <div>
1050
- <h1>Welcome, {session.user.name}</h1>
1051
- </div>
1052
- );
1053
- }
81
+ // Enable 2FA for user
82
+ const { data } = await authClient.twoFactor.enable();
83
+ // data.totpURI contains QR code data
84
+ // data.backupCodes contains recovery codes
1054
85
  ```
1055
86
 
1056
87
  ## Best Practices
1057
88
 
1058
- ### Do's
1059
-
1060
- - Require email verification for new accounts
1061
- - Implement rate limiting on auth endpoints
1062
- - Use secure, httpOnly cookies
1063
- - Enable two-factor authentication option
1064
- - Hash passwords with strong algorithms
1065
- - Validate password strength on signup
1066
- - Set appropriate session expiration
1067
- - Log authentication events
1068
- - Use HTTPS in production
1069
- - Implement account lockout policies
1070
-
1071
- ### Don'ts
1072
-
1073
- - Don't store plain-text passwords
1074
- - Don't use predictable session tokens
1075
- - Don't expose detailed error messages
1076
- - Don't allow unlimited login attempts
1077
- - Don't skip CSRF protection
1078
- - Don't use weak password policies
1079
- - Don't store sensitive data in JWT
1080
- - Don't ignore session fixation
1081
- - Don't disable security headers
1082
- - Don't trust client-side validation alone
89
+ | Do | Avoid |
90
+ |----|-------|
91
+ | Require email verification for new accounts | Storing plain-text passwords |
92
+ | Enable rate limiting on auth endpoints | Exposing detailed error messages |
93
+ | Use httpOnly, secure cookies | Using predictable session tokens |
94
+ | Set strong password requirements (12+ chars) | Allowing unlimited login attempts |
95
+ | Implement proper session expiration | Storing sensitive data in JWT |
96
+ | Log authentication events for auditing | Skipping CSRF protection |
1083
97
 
1084
98
  ## References
1085
99
 
1086
100
  - [Better Auth Documentation](https://www.better-auth.com/docs)
1087
101
  - [Better Auth Plugins](https://www.better-auth.com/docs/plugins)
1088
102
  - [OWASP Authentication Guidelines](https://owasp.org/www-project-web-security-testing-guide/)
1089
- - [OAuth 2.0 Best Practices](https://oauth.net/2/)
1090
- - [NIST Password Guidelines](https://pages.nist.gov/800-63-3/)