@ouim/logto-authkit 0.3.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.
package/README.md ADDED
@@ -0,0 +1,823 @@
1
+ # @ouim/logto-authkit
2
+
3
+ `@ouim/logto-authkit` is a batteries-included auth toolkit for Logto-powered React apps.
4
+
5
+ > Migration notice
6
+ > This package replaces `@ouim/simple-logto`.
7
+ > Import replacements:
8
+ > `@ouim/simple-logto` -> `@ouim/logto-authkit`
9
+ > `@ouim/simple-logto/backend` -> `@ouim/logto-authkit/server`
10
+ > `@ouim/simple-logto/bundler-config` -> `@ouim/logto-authkit/bundler-config`
11
+ > There will be no compatibility shim for `/backend`.
12
+
13
+ It wraps `@logto/react` with the pieces most teams end up building anyway:
14
+
15
+ - a higher-level React provider and hook
16
+ - ready-made sign-in, callback, and account UI
17
+ - backend token verification for Node and Next.js
18
+ - bundler fixes for the `jose` edge cases that usually slow setup down
19
+
20
+ If you want Logto without re-assembling the same frontend and backend auth plumbing from scratch, this package is the opinionated fast path.
21
+
22
+ > Wanna try it? checkout the library live on: [tstore.ouim.me](https://tstore.ouim.me/) & [mocka.ouim.me](https://mocka.ouim.me/)
23
+
24
+ ## What It Actually Ships
25
+
26
+ ### Frontend
27
+
28
+ - `AuthProvider` for wiring Logto into your app with less boilerplate
29
+ - `useAuth` for user state, auth actions, and route protection patterns
30
+ - `UserCenter` for a production-ready account dropdown
31
+ - `CallbackPage` for redirect and popup callback handling
32
+ - `SignInPage` for dedicated `/signin` routes
33
+ - `SignInButton` for drop-in sign-in triggers
34
+ - popup sign-in support
35
+ - guest mode support
36
+ - custom navigation support for SPA routers
37
+
38
+ ### Backend
39
+
40
+ - JWT verification against Logto JWKS
41
+ - Express middleware via `createExpressAuthMiddleware`
42
+ - Next.js request verification via `verifyNextAuth`
43
+ - generic `verifyAuth` helper for custom servers and handlers
44
+ - optional scope checks
45
+ - cookie and bearer-token extraction
46
+ - guest-aware auth context support
47
+
48
+ ### Build tooling
49
+
50
+ - Vite config helpers
51
+ - Webpack config helpers
52
+ - Next.js config helpers
53
+ - a dedicated `bundler-config` entrypoint for build-time imports
54
+
55
+ ## Why We Use It
56
+
57
+ - Faster first integration: frontend and backend auth can be wired from one package.
58
+ - Better defaults: common auth screens and account UI are already handled.
59
+ - Less glue code: cookie syncing, callback handling, popup flows, and request verification are built in.
60
+ - Easier adoption: you still keep Logto underneath, so you are not boxed into a custom auth system.
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ npm install @ouim/logto-authkit @logto/react
66
+ ```
67
+
68
+ Peer dependencies:
69
+
70
+ - `react`
71
+ - `react-dom`
72
+ - `@logto/react`
73
+
74
+ ## Runtime Support
75
+
76
+ The package currently declares compatibility with:
77
+
78
+ - Node.js `18.18+`, `20.x`, `22.x`, and `24.x`
79
+ - React `17.x`, `18.x`, and `19.x`
80
+ - `@logto/react` `3.x` and `4.x`
81
+
82
+ GitHub Actions runs the default validation gate on Node `24`, which is the current Active LTS line as of March 29, 2026. The published `engines` field expresses the broader compatibility policy, while CI stays intentionally lighter for day-to-day pull requests.
83
+
84
+ ## Examples
85
+
86
+ ### Vite + React + Express
87
+
88
+ The [example_app/](./example_app/) directory contains a complete Vite React playground:
89
+
90
+ - **Frontend:** `AuthProvider`, `useAuth`, `UserCenter`, popup sign-in, guest mode
91
+ - **Backend:** Express.js with `createExpressAuthMiddleware`, scope checks, CSRF protection
92
+ - **Setup:** See [example_app/README.md](./example_app/README.md)
93
+
94
+ ### Next.js App Router
95
+
96
+ [examples/nextjs-app-router/](./examples/nextjs-app-router/) demonstrates:
97
+
98
+ - `AuthProvider` wrapping the app
99
+ - Sign-in route with `SignInPage`
100
+ - Callback route with `CallbackPage`
101
+ - Protected API route using `verifyNextAuth`
102
+ - Role-based authorization
103
+ - See [examples/nextjs-app-router/README.md](./examples/nextjs-app-router/README.md)
104
+
105
+ ### Smoke Tests
106
+
107
+ [smoke-fixtures/](./smoke-fixtures/) contains automated tests for:
108
+
109
+ - Vite + React integration
110
+ - Next.js App Router integration
111
+ - React Router integration
112
+ - Node.js backend verification
113
+ - Bundler compatibility (CommonJS + ESM)
114
+
115
+ These validate the package works across different bundler and framework combinations.
116
+
117
+ ### Additional Notes
118
+
119
+ - [docs/notes/](./docs/notes/) contains working implementation notes and architecture decisions
120
+ - [AGENTS.md](./AGENTS.md) documents how AI agents can contribute to this project
121
+
122
+ ## Quick Start
123
+
124
+ ### 1. Wrap your app
125
+
126
+ ```tsx
127
+ import { AuthProvider } from '@ouim/logto-authkit'
128
+
129
+ const logtoConfig = {
130
+ endpoint: 'https://your-tenant.logto.app',
131
+ appId: 'your-app-id',
132
+ resources: ['https://your-api.example.com'],
133
+ }
134
+
135
+ export function AppProviders({ children }: { children: React.ReactNode }) {
136
+ return (
137
+ <AuthProvider config={logtoConfig} callbackUrl="http://localhost:3000/callback">
138
+ {children}
139
+ </AuthProvider>
140
+ )
141
+ }
142
+ ```
143
+
144
+ ### 2. Add the callback route
145
+
146
+ ```tsx
147
+ import { CallbackPage } from '@ouim/logto-authkit'
148
+
149
+ export default function CallbackRoute() {
150
+ return <CallbackPage />
151
+ }
152
+ ```
153
+
154
+ ### 3. Add a sign-in entry point
155
+
156
+ ```tsx
157
+ import { SignInPage } from '@ouim/logto-authkit'
158
+
159
+ export default function SignInRoute() {
160
+ return <SignInPage />
161
+ }
162
+ ```
163
+
164
+ ### 4. Use auth anywhere
165
+
166
+ ```tsx
167
+ import { useAuth } from '@ouim/logto-authkit'
168
+
169
+ export function Dashboard() {
170
+ const { user, isLoadingUser, signIn, signOut } = useAuth()
171
+
172
+ if (isLoadingUser) return <div>Loading...</div>
173
+ if (!user) return <button onClick={() => signIn()}>Sign in</button>
174
+
175
+ return (
176
+ <div>
177
+ <p>Welcome, {user.name ?? user.id}</p>
178
+ <button onClick={() => signOut()}>Sign out</button>
179
+ </div>
180
+ )
181
+ }
182
+ ```
183
+
184
+ ### 5. Add account UI
185
+
186
+ ```tsx
187
+ import { UserCenter } from '@ouim/logto-authkit'
188
+
189
+ export function Navbar() {
190
+ return (
191
+ <nav className="flex items-center justify-between h-16 px-4 border-b">
192
+ <div className="font-bold">MyApp</div>
193
+ <UserCenter />
194
+ </nav>
195
+ )
196
+ }
197
+ ```
198
+
199
+ ![UserCenter](docs/assets/image.png)
200
+
201
+ ![UserCenter logged in](docs/assets/image-1.png)
202
+
203
+ ## Frontend API
204
+
205
+ ### `AuthProvider`
206
+
207
+ Main provider for the package. It wraps Logto, manages auth refresh, and keeps the browser cookie in sync for backend verification.
208
+
209
+ Props:
210
+
211
+ - `config`: Logto config object
212
+ - `callbackUrl?`: default auth callback URL
213
+ - `customNavigate?`: custom navigation function for React Router, Next.js, or other SPA routers
214
+ - `enablePopupSignIn?`: enables popup-based sign-in flow
215
+ - `onTokenRefresh?`: called when an already-authenticated session receives a different access token
216
+ - `onAuthError?`: called when auth loading hits a transient or definitive auth error
217
+ - `onSignOut?`: called immediately before the provider initiates local or global sign-out
218
+
219
+ Example with custom router navigation:
220
+
221
+ ```tsx
222
+ <AuthProvider config={logtoConfig} callbackUrl="/callback" customNavigate={url => router.push(url)} enablePopupSignIn>
223
+ <App />
224
+ </AuthProvider>
225
+ ```
226
+
227
+ Lifecycle callback example:
228
+
229
+ ```tsx
230
+ <AuthProvider
231
+ config={logtoConfig}
232
+ onTokenRefresh={({ expiresAt }) => analytics.track('token_refreshed', { expiresAt })}
233
+ onAuthError={({ error, isTransient }) => console.error('auth error', { message: error.message, isTransient })}
234
+ onSignOut={({ reason }) => analytics.track('signed_out', { reason })}
235
+ >
236
+ <App />
237
+ </AuthProvider>
238
+ ```
239
+
240
+ ### `useAuth`
241
+
242
+ Returns:
243
+
244
+ - `user`
245
+ - `isLoadingUser`
246
+ - `signIn`
247
+ - `signOut`
248
+ - `refreshAuth`
249
+ - `enablePopupSignIn`
250
+
251
+ You can also use it for lightweight route protection:
252
+
253
+ ```tsx
254
+ const { user } = useAuth({
255
+ middleware: 'auth',
256
+ redirectTo: '/signin',
257
+ })
258
+ ```
259
+
260
+ Guest-only route example:
261
+
262
+ ```tsx
263
+ const auth = useAuth({
264
+ middleware: 'guest',
265
+ redirectIfAuthenticated: '/dashboard',
266
+ })
267
+ ```
268
+
269
+ ### `usePermission`
270
+
271
+ Use `usePermission()` for client-side conditional rendering when the current frontend user claims already include permission data.
272
+
273
+ ```tsx
274
+ import { usePermission } from '@ouim/logto-authkit'
275
+
276
+ function AdminActions() {
277
+ const canManageUsers = usePermission('manage:users')
278
+
279
+ if (!canManageUsers) {
280
+ return null
281
+ }
282
+
283
+ return <button>Invite user</button>
284
+ }
285
+ ```
286
+
287
+ Notes:
288
+
289
+ - `usePermission()` reads the `user` object from `AuthProvider`, so it reflects frontend claims only.
290
+ - By default it checks `permissions`, then `scope`, then `scp`.
291
+ - Pass `claimKeys` if your tenant emits permissions under a custom claim name such as `https://example.com/permissions`.
292
+ - While auth state is loading the hook returns `false` so restricted UI does not flash before claims arrive.
293
+
294
+ ### `UserCenter`
295
+
296
+ Prebuilt account dropdown for navbars and app shells.
297
+
298
+ Supports:
299
+
300
+ - signed-in and signed-out states
301
+ - local sign-out by default, or global sign-out when explicitly enabled
302
+ - custom account links
303
+ - custom theme class names
304
+
305
+ ```tsx
306
+ <UserCenter
307
+ signoutCallbackUrl="/"
308
+ globalSignOut={false}
309
+ additionalPages={[
310
+ { link: '/settings', text: 'Settings' },
311
+ { link: '/billing', text: 'Billing' },
312
+ ]}
313
+ />
314
+ ```
315
+
316
+ Pass `globalSignOut={true}` only when you explicitly want the account menu to end the user's wider Logto tenant session, not just the current app session.
317
+
318
+ ### `CallbackPage`
319
+
320
+ Drop this onto your callback route to complete the Logto auth flow.
321
+
322
+ Optional props:
323
+
324
+ - `onSuccess`
325
+ - `onError`
326
+ - `loadingComponent`
327
+ - `successComponent`
328
+ - `className`
329
+
330
+ ### `SignInPage`
331
+
332
+ Use this when you want a dedicated `/signin` route that automatically initiates the auth flow. It also supports popup-based sign-in windows.
333
+
334
+ If you enable popup sign-in on `AuthProvider`, you should still define a real `/signin` route that renders `SignInPage`. The popup window navigates to that route first, and `SignInPage` is what kicks off the Logto flow inside the popup.
335
+
336
+ Optional props:
337
+
338
+ - `loadingComponent`
339
+ - `errorComponent`
340
+ - `className`
341
+
342
+ ```tsx
343
+ <SignInPage
344
+ className="min-h-screen bg-slate-50"
345
+ loadingComponent={<div>Redirecting to Logto...</div>}
346
+ errorComponent={error => <div>Could not start sign-in: {error.message}</div>}
347
+ />
348
+ ```
349
+
350
+ ### `SignInButton`
351
+
352
+ For cases where you want a reusable trigger instead of manually calling `signIn()`.
353
+
354
+ ```tsx
355
+ import { SignInButton } from '@ouim/logto-authkit'
356
+ ;<SignInButton />
357
+ ```
358
+
359
+ ## SSR And Router Boundaries
360
+
361
+ The frontend entrypoint is intentionally browser-oriented. `AuthProvider`, `useAuth`, `SignInPage`, `CallbackPage`, and `UserCenter` all rely on client-only behaviors such as cookies, `window`, popup messaging, focus events, or browser navigation.
362
+
363
+ Use these rules when integrating into SSR-capable apps:
364
+
365
+ - Render frontend auth components only from client components.
366
+ - Keep `/signin` and `/callback` as real browser routes, not server-only handlers.
367
+ - Expect the initial server render to be unauthenticated until the client hydrates and loads the Logto session.
368
+ - Do not treat server-rendered auth state from the frontend package as authoritative for backend access control; use the backend verifier helpers for that.
369
+
370
+ ### React Router
371
+
372
+ Wrap the router tree in `AuthProvider` and pass a router-aware `customNavigate` callback so auth redirects stay inside the SPA router.
373
+
374
+ ```tsx
375
+ 'use client'
376
+
377
+ import { AuthProvider, CallbackPage, SignInPage } from '@ouim/logto-authkit'
378
+ import { BrowserRouter, Route, Routes, useNavigate } from 'react-router-dom'
379
+
380
+ function AuthShell() {
381
+ const navigate = useNavigate()
382
+
383
+ return (
384
+ <AuthProvider
385
+ config={logtoConfig}
386
+ callbackUrl={`${window.location.origin}/callback`}
387
+ customNavigate={url => navigate(url)}
388
+ enablePopupSignIn
389
+ >
390
+ <Routes>
391
+ <Route path="/signin" element={<SignInPage />} />
392
+ <Route path="/callback" element={<CallbackPage />} />
393
+ <Route path="/" element={<Dashboard />} />
394
+ </Routes>
395
+ </AuthProvider>
396
+ )
397
+ }
398
+
399
+ export function App() {
400
+ return (
401
+ <BrowserRouter>
402
+ <AuthShell />
403
+ </BrowserRouter>
404
+ )
405
+ }
406
+ ```
407
+
408
+ ### Next.js App Router
409
+
410
+ Keep the auth UI behind client components and let the backend subpath handle server-side request verification.
411
+
412
+ ```tsx
413
+ // app/providers.tsx
414
+ 'use client'
415
+
416
+ import { AuthProvider } from '@ouim/logto-authkit'
417
+ import { useRouter } from 'next/navigation'
418
+
419
+ export function Providers({ children }: { children: React.ReactNode }) {
420
+ const router = useRouter()
421
+
422
+ return (
423
+ <AuthProvider config={logtoConfig} callbackUrl={`${process.env.NEXT_PUBLIC_APP_URL}/callback`} customNavigate={url => router.push(url)}>
424
+ {children}
425
+ </AuthProvider>
426
+ )
427
+ }
428
+ ```
429
+
430
+ ```tsx
431
+ // app/signin/page.tsx
432
+ 'use client'
433
+
434
+ import { SignInPage } from '@ouim/logto-authkit'
435
+
436
+ export default function SignIn() {
437
+ return <SignInPage />
438
+ }
439
+ ```
440
+
441
+ ```tsx
442
+ // app/callback/page.tsx
443
+ 'use client'
444
+
445
+ import { CallbackPage } from '@ouim/logto-authkit'
446
+
447
+ export default function Callback() {
448
+ return <CallbackPage />
449
+ }
450
+ ```
451
+
452
+ For protected server routes, route handlers, or middleware, import from `@ouim/logto-authkit/server` instead of trying to read frontend auth state during SSR.
453
+
454
+ These React Router and Next.js examples are mirrored by packed smoke fixtures in `smoke-fixtures/` so CI catches export or packaging drift against the documented integration patterns.
455
+
456
+ ## Backend API
457
+
458
+ Import backend helpers from the dedicated subpath:
459
+
460
+ ```ts
461
+ import { createExpressAuthMiddleware, hasScopes, requireScopes, verifyAuth, verifyNextAuth } from '@ouim/logto-authkit/server'
462
+ ```
463
+
464
+ ### Express middleware
465
+
466
+ `createExpressAuthMiddleware` automatically parses cookies for you, so you do not need to add `cookie-parser` yourself.
467
+
468
+ ```ts
469
+ import express from 'express'
470
+ import { createExpressAuthMiddleware } from '@ouim/logto-authkit/server'
471
+
472
+ const app = express()
473
+
474
+ const authMiddleware = createExpressAuthMiddleware({
475
+ logtoUrl: 'https://your-tenant.logto.app',
476
+ audience: 'https://your-api.example.com',
477
+ cookieName: 'logto_authtoken',
478
+ requiredScope: 'read:profile',
479
+ allowGuest: true,
480
+ })
481
+
482
+ app.get('/api/me', authMiddleware, (req, res) => {
483
+ res.json({
484
+ userId: req.auth?.userId,
485
+ isAuthenticated: req.auth?.isAuthenticated,
486
+ isGuest: req.auth?.isGuest,
487
+ })
488
+ })
489
+ ```
490
+
491
+ ### Next.js route handlers
492
+
493
+ ```ts
494
+ import { verifyNextAuth } from '@ouim/logto-authkit/server'
495
+
496
+ export async function GET(request: Request) {
497
+ const result = await verifyNextAuth(request, {
498
+ logtoUrl: process.env.LOGTO_URL!,
499
+ audience: process.env.LOGTO_AUDIENCE!,
500
+ allowGuest: false,
501
+ })
502
+
503
+ if (!result.success) {
504
+ return Response.json({ error: result.error }, { status: 401 })
505
+ }
506
+
507
+ return Response.json({
508
+ userId: result.auth.userId,
509
+ payload: result.auth.payload,
510
+ })
511
+ }
512
+ ```
513
+
514
+ ### SSR-safe backend verification pattern
515
+
516
+ For SSR frameworks, make authorization decisions on the server with the backend subpath and pass only the derived result to your rendered UI.
517
+
518
+ ```ts
519
+ import { verifyAuth } from '@ouim/logto-authkit/server'
520
+
521
+ export async function loadUserFromRequest(request: Request) {
522
+ const auth = await verifyAuth(request, {
523
+ logtoUrl: process.env.LOGTO_URL!,
524
+ audience: process.env.LOGTO_AUDIENCE!,
525
+ allowGuest: true,
526
+ })
527
+
528
+ return {
529
+ userId: auth.userId,
530
+ isAuthenticated: auth.isAuthenticated,
531
+ isGuest: auth.isGuest,
532
+ }
533
+ }
534
+ ```
535
+
536
+ ### Generic token verification
537
+
538
+ ```ts
539
+ import { verifyAuth } from '@ouim/logto-authkit/server'
540
+
541
+ const auth = await verifyAuth('your-jwt-token', {
542
+ logtoUrl: 'https://your-tenant.logto.app',
543
+ audience: 'https://your-api.example.com',
544
+ })
545
+ ```
546
+
547
+ ### Backend options
548
+
549
+ - `logtoUrl`: required
550
+ - `audience`: required for protected API resources, accepts either a single audience string or an array of allowed audiences
551
+ - `cookieName?`: defaults to `logto_authtoken`
552
+ - `requiredScope?`: rejects requests missing the given scope
553
+ - `allowGuest?`: enables guest auth fallback
554
+ - `jwksCacheTtlMs?`: overrides the default 5 minute in-memory JWKS cache TTL
555
+ - `skipJwksCache?`: bypasses the in-memory JWKS cache for a single verifier/middleware instance
556
+
557
+ ### Multi-scope and role authorization helpers
558
+
559
+ If you need more than the built-in single `requiredScope` check, use the backend authorization helpers after token verification:
560
+
561
+ ```ts
562
+ import { hasRole, hasScopes, requireRole, requireScopes, verifyAuth } from '@ouim/logto-authkit/server'
563
+
564
+ const auth = await verifyAuth(request, {
565
+ logtoUrl: process.env.LOGTO_URL!,
566
+ audience: process.env.LOGTO_AUDIENCE!,
567
+ })
568
+
569
+ if (!hasScopes(auth, ['profile:read', 'profile:write'], { mode: 'any' })) {
570
+ return Response.json({ error: 'Forbidden' }, { status: 403 })
571
+ }
572
+
573
+ requireScopes(auth, ['profile:read', 'profile:write'])
574
+
575
+ if (!hasRole(auth, 'admin')) {
576
+ return Response.json({ error: 'Forbidden' }, { status: 403 })
577
+ }
578
+
579
+ requireRole(auth, 'admin')
580
+ ```
581
+
582
+ Notes:
583
+
584
+ - `hasScopes(subject, scopes, { mode })` returns a boolean for either a raw `AuthPayload` or a full `AuthContext`.
585
+ - `requireScopes(subject, scopes, { mode })` throws an error you can map to `403`.
586
+ - `mode: 'all'` is the default; use `mode: 'any'` when any one of the scopes should be enough.
587
+ - Scope parsing follows the OAuth `scope` claim convention: a whitespace-delimited string.
588
+ - `hasRole(subject, role, { claimKeys })` and `requireRole(subject, role, { claimKeys })` check role claims without coupling the logic to Express or Next.js.
589
+ - Role helpers look for `roles` first and then `role` by default. If your Logto tenant maps roles into a custom claim, pass `claimKeys`, for example `['https://example.com/roles']`.
590
+
591
+ ### JWKS cache controls
592
+
593
+ Backend verification uses a per-process in-memory JWKS cache by default. If you need tighter control during key rotation, debugging, or unusual network setups, you can tune or clear it explicitly:
594
+
595
+ ```ts
596
+ import { clearJwksCache, invalidateJwksCache, verifyAuth } from '@ouim/logto-authkit/server'
597
+
598
+ await verifyAuth(token, {
599
+ logtoUrl: 'https://your-tenant.logto.app',
600
+ audience: 'https://your-api.example.com',
601
+ jwksCacheTtlMs: 60_000,
602
+ })
603
+
604
+ invalidateJwksCache('https://your-tenant.logto.app')
605
+ clearJwksCache()
606
+ ```
607
+
608
+ ### Auth context shape
609
+
610
+ ```ts
611
+ interface AuthContext {
612
+ userId: string | null
613
+ isAuthenticated: boolean
614
+ payload: AuthPayload | null
615
+ isGuest?: boolean
616
+ guestId?: string
617
+ }
618
+ ```
619
+
620
+ ## Bundler Config
621
+
622
+ This package includes bundler helpers for the `jose` resolution issues that often show up during Logto integration.
623
+
624
+ For build-time scripts, prefer the dedicated subpath:
625
+
626
+ ```ts
627
+ import { viteConfig, getBundlerConfig } from '@ouim/logto-authkit/bundler-config'
628
+ ```
629
+
630
+ ### Vite
631
+
632
+ ```ts
633
+ import { defineConfig } from 'vite'
634
+ import { viteConfig } from '@ouim/logto-authkit/bundler-config'
635
+
636
+ export default defineConfig({
637
+ ...viteConfig,
638
+ })
639
+ ```
640
+
641
+ ### Webpack
642
+
643
+ ```ts
644
+ import { webpackConfig } from '@ouim/logto-authkit/bundler-config'
645
+
646
+ export default {
647
+ ...webpackConfig,
648
+ }
649
+ ```
650
+
651
+ ### Next.js
652
+
653
+ ```ts
654
+ import { nextjsConfig } from '@ouim/logto-authkit/bundler-config'
655
+
656
+ const nextConfig = {
657
+ ...nextjsConfig,
658
+ }
659
+
660
+ export default nextConfig
661
+ ```
662
+
663
+ ## TypeScript
664
+
665
+ The package ships typed frontend and backend exports.
666
+
667
+ ```ts
668
+ import type {
669
+ LogtoUser,
670
+ AuthOptions,
671
+ AuthContextType,
672
+ AuthProviderProps,
673
+ CallbackPageProps,
674
+ SignInPageProps,
675
+ AdditionalPage,
676
+ SignInButtonProps,
677
+ } from '@ouim/logto-authkit'
678
+
679
+ import type {
680
+ AuthContext,
681
+ AuthPayload,
682
+ AuthorizationMode,
683
+ VerifyAuthOptions,
684
+ ExpressRequest,
685
+ ExpressResponse,
686
+ ExpressNext,
687
+ NextRequest,
688
+ NextResponse,
689
+ } from '@ouim/logto-authkit/server'
690
+ ```
691
+
692
+ ## Positioning
693
+
694
+ `@ouim/logto-authkit` is best thought of as the practical app-layer around Logto:
695
+
696
+ - Logto remains the identity platform
697
+ - `@logto/react` remains the core SDK
698
+ - this package adds the missing productized layer most app teams want on day one
699
+
700
+ If your team eventually needs lower-level control, you can still drop down to the official Logto APIs without throwing your whole auth model away.
701
+
702
+ ## Security & Advanced Features
703
+
704
+ This package includes **security hardening** that most auth libraries leave to you:
705
+
706
+ - **CSRF Protection** — Double-submit cookie pattern for backend routes
707
+ - **Cookie Security** — All auth/guest cookies use `Secure`, `SameSite=Strict`
708
+ - **JWKS Cache Invalidation** — Automatic key rotation detection
709
+ - **Payload Validation** — JWT fields validated before use
710
+ - **Network Resilience** — Transient errors auto-retry; auth errors fail fast
711
+ - **Backend Cookie Upgrade** — Helper to set `HttpOnly` cookies from the backend
712
+
713
+ See [docs/SECURITY_AND_FEATURES.md](./docs/SECURITY_AND_FEATURES.md) for details.
714
+
715
+ ### New in This Release
716
+
717
+ - `usePermission` hook for frontend permission checks
718
+ - `checkRoleAuthorization` and `checkMultiScopeAuthorization` backend helpers
719
+ - Provider lifecycle callbacks: `onTokenRefresh`, `onAuthError`, `onSignOut`
720
+ - Configurable post-callback redirect on `CallbackPage`
721
+ - Proactive token refresh before expiry
722
+ - Configurable JWKS cache TTL
723
+
724
+ ### Breaking Changes
725
+
726
+ - `UserCenter` now defaults to **local sign-out** (`global: false`) — much safer. Opt into global logout with `globalSignOut={true}` if you need it.
727
+
728
+ See [docs/MIGRATION_GUIDE.md](./docs/MIGRATION_GUIDE.md) for upgrade instructions.
729
+
730
+ ## Repository Notes
731
+
732
+ - frontend and backend helpers are published from the same package
733
+ - backend helpers are exposed from `@ouim/logto-authkit/server`
734
+ - bundler helpers are exposed from `@ouim/logto-authkit/bundler-config`
735
+
736
+ ## Key Documentation
737
+
738
+ ### Core Guides
739
+
740
+ - **[SECURITY_AND_FEATURES.md](./docs/SECURITY_AND_FEATURES.md)** — Security hardening, CSRF protection, role-based authorization
741
+ - **[PERMISSIONS_AND_AUTHORIZATION.md](./docs/PERMISSIONS_AND_AUTHORIZATION.md)** — Using `usePermission` and backend authorization helpers
742
+ - **[MIGRATION_GUIDE.md](./docs/MIGRATION_GUIDE.md)** — Upgrade guide for v0.1.9+
743
+ - **[src/server/README.md](./src/server/README.md)** — Backend verification API reference
744
+
745
+ ### Project Information
746
+
747
+ - **[CONTRIBUTING.md](./CONTRIBUTING.md)** — Contributing guidelines and branch protection rules
748
+ - **[CI_CD_AND_RELEASES.md](./docs/CI_CD_AND_RELEASES.md)** — GitHub Actions workflows, smoke tests, release process
749
+ - **[CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)** — Community standards
750
+ - **[CHANGELOG.md](./CHANGELOG.md)** — Version history and release notes
751
+ - **[SECURITY.md](./SECURITY.md)** — Vulnerability disclosure policy
752
+
753
+ ## Troubleshooting
754
+
755
+ ### CORS errors on your backend API
756
+
757
+ Cause: your API is rejecting the browser origin or not allowing credentialed requests, so auth cookies or bearer-token requests never reach the server correctly.
758
+
759
+ Fix:
760
+
761
+ - Allow your frontend origin in the backend CORS config.
762
+ - If you rely on cookies, enable credentials on both sides: backend `Access-Control-Allow-Credentials: true` and frontend `fetch(..., { credentials: 'include' })`.
763
+ - Keep the frontend app, callback route, and backend cookie domain aligned. A cookie set for one host will not be sent to another.
764
+
765
+ ### JWKS fetch failures
766
+
767
+ Cause: the backend cannot reach `https://<your-logto-host>/oidc/jwks`, the `logtoUrl` is wrong, or the Logto tenant URL includes a typo or wrong environment.
768
+
769
+ Fix:
770
+
771
+ - Verify `logtoUrl` is the tenant base URL, for example `https://your-tenant.logto.app`.
772
+ - Open `https://your-tenant.logto.app/oidc/jwks` directly and confirm it returns JSON.
773
+ - Check outbound network rules, proxy settings, and TLS certificates on the server running `verifyAuth` / `verifyNextAuth`.
774
+ - If failures happen only after a deployment or key rotation, retry once first: the verifier already invalidates stale JWKS cache entries and refetches keys automatically.
775
+
776
+ ### "Invalid audience"
777
+
778
+ Cause: the token's `aud` claim does not include the API resource identifier you passed as `audience` in the backend verifier.
779
+
780
+ Fix:
781
+
782
+ - Make sure the frontend Logto config requests the same resource in `resources`.
783
+ - Make sure the backend `audience` matches that resource exactly.
784
+ - If your API accepts multiple resources, pass `audience` as an array to backend helpers.
785
+ - Decode a failing token and compare its `aud` claim with your configured `audience` value instead of assuming they match.
786
+
787
+ ### Popup sign-in is blocked
788
+
789
+ Cause: the browser blocked `window.open`, usually because the sign-in call was not triggered from a direct user interaction or the site is in a stricter popup policy context.
790
+
791
+ Fix:
792
+
793
+ - Trigger popup sign-in from a real click or tap handler.
794
+ - Keep a real `/signin` route that renders `SignInPage`; popup flow depends on it.
795
+ - If popup restrictions are unavoidable, disable popup flow and use the default redirect flow instead.
796
+ - Test with browser extensions disabled if a popup blocker is interfering during development.
797
+
798
+ ### Infinite redirect loop
799
+
800
+ Cause: the app is repeatedly sending unauthenticated users to sign-in without successfully finishing the callback or persisting the token.
801
+
802
+ Fix:
803
+
804
+ - Confirm both `/signin` and `/callback` routes exist and render `SignInPage` and `CallbackPage`.
805
+ - Ensure `callbackUrl` in `AuthProvider` exactly matches the redirect URI configured in Logto.
806
+ - Do not protect the callback route itself with `useAuth({ middleware: 'auth' })`.
807
+ - If you use custom navigation, verify it does not rewrite the callback URL or strip query parameters before `CallbackPage` runs.
808
+ - Check whether auth cookies are being cleared or blocked after callback, especially across different domains, subdomains, or HTTP/non-HTTPS environments.
809
+
810
+ ### Local `file:` linked package issues
811
+
812
+ Cause: when `@ouim/logto-authkit` is consumed from a local path or symlink, the app can resolve a different React instance than the linked package. That usually shows up as invalid hook calls in Vite apps, or client/server boundary issues in Next.js App Router.
813
+
814
+ Fix:
815
+
816
+ - For Vite consumers, dedupe `react` and `react-dom` and alias them to the app's own `node_modules`.
817
+ - When merging `viteConfig`, merge `resolve` and `resolve.alias` instead of replacing them.
818
+ - For Next.js App Router, make sure your local build preserves the frontend entry's `'use client'` directive.
819
+ - See the dedicated guide: [docs/LINKED_LOCAL_PACKAGE_TROUBLESHOOTING.md](./docs/LINKED_LOCAL_PACKAGE_TROUBLESHOOTING.md)
820
+
821
+ ## License
822
+
823
+ MIT