omgkit 2.1.0 → 2.2.0

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 (56) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  5. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  6. package/plugin/skills/databases/redis/SKILL.md +885 -25
  7. package/plugin/skills/devops/aws/SKILL.md +686 -28
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  10. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  11. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  12. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  13. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  14. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  15. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  16. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  17. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  18. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  19. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  20. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  21. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  23. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  26. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  27. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  28. package/plugin/skills/languages/python/SKILL.md +489 -25
  29. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  30. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  31. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  32. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  33. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  34. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  35. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  36. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  37. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  38. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  39. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  40. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  41. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  42. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  43. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  44. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  45. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  46. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  47. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  48. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  49. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  50. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  51. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  52. package/plugin/skills/security/oauth/SKILL.md +968 -31
  53. package/plugin/skills/security/owasp/SKILL.md +894 -33
  54. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  55. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  56. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,53 +1,1090 @@
1
1
  ---
2
2
  name: better-auth
3
- description: Better Auth library. Use for authentication in TypeScript projects.
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
4
11
  ---
5
12
 
6
- # Better Auth Skill
13
+ # Better Auth
14
+
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
7
32
 
8
- ## Setup
9
33
  ```typescript
10
- import { betterAuth } from 'better-auth';
34
+ // lib/auth.ts
35
+ import { betterAuth } from "better-auth";
36
+ 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";
11
41
 
12
42
  export const auth = betterAuth({
13
- database: {
14
- provider: 'postgresql',
15
- url: process.env.DATABASE_URL,
16
- },
43
+ database: prismaAdapter(prisma, {
44
+ provider: "postgresql",
45
+ }),
46
+
47
+ // Email and password authentication
17
48
  emailAndPassword: {
18
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
19
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
20
108
  socialProviders: {
21
109
  google: {
22
- clientId: process.env.GOOGLE_CLIENT_ID,
23
- clientSecret: process.env.GOOGLE_CLIENT_SECRET,
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"],
24
123
  },
25
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
+ },
163
+ },
164
+
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
+ },
173
+
174
+ // Trusted origins for CORS
175
+ trustedOrigins: [
176
+ process.env.FRONTEND_URL!,
177
+ process.env.ADMIN_URL!,
178
+ ].filter(Boolean),
26
179
  });
180
+
181
+ export type Auth = typeof auth;
27
182
  ```
28
183
 
29
- ## Client Usage
30
- ```typescript
31
- import { createAuthClient } from 'better-auth/client';
184
+ ### 2. Client Setup
32
185
 
33
- const authClient = createAuthClient();
186
+ ```typescript
187
+ // lib/auth-client.ts
188
+ import { createAuthClient } from "better-auth/client";
189
+ import { twoFactorClient } from "better-auth/client/plugins";
190
+ import { organizationClient } from "better-auth/client/plugins";
34
191
 
35
- // Sign up
36
- await authClient.signUp.email({
37
- email: 'user@example.com',
38
- password: 'password',
39
- name: 'User',
192
+ export const authClient = createAuthClient({
193
+ baseURL: process.env.NEXT_PUBLIC_API_URL,
194
+ plugins: [
195
+ twoFactorClient(),
196
+ organizationClient(),
197
+ ],
40
198
  });
41
199
 
42
- // Sign in
43
- await authClient.signIn.email({
44
- email: 'user@example.com',
45
- password: 'password',
46
- });
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
+ }
472
+ ```
473
+
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
748
+
749
+ ```typescript
750
+ // middleware.ts
751
+ import { auth } from "@/lib/auth";
752
+ import { NextRequest, NextResponse } from "next/server";
753
+
754
+ const publicRoutes = ["/", "/login", "/signup", "/forgot-password"];
755
+ const authRoutes = ["/login", "/signup"];
756
+
757
+ 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
+ }
769
+
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);
775
+ }
776
+
777
+ return NextResponse.next();
778
+ }
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
+ ```
858
+
859
+ ### 7. Password Reset Flow
860
+
861
+ ```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
+ }
975
+
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
+ );
1016
+ }
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
+
1044
+ if (!session) {
1045
+ redirect("/login");
1046
+ }
1047
+
1048
+ return (
1049
+ <div>
1050
+ <h1>Welcome, {session.user.name}</h1>
1051
+ </div>
1052
+ );
1053
+ }
47
1054
  ```
48
1055
 
49
1056
  ## Best Practices
50
- - Enable rate limiting
51
- - Use secure cookies
52
- - Implement email verification
53
- - Add MFA option
1057
+
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
1083
+
1084
+ ## References
1085
+
1086
+ - [Better Auth Documentation](https://www.better-auth.com/docs)
1087
+ - [Better Auth Plugins](https://www.better-auth.com/docs/plugins)
1088
+ - [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/)