@githat/nextjs 0.2.0 → 0.2.2
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/README.md +584 -48
- package/dist/githat.css +41 -0
- package/dist/index.d.mts +213 -2
- package/dist/index.d.ts +213 -2
- package/dist/index.js +792 -96
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +786 -95
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +73 -4
- package/dist/middleware.d.ts +73 -4
- package/dist/middleware.js +96 -12
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +85 -13
- package/dist/middleware.mjs.map +1 -1
- package/dist/proxy.d.mts +78 -0
- package/dist/proxy.d.ts +78 -0
- package/dist/proxy.js +138 -0
- package/dist/proxy.js.map +1 -0
- package/dist/proxy.mjs +101 -0
- package/dist/proxy.mjs.map +1 -0
- package/dist/server.d.mts +164 -0
- package/dist/server.d.ts +164 -0
- package/dist/server.js +203 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +163 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +23 -3
package/README.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
# @githat/nextjs
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**SDK for connecting your Next.js or React app to GitHat's identity platform — auth, orgs, teams, API keys, and more.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
GitHat is your backend. This SDK connects your app to `api.githat.io` with pre-built UI components, hooks, and middleware. No backend to deploy.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@githat/nextjs)
|
|
10
10
|
[](https://www.npmjs.com/package/@githat/nextjs)
|
|
11
11
|
[](https://www.typescriptlang.org/)
|
|
12
12
|
[](./LICENSE)
|
|
13
13
|
|
|
14
|
-
[Quick Start](#quick-start) · [Components](#components) · [Hooks](#hooks) · [Middleware](#middleware) · [Docs](https://githat.io/docs)
|
|
14
|
+
[Quick Start](#quick-start) · [Components](#components) · [Hooks](#hooks) · [Middleware](#middleware) · [Server-Side](#server-side-authentication) · [Docs](https://githat.io/docs)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
@@ -19,14 +19,17 @@ Build secure, multi-tenant applications with pre-built UI components, hooks, and
|
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
|
-
- **Pre-built UI Components** — Sign-in, sign-up,
|
|
23
|
-
- **React Hooks** — `useAuth()` and `
|
|
22
|
+
- **Pre-built UI Components** — Sign-in, sign-up, password reset, email verification — all themed and ready to go
|
|
23
|
+
- **React Hooks** — `useAuth()`, `useGitHat()`, and `useData()` for full control over auth state and API calls
|
|
24
24
|
- **Next.js Middleware** — Protect routes at the edge with a single line of config
|
|
25
|
-
- **
|
|
25
|
+
- **Server-Side Auth** — Verify tokens, wrap API routes, and inject auth headers
|
|
26
|
+
- **Customer Data API** — Store and query app data in GitHat's managed DynamoDB
|
|
27
|
+
- **Multi-Tenant** — Organizations, team invitations, and role-based access — managed by GitHat's backend
|
|
26
28
|
- **MCP & Agent Verification** — Verify MCP servers and AI agents on-chain
|
|
27
29
|
- **Dark Theme Included** — Import `@githat/nextjs/styles` for a polished dark UI out of the box
|
|
28
30
|
- **TypeScript First** — Full type definitions for every component, hook, and utility
|
|
29
31
|
- **Dual Output** — Ships ESM + CJS so it works everywhere
|
|
32
|
+
- **Hosted Backend** — All features powered by GitHat's platform (`api.githat.io`). No backend deployment required
|
|
30
33
|
|
|
31
34
|
---
|
|
32
35
|
|
|
@@ -98,6 +101,10 @@ export default function SignUpPage() {
|
|
|
98
101
|
|
|
99
102
|
### 5. Protect Routes with Middleware
|
|
100
103
|
|
|
104
|
+
<!-- tabs:start -->
|
|
105
|
+
|
|
106
|
+
#### **Next.js 14-15 (middleware.ts)**
|
|
107
|
+
|
|
101
108
|
```ts
|
|
102
109
|
// middleware.ts (project root)
|
|
103
110
|
import { authMiddleware } from '@githat/nextjs/middleware';
|
|
@@ -111,6 +118,23 @@ export const config = {
|
|
|
111
118
|
};
|
|
112
119
|
```
|
|
113
120
|
|
|
121
|
+
#### **Next.js 16+ (proxy.ts)**
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// proxy.ts (project root)
|
|
125
|
+
import { authProxy } from '@githat/nextjs/proxy';
|
|
126
|
+
|
|
127
|
+
export const proxy = authProxy({
|
|
128
|
+
publicRoutes: ['/', '/sign-in', '/sign-up', '/forgot-password'],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const config = {
|
|
132
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
<!-- tabs:end -->
|
|
137
|
+
|
|
114
138
|
### 6. Use Auth State Anywhere
|
|
115
139
|
|
|
116
140
|
```tsx
|
|
@@ -141,32 +165,91 @@ That's it. You now have a fully authenticated app with sign-in, sign-up, route p
|
|
|
141
165
|
|
|
142
166
|
### Provider
|
|
143
167
|
|
|
144
|
-
| Component | Description | Key Props
|
|
145
|
-
|
|
168
|
+
| Component | Description | Key Props |
|
|
169
|
+
|------------------|------------------------------------------|-----------------------------------------------------------|
|
|
146
170
|
| `GitHatProvider` | Wraps your app and provides auth context | `config` (required) — see [Configuration](#configuration) |
|
|
147
171
|
|
|
148
172
|
### Authentication Forms
|
|
149
173
|
|
|
150
|
-
| Component
|
|
151
|
-
|
|
152
|
-
| `SignInForm`
|
|
153
|
-
| `SignUpForm`
|
|
174
|
+
| Component | Description | Props |
|
|
175
|
+
|----------------------|--------------------------------------|-----------------------------------------------------|
|
|
176
|
+
| `SignInForm` | Email + password sign-in form | `onSuccess?`, `signUpUrl?`, `forgotPasswordUrl?` |
|
|
177
|
+
| `SignUpForm` | Name + email + password sign-up form | `onSuccess?`, `signInUrl?` |
|
|
178
|
+
| `ForgotPasswordForm` | Request password reset email | `onSuccess?`, `onError?`, `signInUrl?` |
|
|
179
|
+
| `ResetPasswordForm` | Reset password with token | `token`, `onSuccess?`, `onError?`, `signInUrl?`, `minPasswordLength?` |
|
|
180
|
+
| `VerifyEmailStatus` | Auto-verify email from URL token | `token`, `onSuccess?`, `onError?`, `signInUrl?`, `redirectDelay?` |
|
|
181
|
+
| `ChangePasswordForm` | Change password (authenticated) | `onSuccess?`, `onError?`, `minPasswordLength?` |
|
|
154
182
|
|
|
155
183
|
### Navigation
|
|
156
184
|
|
|
157
|
-
| Component | Description
|
|
158
|
-
|
|
159
|
-
| `SignInButton` | Link/button to your sign-in page
|
|
160
|
-
| `SignUpButton` | Link/button to your sign-up page
|
|
161
|
-
| `UserButton` | Avatar dropdown with user info and sign-out
|
|
162
|
-
| `OrgSwitcher` | Dropdown to switch between organizations
|
|
185
|
+
| Component | Description | Props |
|
|
186
|
+
|----------------|---------------------------------------------|------------------------------------|
|
|
187
|
+
| `SignInButton` | Link/button to your sign-in page | `className?`, `children?`, `href?` |
|
|
188
|
+
| `SignUpButton` | Link/button to your sign-up page | `className?`, `children?`, `href?` |
|
|
189
|
+
| `UserButton` | Avatar dropdown with user info and sign-out | — (uses context) |
|
|
190
|
+
| `OrgSwitcher` | Dropdown to switch between organizations | — (uses context) |
|
|
163
191
|
|
|
164
192
|
### Protection & Verification
|
|
165
193
|
|
|
166
|
-
| Component | Description | Props
|
|
167
|
-
|
|
168
|
-
| `ProtectedRoute` | Redirects unauthenticated users to sign-in | `children`, `fallback?`
|
|
169
|
-
| `VerifiedBadge` | Displays MCP/agent verification status | `type: 'mcp' \| 'agent'`, `identifier`, `label?`
|
|
194
|
+
| Component | Description | Props |
|
|
195
|
+
|------------------|--------------------------------------------|--------------------------------------------------|
|
|
196
|
+
| `ProtectedRoute` | Redirects unauthenticated users to sign-in | `children`, `fallback?` |
|
|
197
|
+
| `VerifiedBadge` | Displays MCP/agent verification status | `type: 'mcp' \| 'agent'`, `identifier`, `label?` |
|
|
198
|
+
|
|
199
|
+
### Example: Password Reset Flow
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
// app/forgot-password/page.tsx
|
|
203
|
+
import { ForgotPasswordForm } from '@githat/nextjs';
|
|
204
|
+
|
|
205
|
+
export default function ForgotPasswordPage() {
|
|
206
|
+
return (
|
|
207
|
+
<div style={{ maxWidth: 400, margin: '80px auto' }}>
|
|
208
|
+
<ForgotPasswordForm signInUrl="/sign-in" />
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// app/reset-password/page.tsx
|
|
216
|
+
'use client';
|
|
217
|
+
|
|
218
|
+
import { ResetPasswordForm } from '@githat/nextjs';
|
|
219
|
+
import { useSearchParams } from 'next/navigation';
|
|
220
|
+
|
|
221
|
+
export default function ResetPasswordPage() {
|
|
222
|
+
const searchParams = useSearchParams();
|
|
223
|
+
const token = searchParams.get('token') || '';
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div style={{ maxWidth: 400, margin: '80px auto' }}>
|
|
227
|
+
<ResetPasswordForm token={token} signInUrl="/sign-in" />
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Example: Email Verification
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
// app/verify-email/page.tsx
|
|
237
|
+
'use client';
|
|
238
|
+
|
|
239
|
+
import { VerifyEmailStatus } from '@githat/nextjs';
|
|
240
|
+
import { useSearchParams } from 'next/navigation';
|
|
241
|
+
|
|
242
|
+
export default function VerifyEmailPage() {
|
|
243
|
+
const searchParams = useSearchParams();
|
|
244
|
+
const token = searchParams.get('token') || '';
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<div style={{ maxWidth: 400, margin: '80px auto' }}>
|
|
248
|
+
<VerifyEmailStatus token={token} signInUrl="/sign-in" />
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
170
253
|
|
|
171
254
|
### Example: Protected Dashboard
|
|
172
255
|
|
|
@@ -255,14 +338,30 @@ export function CustomLogin() {
|
|
|
255
338
|
|
|
256
339
|
### `useGitHat()`
|
|
257
340
|
|
|
258
|
-
Access the GitHat API client for authenticated requests, org management, and
|
|
341
|
+
Access the GitHat API client for authenticated requests, org management, password reset, email verification, and more.
|
|
259
342
|
|
|
260
343
|
```tsx
|
|
261
344
|
const {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
345
|
+
// API Client
|
|
346
|
+
fetch, // <T>(path: string, init?: RequestInit) => Promise<T>
|
|
347
|
+
getUserOrgs, // () => Promise<{ orgs: GitHatOrg[] }>
|
|
348
|
+
|
|
349
|
+
// MCP & Agent Verification
|
|
350
|
+
verifyMCP, // (domain: string) => Promise<{ verified: boolean }>
|
|
351
|
+
verifyAgent, // (wallet: string) => Promise<{ verified: boolean }>
|
|
352
|
+
|
|
353
|
+
// Organization Metadata
|
|
354
|
+
getOrgMetadata, // () => Promise<OrgMetadata>
|
|
355
|
+
updateOrgMetadata, // (updates: OrgMetadata) => Promise<OrgMetadata>
|
|
356
|
+
|
|
357
|
+
// Password Management
|
|
358
|
+
forgotPassword, // (email: string) => Promise<{ success: boolean }>
|
|
359
|
+
resetPassword, // (token: string, newPassword: string) => Promise<{ success: boolean }>
|
|
360
|
+
changePassword, // (currentPassword: string, newPassword: string) => Promise<{ success: boolean }>
|
|
361
|
+
|
|
362
|
+
// Email Verification
|
|
363
|
+
verifyEmail, // (token: string) => Promise<{ success: boolean }>
|
|
364
|
+
resendVerificationEmail, // (email: string) => Promise<{ success: boolean }>
|
|
266
365
|
} = useGitHat();
|
|
267
366
|
```
|
|
268
367
|
|
|
@@ -302,6 +401,143 @@ const { fetch } = useGitHat();
|
|
|
302
401
|
const apps = await fetch<{ apps: App[] }>('/user/apps');
|
|
303
402
|
```
|
|
304
403
|
|
|
404
|
+
#### Example: Organization metadata
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
const { getOrgMetadata, updateOrgMetadata } = useGitHat();
|
|
408
|
+
|
|
409
|
+
// Read metadata
|
|
410
|
+
const meta = await getOrgMetadata();
|
|
411
|
+
console.log(meta.stripeAccountId);
|
|
412
|
+
|
|
413
|
+
// Update metadata (requires admin or owner role)
|
|
414
|
+
await updateOrgMetadata({ stripeAccountId: 'acct_xxx', features: ['pos'] });
|
|
415
|
+
|
|
416
|
+
// Delete a key by setting it to null
|
|
417
|
+
await updateOrgMetadata({ oldKey: null });
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Example: Password reset flow (programmatic)
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
const { forgotPassword, resetPassword, changePassword } = useGitHat();
|
|
424
|
+
|
|
425
|
+
// Request reset email
|
|
426
|
+
await forgotPassword('user@example.com');
|
|
427
|
+
|
|
428
|
+
// Reset with token (from email link)
|
|
429
|
+
await resetPassword(token, 'NewSecurePassword123!');
|
|
430
|
+
|
|
431
|
+
// Change password (authenticated user)
|
|
432
|
+
await changePassword('currentPassword', 'newSecurePassword123!');
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### Example: Email verification (programmatic)
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
const { verifyEmail, resendVerificationEmail } = useGitHat();
|
|
439
|
+
|
|
440
|
+
// Verify with token from email link
|
|
441
|
+
await verifyEmail(token);
|
|
442
|
+
|
|
443
|
+
// Resend verification email
|
|
444
|
+
await resendVerificationEmail('user@example.com');
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### `useData()`
|
|
448
|
+
|
|
449
|
+
Access GitHat's Customer Data API for storing app data in managed DynamoDB. Must be used within a `<GitHatProvider>`.
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
const {
|
|
453
|
+
put, // <T>(collection: string, data: T) => Promise<PutResult<T>>
|
|
454
|
+
get, // <T>(collection: string, id: string) => Promise<T | null>
|
|
455
|
+
query, // <T>(collection: string, options?: QueryOptions) => Promise<QueryResult<T>>
|
|
456
|
+
remove, // (collection: string, id: string) => Promise<DeleteResult>
|
|
457
|
+
batch, // (collection: string, operations: BatchOperation[]) => Promise<BatchResult>
|
|
458
|
+
} = useData();
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### Example: CRUD operations
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
'use client';
|
|
465
|
+
|
|
466
|
+
import { useData } from '@githat/nextjs';
|
|
467
|
+
|
|
468
|
+
interface Order {
|
|
469
|
+
id: string;
|
|
470
|
+
amount: number;
|
|
471
|
+
status: 'pending' | 'completed' | 'cancelled';
|
|
472
|
+
createdAt: string;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function OrderManager() {
|
|
476
|
+
const { put, get, query, remove, batch } = useData();
|
|
477
|
+
|
|
478
|
+
// Create or update an item
|
|
479
|
+
const createOrder = async () => {
|
|
480
|
+
const result = await put<Order>('orders', {
|
|
481
|
+
id: 'order_123',
|
|
482
|
+
amount: 99.99,
|
|
483
|
+
status: 'pending',
|
|
484
|
+
createdAt: new Date().toISOString(),
|
|
485
|
+
});
|
|
486
|
+
console.log(result.created ? 'Created' : 'Updated');
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// Get a single item
|
|
490
|
+
const getOrder = async () => {
|
|
491
|
+
const order = await get<Order>('orders', 'order_123');
|
|
492
|
+
if (order) {
|
|
493
|
+
console.log(`Order: $${order.amount}`);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Query with filters and pagination
|
|
498
|
+
const getPendingOrders = async () => {
|
|
499
|
+
const { items, nextCursor } = await query<Order>('orders', {
|
|
500
|
+
filter: { status: 'pending' },
|
|
501
|
+
limit: 20,
|
|
502
|
+
});
|
|
503
|
+
console.log(`Found ${items.length} pending orders`);
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// Delete an item
|
|
507
|
+
const deleteOrder = async () => {
|
|
508
|
+
await remove('orders', 'order_123');
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Batch operations (max 100 per request)
|
|
512
|
+
const bulkUpdate = async () => {
|
|
513
|
+
await batch('orders', [
|
|
514
|
+
{ type: 'put', id: 'order_1', data: { amount: 50, status: 'completed' } },
|
|
515
|
+
{ type: 'put', id: 'order_2', data: { amount: 75, status: 'pending' } },
|
|
516
|
+
{ type: 'delete', id: 'order_3' },
|
|
517
|
+
]);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<div>
|
|
522
|
+
<button onClick={createOrder}>Create Order</button>
|
|
523
|
+
<button onClick={getOrder}>Get Order</button>
|
|
524
|
+
<button onClick={getPendingOrders}>List Pending</button>
|
|
525
|
+
<button onClick={deleteOrder}>Delete Order</button>
|
|
526
|
+
<button onClick={bulkUpdate}>Bulk Update</button>
|
|
527
|
+
</div>
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Data API Tier Limits
|
|
533
|
+
|
|
534
|
+
| Tier | Max Items |
|
|
535
|
+
|------------|-----------|
|
|
536
|
+
| Free | 1,000 |
|
|
537
|
+
| Basic | 10,000 |
|
|
538
|
+
| Pro | 100,000 |
|
|
539
|
+
| Enterprise | Unlimited |
|
|
540
|
+
|
|
305
541
|
---
|
|
306
542
|
|
|
307
543
|
## Middleware
|
|
@@ -309,13 +545,13 @@ const apps = await fetch<{ apps: App[] }>('/user/apps');
|
|
|
309
545
|
The `authMiddleware` function protects your Next.js routes at the edge. Unauthenticated users are redirected to your sign-in page with a `redirect_url` query parameter for post-login redirect.
|
|
310
546
|
|
|
311
547
|
```ts
|
|
312
|
-
// middleware.ts
|
|
548
|
+
// middleware.ts (Next.js 14-15)
|
|
313
549
|
import { authMiddleware } from '@githat/nextjs/middleware';
|
|
314
550
|
|
|
315
551
|
export default authMiddleware({
|
|
316
552
|
publicRoutes: ['/', '/sign-in', '/sign-up', '/pricing'],
|
|
317
|
-
signInUrl: '/sign-in',
|
|
318
|
-
|
|
553
|
+
signInUrl: '/sign-in',
|
|
554
|
+
injectHeaders: true, // Optional: inject x-githat-* headers
|
|
319
555
|
});
|
|
320
556
|
|
|
321
557
|
export const config = {
|
|
@@ -325,13 +561,193 @@ export const config = {
|
|
|
325
561
|
|
|
326
562
|
### Middleware Options
|
|
327
563
|
|
|
328
|
-
| Option
|
|
329
|
-
|
|
330
|
-
| `publicRoutes`
|
|
331
|
-
| `signInUrl`
|
|
332
|
-
| `
|
|
564
|
+
| Option | Type | Default | Description |
|
|
565
|
+
|--------------------|------------|-------------------------|------------------------------------------------------|
|
|
566
|
+
| `publicRoutes` | `string[]` | `['/']` | Routes accessible without authentication |
|
|
567
|
+
| `signInUrl` | `string` | `'/sign-in'` | Where to redirect unauthenticated users |
|
|
568
|
+
| `tokenCookie` | `string` | `'githat_access'` | Cookie name for httpOnly access token |
|
|
569
|
+
| `legacyTokenCookie`| `string` | `'githat_access_token'` | Legacy cookie name (localStorage bridge) |
|
|
570
|
+
| `injectHeaders` | `boolean` | `false` | Inject `x-githat-*` headers into requests |
|
|
571
|
+
| `secretKey` | `string` | — | Secret key for local JWT verification (recommended) |
|
|
572
|
+
|
|
573
|
+
### Header Injection
|
|
574
|
+
|
|
575
|
+
When `injectHeaders: true`, the middleware decodes the JWT and adds these headers to downstream requests:
|
|
576
|
+
|
|
577
|
+
| Header | Value |
|
|
578
|
+
|---------------------|----------------------------|
|
|
579
|
+
| `x-githat-user-id` | User's unique ID |
|
|
580
|
+
| `x-githat-email` | User's email address |
|
|
581
|
+
| `x-githat-org-id` | Current organization ID |
|
|
582
|
+
| `x-githat-org-slug` | Current organization slug |
|
|
583
|
+
| `x-githat-role` | User's role (owner/admin/member) |
|
|
584
|
+
|
|
585
|
+
This allows API routes to access user info without re-verifying the token.
|
|
333
586
|
|
|
334
|
-
>
|
|
587
|
+
> **⚠️ Important: API routes are NOT protected by the middleware/proxy.** Both `authMiddleware` and `authProxy` automatically skip all `/api` routes (`pathname.startsWith('/api')` → pass-through). This is by design — API routes should use `withAuth()` or `getAuth()` from `@githat/nextjs/server` to verify tokens. See [Server-Side Authentication](#server-side-authentication) below.
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Next.js 16+ Proxy
|
|
592
|
+
|
|
593
|
+
Next.js 16 renamed `middleware.ts` to `proxy.ts` and the export from `export default middleware` to `export const proxy`. Use `authProxy` for Next.js 16+:
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
// proxy.ts (Next.js 16+)
|
|
597
|
+
import { authProxy } from '@githat/nextjs/proxy';
|
|
598
|
+
|
|
599
|
+
export const proxy = authProxy({
|
|
600
|
+
publicRoutes: ['/', '/about', '/pricing', '/sign-in', '/sign-up'],
|
|
601
|
+
signInUrl: '/sign-in',
|
|
602
|
+
injectHeaders: true,
|
|
603
|
+
secretKey: process.env.GITHAT_SECRET_KEY, // Recommended
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
export const config = {
|
|
607
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
608
|
+
};
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
The `authProxy` function accepts the same options as `authMiddleware`.
|
|
612
|
+
|
|
613
|
+
> **⚠️ Important: API routes are NOT protected by `authProxy` or `authMiddleware`.** Both automatically skip all `/api` routes. You **must** use `withAuth()` or `getAuth()` from `@githat/nextjs/server` to protect your API route handlers. See below.
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## Server-Side Authentication
|
|
618
|
+
|
|
619
|
+
The `@githat/nextjs/server` module provides utilities for server-side token verification in API routes and middleware.
|
|
620
|
+
|
|
621
|
+
### Import
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
import {
|
|
625
|
+
verifyToken,
|
|
626
|
+
getAuth,
|
|
627
|
+
withAuth,
|
|
628
|
+
getOrgMetadata,
|
|
629
|
+
updateOrgMetadata,
|
|
630
|
+
COOKIE_NAMES,
|
|
631
|
+
type AuthPayload,
|
|
632
|
+
type VerifyOptions,
|
|
633
|
+
} from '@githat/nextjs/server';
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### `verifyToken(token, options?)`
|
|
637
|
+
|
|
638
|
+
Verify a JWT token and return the decoded auth payload.
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
// Local verification (fast, ~1ms) — recommended for production
|
|
642
|
+
const auth = await verifyToken(token, {
|
|
643
|
+
secretKey: process.env.GITHAT_SECRET_KEY,
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// API-based verification (simpler setup, ~50-100ms)
|
|
647
|
+
const auth = await verifyToken(token);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
Returns `AuthPayload`:
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
interface AuthPayload {
|
|
654
|
+
userId: string;
|
|
655
|
+
email: string;
|
|
656
|
+
orgId: string | null;
|
|
657
|
+
orgSlug: string | null;
|
|
658
|
+
role: 'owner' | 'admin' | 'member' | null;
|
|
659
|
+
tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### `getAuth(request, options?)`
|
|
664
|
+
|
|
665
|
+
Extract and verify the auth token from a Next.js request. Checks cookies first, then Authorization header.
|
|
666
|
+
|
|
667
|
+
```ts
|
|
668
|
+
// app/api/orders/route.ts
|
|
669
|
+
import { getAuth } from '@githat/nextjs/server';
|
|
670
|
+
|
|
671
|
+
export async function GET(request: Request) {
|
|
672
|
+
const auth = await getAuth(request, {
|
|
673
|
+
secretKey: process.env.GITHAT_SECRET_KEY,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
if (!auth) {
|
|
677
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// auth.userId, auth.orgId, auth.role available
|
|
681
|
+
return Response.json({ userId: auth.userId });
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### `withAuth(handler, options?)`
|
|
686
|
+
|
|
687
|
+
Wrap an API route handler with authentication. The handler only runs if the request has a valid token. **This is required for every protected API route** — the middleware/proxy does not protect `/api` routes.
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
// app/api/orders/route.ts
|
|
691
|
+
import { withAuth } from '@githat/nextjs/server';
|
|
692
|
+
|
|
693
|
+
export const GET = withAuth(
|
|
694
|
+
async (request, auth) => {
|
|
695
|
+
// auth is guaranteed to be valid here
|
|
696
|
+
const orders = await db.orders.findMany({
|
|
697
|
+
where: { orgId: auth.orgId },
|
|
698
|
+
});
|
|
699
|
+
return Response.json({ orders });
|
|
700
|
+
},
|
|
701
|
+
{ secretKey: process.env.GITHAT_SECRET_KEY }
|
|
702
|
+
);
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
#### Custom unauthorized response
|
|
706
|
+
|
|
707
|
+
```ts
|
|
708
|
+
export const GET = withAuth(
|
|
709
|
+
async (request, auth) => {
|
|
710
|
+
return Response.json({ userId: auth.userId });
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
secretKey: process.env.GITHAT_SECRET_KEY,
|
|
714
|
+
onUnauthorized: () => Response.redirect('/sign-in'),
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### `getOrgMetadata(orgId, options)` / `updateOrgMetadata(orgId, metadata, options)`
|
|
720
|
+
|
|
721
|
+
Server-side org metadata operations.
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
import { getOrgMetadata, updateOrgMetadata } from '@githat/nextjs/server';
|
|
725
|
+
|
|
726
|
+
// Get metadata
|
|
727
|
+
const meta = await getOrgMetadata(orgId, {
|
|
728
|
+
token: accessToken,
|
|
729
|
+
apiUrl: 'https://api.githat.io',
|
|
730
|
+
});
|
|
731
|
+
console.log(meta.stripeAccountId);
|
|
732
|
+
|
|
733
|
+
// Update metadata
|
|
734
|
+
await updateOrgMetadata(
|
|
735
|
+
orgId,
|
|
736
|
+
{ stripeAccountId: 'acct_xxx' },
|
|
737
|
+
{ token: accessToken }
|
|
738
|
+
);
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### `COOKIE_NAMES`
|
|
742
|
+
|
|
743
|
+
Constants for cookie names used by the SDK.
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
import { COOKIE_NAMES } from '@githat/nextjs/server';
|
|
747
|
+
|
|
748
|
+
// COOKIE_NAMES.accessToken = 'githat_access'
|
|
749
|
+
// COOKIE_NAMES.refreshToken = 'githat_refresh'
|
|
750
|
+
```
|
|
335
751
|
|
|
336
752
|
---
|
|
337
753
|
|
|
@@ -341,20 +757,36 @@ export const config = {
|
|
|
341
757
|
|
|
342
758
|
Pass this to `<GitHatProvider config={...}>`:
|
|
343
759
|
|
|
344
|
-
| Property
|
|
345
|
-
|
|
346
|
-
| `publishableKey`
|
|
347
|
-
| `apiUrl`
|
|
348
|
-
| `signInUrl`
|
|
349
|
-
| `signUpUrl`
|
|
350
|
-
| `afterSignInUrl`
|
|
351
|
-
| `afterSignOutUrl
|
|
760
|
+
| Property | Type | Required | Default | Description |
|
|
761
|
+
|------------------|--------------------------------|----------|---------------------------|--------------------------------------|
|
|
762
|
+
| `publishableKey` | `string` | Yes | — | Your GitHat publishable key |
|
|
763
|
+
| `apiUrl` | `string` | No | `'https://api.githat.io'` | API base URL |
|
|
764
|
+
| `signInUrl` | `string` | No | `'/sign-in'` | Sign-in page route |
|
|
765
|
+
| `signUpUrl` | `string` | No | `'/sign-up'` | Sign-up page route |
|
|
766
|
+
| `afterSignInUrl` | `string` | No | `'/'` | Redirect after sign-in |
|
|
767
|
+
| `afterSignOutUrl`| `string` | No | `'/'` | Redirect after sign-out |
|
|
768
|
+
| `tokenStorage` | `'localStorage' \| 'cookie'` | No | `'localStorage'` | Token storage mode (see below) |
|
|
769
|
+
|
|
770
|
+
### Token Storage Modes
|
|
771
|
+
|
|
772
|
+
| Mode | Description |
|
|
773
|
+
|----------------|--------------------------------------------------------------------------|
|
|
774
|
+
| `localStorage` | Default. Tokens stored in browser localStorage. Simple client-side setup. |
|
|
775
|
+
| `cookie` | Tokens stored in httpOnly cookies. More secure, XSS-resistant. Better for SSR. |
|
|
776
|
+
|
|
777
|
+
When using `cookie` mode:
|
|
778
|
+
|
|
779
|
+
- Login/refresh automatically set httpOnly cookies
|
|
780
|
+
- SDK reads auth state from cookies server-side
|
|
781
|
+
- Better security for apps with server-side rendering
|
|
782
|
+
- Required for `getAuth()` and `withAuth()` server utilities
|
|
352
783
|
|
|
353
784
|
### Environment Variables
|
|
354
785
|
|
|
355
786
|
```env
|
|
356
787
|
# .env.local
|
|
357
|
-
NEXT_PUBLIC_GITHAT_KEY=
|
|
788
|
+
NEXT_PUBLIC_GITHAT_KEY=pk_live_your_key_here
|
|
789
|
+
GITHAT_SECRET_KEY=sk_live_your_secret_key # For server-side verification
|
|
358
790
|
```
|
|
359
791
|
|
|
360
792
|
### React/Vite Setup
|
|
@@ -374,7 +806,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
374
806
|
);
|
|
375
807
|
```
|
|
376
808
|
|
|
377
|
-
> **Note:** The middleware
|
|
809
|
+
> **Note:** The middleware/proxy exports are Next.js-specific. For Vite/CRA apps, use `<ProtectedRoute>` for client-side route protection.
|
|
378
810
|
|
|
379
811
|
---
|
|
380
812
|
|
|
@@ -386,7 +818,7 @@ Import the built-in dark theme:
|
|
|
386
818
|
import '@githat/nextjs/styles';
|
|
387
819
|
```
|
|
388
820
|
|
|
389
|
-
This provides styled defaults for all components (`SignInForm`, `SignUpForm`, `UserButton`, `OrgSwitcher`, etc.). All class names are prefixed with `githat-` to avoid conflicts.
|
|
821
|
+
This provides styled defaults for all components (`SignInForm`, `SignUpForm`, `ForgotPasswordForm`, `ResetPasswordForm`, `VerifyEmailStatus`, `ChangePasswordForm`, `UserButton`, `OrgSwitcher`, etc.). All class names are prefixed with `githat-` to avoid conflicts.
|
|
390
822
|
|
|
391
823
|
### Customization
|
|
392
824
|
|
|
@@ -416,6 +848,7 @@ Every export is fully typed. Import types directly:
|
|
|
416
848
|
|
|
417
849
|
```tsx
|
|
418
850
|
import type {
|
|
851
|
+
// Core types
|
|
419
852
|
GitHatUser,
|
|
420
853
|
GitHatOrg,
|
|
421
854
|
GitHatConfig,
|
|
@@ -424,6 +857,30 @@ import type {
|
|
|
424
857
|
SignUpData,
|
|
425
858
|
SignUpResult,
|
|
426
859
|
GitHatContextValue,
|
|
860
|
+
|
|
861
|
+
// Password & Email
|
|
862
|
+
PasswordResetResult,
|
|
863
|
+
EmailVerificationResult,
|
|
864
|
+
|
|
865
|
+
// Server-side
|
|
866
|
+
AuthPayload,
|
|
867
|
+
VerifyOptions,
|
|
868
|
+
OrgMetadata,
|
|
869
|
+
WithAuthOptions,
|
|
870
|
+
AuthenticatedHandler,
|
|
871
|
+
|
|
872
|
+
// Customer Data API
|
|
873
|
+
DataItem,
|
|
874
|
+
QueryOptions,
|
|
875
|
+
QueryResult,
|
|
876
|
+
PutResult,
|
|
877
|
+
DeleteResult,
|
|
878
|
+
BatchOperation,
|
|
879
|
+
BatchResult,
|
|
880
|
+
|
|
881
|
+
// Middleware/Proxy
|
|
882
|
+
AuthHandlerOptions,
|
|
883
|
+
AuthProxyOptions,
|
|
427
884
|
} from '@githat/nextjs';
|
|
428
885
|
```
|
|
429
886
|
|
|
@@ -446,6 +903,15 @@ interface GitHatOrg {
|
|
|
446
903
|
tier: string;
|
|
447
904
|
}
|
|
448
905
|
|
|
906
|
+
interface AuthPayload {
|
|
907
|
+
userId: string;
|
|
908
|
+
email: string;
|
|
909
|
+
orgId: string | null;
|
|
910
|
+
orgSlug: string | null;
|
|
911
|
+
role: 'owner' | 'admin' | 'member' | null;
|
|
912
|
+
tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
|
|
913
|
+
}
|
|
914
|
+
|
|
449
915
|
interface SignUpData {
|
|
450
916
|
email: string;
|
|
451
917
|
password: string;
|
|
@@ -469,7 +935,77 @@ Use the companion CLI to scaffold a complete Next.js or React app with GitHat au
|
|
|
469
935
|
npx create-githat-app my-app
|
|
470
936
|
```
|
|
471
937
|
|
|
472
|
-
This generates a full project
|
|
938
|
+
This generates a full project connected to GitHat's hosted backend — sign-in, sign-up, password reset, email verification, dashboard, settings, and team management pages. No backend to deploy.
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
## Known Limitations
|
|
943
|
+
|
|
944
|
+
These are things GitHat does **not** provide today. They are not blockers — each has a recommended workaround.
|
|
945
|
+
|
|
946
|
+
### No webhooks / event system
|
|
947
|
+
|
|
948
|
+
GitHat does not send server-side notifications when users register, verify email, or delete accounts. Use `onSuccess` callbacks on form components to call your own API endpoint instead:
|
|
949
|
+
|
|
950
|
+
```tsx
|
|
951
|
+
<SignUpForm
|
|
952
|
+
onSuccess={async (result) => {
|
|
953
|
+
// result: { requiresVerification: boolean, email: string }
|
|
954
|
+
// Call your API to sync user, send welcome email, etc.
|
|
955
|
+
await fetch('/api/on-signup', {
|
|
956
|
+
method: 'POST',
|
|
957
|
+
headers: { 'Content-Type': 'application/json' },
|
|
958
|
+
body: JSON.stringify({ email: result.email }),
|
|
959
|
+
});
|
|
960
|
+
}}
|
|
961
|
+
/>
|
|
962
|
+
|
|
963
|
+
<VerifyEmailStatus
|
|
964
|
+
token={token}
|
|
965
|
+
onSuccess={async () => {
|
|
966
|
+
// Email verified — trigger your post-verification logic
|
|
967
|
+
await fetch('/api/on-email-verified', { method: 'POST' });
|
|
968
|
+
}}
|
|
969
|
+
/>
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
### No user metadata API
|
|
973
|
+
|
|
974
|
+
Org metadata exists (`getOrgMetadata` / `updateOrgMetadata`), but there is no per-user equivalent. Use the [Customer Data API](https://githat.io/docs/data) or your own database:
|
|
975
|
+
|
|
976
|
+
```tsx
|
|
977
|
+
import { useData, useAuth } from '@githat/nextjs';
|
|
978
|
+
|
|
979
|
+
function useUserPreferences() {
|
|
980
|
+
const { user } = useAuth();
|
|
981
|
+
const { put, get } = useData();
|
|
982
|
+
|
|
983
|
+
const getPreferences = async () => {
|
|
984
|
+
if (!user) return null;
|
|
985
|
+
return get('user-preferences', user.id);
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
const updatePreferences = async (prefs: Partial<Preferences>) => {
|
|
989
|
+
if (!user) throw new Error('Not authenticated');
|
|
990
|
+
const current = await getPreferences() || { theme: 'dark', notifications: true };
|
|
991
|
+
return put('user-preferences', { id: user.id, ...current, ...prefs });
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
return { getPreferences, updatePreferences };
|
|
995
|
+
}
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
### No bulk user import
|
|
999
|
+
|
|
1000
|
+
There is no self-service API to import existing users from another auth provider. For beta or new apps, users re-register. For production migrations, contact us for enterprise import.
|
|
1001
|
+
|
|
1002
|
+
### API routes not protected by middleware
|
|
1003
|
+
|
|
1004
|
+
`authMiddleware` and `authProxy` skip `/api` routes by design. Use `withAuth()` or `getAuth()` per-route — see [Server-Side Authentication](#server-side-authentication).
|
|
1005
|
+
|
|
1006
|
+
### Migrating from Clerk?
|
|
1007
|
+
|
|
1008
|
+
See the [Clerk Migration Guide](https://githat.io/docs/clerk-migration) for a step-by-step walkthrough of switching from Clerk to GitHat with zero downtime.
|
|
473
1009
|
|
|
474
1010
|
---
|
|
475
1011
|
|