@smittdev/next-jwt-auth 0.1.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.
@@ -0,0 +1,2 @@
1
+ export { AuthProvider, useSession, useAuth } from "./provider";
2
+ export type { AuthProviderProps, AuthActions } from "./provider";
@@ -0,0 +1,424 @@
1
+ "use client";
2
+
3
+ import React, {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from "react";
12
+ import { useRouter } from "next/navigation";
13
+ import type {
14
+ ClientSession,
15
+ Session,
16
+ ActionResult,
17
+ LoginActionOptions,
18
+ SessionActionData,
19
+ } from "../types";
20
+
21
+ // ─── AuthActions ──────────────────────────────────────────────────────────────
22
+
23
+ export interface AuthActions {
24
+ login(
25
+ credentials: Record<string, unknown>,
26
+ options?: LoginActionOptions,
27
+ ): Promise<ActionResult<SessionActionData>>;
28
+ logout(options?: {
29
+ redirect?: boolean;
30
+ redirectTo?: string;
31
+ }): Promise<ActionResult<null>>;
32
+ /** Syncs client session state. Silently rotates tokens if expired before returning. */
33
+ fetchSession(): Promise<ActionResult<SessionActionData | null>>;
34
+ /** Manually update the access token stored in the HTTP-only cookie */
35
+ updateSessionToken(newAccessToken: string): Promise<ActionResult<SessionActionData>>;
36
+ }
37
+
38
+ // ─── Context ──────────────────────────────────────────────────────────────────
39
+
40
+ interface AuthContextValue {
41
+ session: ClientSession;
42
+ login: AuthActions["login"];
43
+ logout: AuthActions["logout"];
44
+ fetchSession: AuthActions["fetchSession"];
45
+ updateSessionToken: AuthActions["updateSessionToken"];
46
+ }
47
+
48
+ const AuthContext = createContext<AuthContextValue | undefined>(undefined);
49
+
50
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
51
+
52
+ const LOADING_SESSION: ClientSession = {
53
+ status: "loading",
54
+ user: null,
55
+ accessToken: null,
56
+ refreshToken: null,
57
+ };
58
+
59
+ const UNAUTHENTICATED: ClientSession = {
60
+ status: "unauthenticated",
61
+ user: null,
62
+ accessToken: null,
63
+ refreshToken: null,
64
+ };
65
+
66
+ function buildAuthenticatedState(data: SessionActionData): ClientSession {
67
+ return {
68
+ status: "authenticated",
69
+ user: data.user,
70
+ accessToken: data.accessToken,
71
+ refreshToken: data.refreshToken,
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Derives the correct initial client session state from the `initialSession` prop.
77
+ *
78
+ * Three cases:
79
+ * - `undefined` (prop not passed) → "loading". The provider will fetch the
80
+ * session from the server on mount. This is the no-SSR path.
81
+ * - `null` (explicitly passed from a server component that found no session)
82
+ * → "unauthenticated". No fetch needed — the server already checked.
83
+ * - `Session` object → "authenticated". No fetch needed — hydrate immediately.
84
+ *
85
+ * The distinction between `undefined` and `null` is intentional:
86
+ * - `undefined` means "I didn't check" → client must check.
87
+ * - `null` means "I checked and there is no session" → trust the server.
88
+ */
89
+ function buildInitialState(
90
+ initialSession: Session | null | undefined,
91
+ ): ClientSession {
92
+ if (initialSession === undefined) return LOADING_SESSION;
93
+ if (initialSession === null) return UNAUTHENTICATED;
94
+ return buildAuthenticatedState(initialSession);
95
+ }
96
+
97
+ // ─── AuthProvider ─────────────────────────────────────────────────────────────
98
+
99
+ export interface AuthProviderProps {
100
+ children: React.ReactNode;
101
+ /**
102
+ * The server-side session passed from your root layout.
103
+ *
104
+ * - Pass the result of `await auth.getSession()` to hydrate instantly with
105
+ * no client-side fetch and no loading flicker.
106
+ * - Pass `null` explicitly if you know the user is unauthenticated on the
107
+ * server — skips the client fetch.
108
+ * - Omit entirely (or pass `undefined`) to let the client fetch the session
109
+ * on mount. The session will start as "loading" until the fetch completes.
110
+ *
111
+ * @example
112
+ * // app/layout.tsx — recommended: pass session from server
113
+ * const session = await auth.getSession();
114
+ * <AuthProvider initialSession={session} actions={auth.actions}>
115
+ *
116
+ * // Client-only apps — omit initialSession, the client will fetch on mount
117
+ * <AuthProvider actions={auth.actions}>
118
+ */
119
+ initialSession?: Session | null;
120
+ /** The server actions object from `auth.actions`. */
121
+ actions: AuthActions;
122
+ /**
123
+ * Called when a session silently expires — i.e., a background refresh or
124
+ * revalidation fails and the user was previously authenticated.
125
+ *
126
+ * NOT called when the user explicitly calls logout().
127
+ * NOT called during the initial mount fetch that finds no session.
128
+ *
129
+ * @example
130
+ * <AuthProvider onSessionExpired={() => toast.error("Your session expired.")} ...>
131
+ */
132
+ onSessionExpired?: () => void;
133
+ /**
134
+ * Whether to revalidate the session when the browser tab regains focus.
135
+ * Defaults to `true`. Set to `false` to disable.
136
+ *
137
+ * When revalidation finds an expired session for a previously-authenticated
138
+ * user, `onSessionExpired` is called if provided.
139
+ */
140
+ refreshOnFocus?: boolean;
141
+ }
142
+
143
+ /**
144
+ * Wraps your app and provides session state + auth actions to all client components.
145
+ *
146
+ * ── Session initialisation ────────────────────────────────────────────────────
147
+ *
148
+ * Pass `initialSession` from a Server Component for instant hydration with no
149
+ * loading state. If omitted, the provider starts in "loading" and fetches the
150
+ * session from the server on mount.
151
+ *
152
+ * ── Features ──────────────────────────────────────────────────────────────────
153
+ *
154
+ * Refresh on focus: when a tab regains visibility, the session is revalidated.
155
+ * Configurable via `refreshOnFocus` (default: true).
156
+ *
157
+ * Session expiry callback: supply `onSessionExpired` to be notified when a
158
+ * background revalidation fails for a previously-authenticated user.
159
+ *
160
+ * @example
161
+ * // app/layout.tsx
162
+ * import { auth } from "@/auth";
163
+ * import { AuthProvider } from "@/lib/auth/client";
164
+ *
165
+ * export default async function RootLayout({ children }) {
166
+ * const session = await auth.getSession();
167
+ * return (
168
+ * <html><body>
169
+ * <AuthProvider
170
+ * actions={auth.actions}
171
+ * initialSession={session}
172
+ * onSessionExpired={() => toast.error("Session expired")}
173
+ * >
174
+ * {children}
175
+ * </AuthProvider>
176
+ * </body></html>
177
+ * );
178
+ * }
179
+ */
180
+ export function AuthProvider({
181
+ children,
182
+ initialSession,
183
+ actions,
184
+ onSessionExpired,
185
+ refreshOnFocus = true,
186
+ }: AuthProviderProps) {
187
+ // Validate actions at startup so misconfigured setups fail fast with a clear
188
+ // message instead of throwing an obscure error when an action is first called.
189
+ if (
190
+ !actions ||
191
+ typeof actions.login !== "function" ||
192
+ typeof actions.logout !== "function" ||
193
+ typeof actions.fetchSession !== "function" ||
194
+ typeof actions.updateSessionToken !== "function"
195
+ ) {
196
+ throw new Error(
197
+ "[next-jwt-auth] <AuthProvider> requires an `actions` prop with login, logout, fetchSession, and updateSessionToken.\n" +
198
+ "Pass `actions={auth.actions}` from your auth.ts export.\n" +
199
+ "Example: <AuthProvider actions={auth.actions}>",
200
+ );
201
+ }
202
+
203
+ const [session, setSession] = useState<ClientSession>(() =>
204
+ buildInitialState(initialSession),
205
+ );
206
+
207
+ const router = useRouter();
208
+
209
+ // ── Stable refs ──────────────────────────────────────────────────────────────
210
+ // Keep refs in sync so effects/callbacks never close over stale values.
211
+
212
+ const onSessionExpiredRef = useRef(onSessionExpired);
213
+ useEffect(() => {
214
+ onSessionExpiredRef.current = onSessionExpired;
215
+ }, [onSessionExpired]);
216
+
217
+ const actionsRef = useRef(actions);
218
+ useEffect(() => {
219
+ actionsRef.current = actions;
220
+ }, [actions]);
221
+
222
+ const sessionRef = useRef(session);
223
+ useEffect(() => {
224
+ sessionRef.current = session;
225
+ }, [session]);
226
+
227
+ // ── Mount fetch ──────────────────────────────────────────────────────────────
228
+ // Only runs when `initialSession` was not provided (undefined). In that case
229
+ // the initial state is "loading" and we need to ask the server for the session.
230
+ //
231
+ // If `initialSession` was explicitly passed (null or Session object), the server
232
+ // already resolved the state — skip the fetch entirely.
233
+ useEffect(() => {
234
+ if (initialSession !== undefined) return;
235
+
236
+ let cancelled = false;
237
+
238
+ async function fetchOnMount() {
239
+ const result = await actionsRef.current.fetchSession();
240
+
241
+ if (cancelled) return;
242
+
243
+ if (!result.success || !result.data) {
244
+ setSession(UNAUTHENTICATED);
245
+ // Do NOT call onSessionExpired here. The user was never authenticated
246
+ // in this session — "no session found on mount" is normal, not an expiry.
247
+ } else {
248
+ setSession(buildAuthenticatedState(result.data));
249
+ }
250
+ }
251
+
252
+ fetchOnMount().catch(() => {
253
+ if (!cancelled) setSession(UNAUTHENTICATED);
254
+ });
255
+
256
+ return () => {
257
+ cancelled = true;
258
+ };
259
+ // eslint-disable-next-line react-hooks/exhaustive-deps
260
+ }, []); // intentionally empty — runs exactly once on mount
261
+
262
+ // ── Refresh on focus ─────────────────────────────────────────────────────────
263
+ useEffect(() => {
264
+ if (!refreshOnFocus) return;
265
+
266
+ const handleVisibilityChange = async () => {
267
+ // Only revalidate when the tab becomes visible and we have an established
268
+ // authenticated session. Skip during loading and for unauthenticated users
269
+ // to avoid unnecessary network requests.
270
+ if (
271
+ document.visibilityState !== "visible" ||
272
+ sessionRef.current.status !== "authenticated"
273
+ ) {
274
+ return;
275
+ }
276
+
277
+ const result = await actionsRef.current.fetchSession();
278
+
279
+ if (!result.success || !result.data) {
280
+ setSession(UNAUTHENTICATED);
281
+ // The user was authenticated when they tabbed away — this is an expiry.
282
+ onSessionExpiredRef.current?.();
283
+ } else {
284
+ setSession(buildAuthenticatedState(result.data));
285
+ }
286
+
287
+ // Re-run server components so middleware and requireSession() guards can
288
+ // act on the latest session state (redirect away from protected pages if
289
+ // the session expired, or refresh data if it was renewed).
290
+ router.refresh();
291
+ };
292
+
293
+ document.addEventListener("visibilitychange", handleVisibilityChange);
294
+ return () =>
295
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
296
+ }, [refreshOnFocus, router]);
297
+
298
+ // ── Auth callbacks ───────────────────────────────────────────────────────────
299
+
300
+ const login = useCallback<AuthActions["login"]>(
301
+ async (credentials, options) => {
302
+ const result = await actions.login(credentials, options);
303
+ if (result.success) {
304
+ setSession(buildAuthenticatedState(result.data));
305
+ }
306
+ return result;
307
+ },
308
+ [actions],
309
+ );
310
+
311
+ const logout = useCallback<AuthActions["logout"]>(
312
+ async (options) => {
313
+ // Optimistically clear local state for instant UI response.
314
+ setSession(UNAUTHENTICATED);
315
+ return actions.logout(options);
316
+ },
317
+ [actions],
318
+ );
319
+
320
+ const fetchSession = useCallback<AuthActions["fetchSession"]>(async () => {
321
+ const result = await actions.fetchSession();
322
+ if (!result.success || !result.data) {
323
+ const wasAuthenticated = sessionRef.current.status === "authenticated";
324
+ setSession(UNAUTHENTICATED);
325
+ // Only fire onSessionExpired if the user had an active session —
326
+ // avoids calling it during normal unauthenticated revalidations.
327
+ if (wasAuthenticated) {
328
+ onSessionExpiredRef.current?.();
329
+ }
330
+ } else {
331
+ setSession(buildAuthenticatedState(result.data));
332
+ }
333
+ return result;
334
+ }, [actions]);
335
+
336
+ const updateSessionToken = useCallback<AuthActions["updateSessionToken"]>(
337
+ async (newAccessToken) => {
338
+ const result = await actions.updateSessionToken(newAccessToken);
339
+ if (result.success) {
340
+ setSession(buildAuthenticatedState(result.data));
341
+ router.refresh(); // Refresh Server Components so they get the new token
342
+ }
343
+ return result;
344
+ },
345
+ [actions, router],
346
+ );
347
+
348
+ const contextValue = useMemo<AuthContextValue>(
349
+ () => ({ session, login, logout, fetchSession, updateSessionToken }),
350
+ [session, login, logout, fetchSession, updateSessionToken],
351
+ );
352
+
353
+ return (
354
+ <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
355
+ );
356
+ }
357
+
358
+ // ─── Hooks ────────────────────────────────────────────────────────────────────
359
+
360
+ /**
361
+ * Returns the current client-side session.
362
+ * Must be called inside a component wrapped by <AuthProvider>.
363
+ *
364
+ * Always check `session.status` before accessing `session.user`:
365
+ * - "loading" — session is being fetched, render a skeleton/spinner
366
+ * - "authenticated" — session.user, session.accessToken are available
367
+ * - "unauthenticated" — no session exists
368
+ *
369
+ * @example
370
+ * const session = useSession();
371
+ * if (session.status === "loading") return <Spinner />;
372
+ * if (session.status === "unauthenticated") return <LoginPrompt />;
373
+ * return <p>Hello, {session.user.email}</p>;
374
+ */
375
+ export function useSession(): ClientSession {
376
+ const ctx = useContext(AuthContext);
377
+ if (!ctx) {
378
+ throw new Error(
379
+ "[next-jwt-auth] useSession() was called outside of <AuthProvider>.\n" +
380
+ "Wrap your app in <AuthProvider> in your root layout.",
381
+ );
382
+ }
383
+ return ctx.session;
384
+ }
385
+
386
+ /**
387
+ * Returns auth action handlers.
388
+ * Must be called inside a component wrapped by <AuthProvider>.
389
+ *
390
+ * Loading state is intentionally not included — wrap calls in your own
391
+ * useTransition() or useState to track pending state where you need it.
392
+ *
393
+ * @example
394
+ * const { login, logout } = useAuth();
395
+ *
396
+ * // Login honouring the callbackUrl from the current URL
397
+ * await login({ email, password }, { callbackUrl: searchParams.get("callbackUrl") });
398
+ *
399
+ * // Disable redirect — handle navigation yourself
400
+ * const result = await login({ email, password }, { redirect: false });
401
+ * if (result.success) router.push("/dashboard");
402
+ *
403
+ * // Logout with default redirect
404
+ * await logout();
405
+ *
406
+ * // Logout without redirect
407
+ * const result = await logout({ redirect: false });
408
+ * if (result.success) router.replace("/");
409
+ */
410
+ export function useAuth() {
411
+ const ctx = useContext(AuthContext);
412
+ if (!ctx) {
413
+ throw new Error(
414
+ "[next-jwt-auth] useAuth() was called outside of <AuthProvider>.\n" +
415
+ "Wrap your app in <AuthProvider> in your root layout.",
416
+ );
417
+ }
418
+ return {
419
+ login: ctx.login,
420
+ logout: ctx.logout,
421
+ fetchSession: ctx.fetchSession,
422
+ updateSessionToken: ctx.updateSessionToken,
423
+ };
424
+ }
@@ -0,0 +1,54 @@
1
+ // lib/auth/config.ts
2
+ //
3
+ // Module-level singleton that holds the resolved auth configuration.
4
+ // `Auth()` calls `setGlobalAuthConfig()` once at startup.
5
+ // Every internal module then calls `getGlobalAuthConfig()` instead of
6
+ // receiving config as a constructor argument, keeping function signatures clean.
7
+
8
+ import type { ResolvedAuthConfig } from "./types";
9
+
10
+ let _config: ResolvedAuthConfig | null = null;
11
+
12
+ /**
13
+ * Stores the resolved config globally. Called once by `Auth()`.
14
+ * Subsequent calls (e.g. hot reload in dev) safely overwrite the existing value.
15
+ */
16
+ export function setGlobalAuthConfig(config: ResolvedAuthConfig): void {
17
+ _config = config;
18
+ }
19
+
20
+ /**
21
+ * Returns the resolved config.
22
+ * Throws a descriptive error if `Auth()` was never called — catches the
23
+ * "forgot to initialize" mistake early with a clear message.
24
+ */
25
+ export function getGlobalAuthConfig(): ResolvedAuthConfig {
26
+ if (!_config) {
27
+ throw new Error(
28
+ "[next-jwt-auth] Auth has not been initialized.\n" +
29
+ "Make sure `Auth()` is called in your auth.ts and that file is imported " +
30
+ "before any auth utilities are used (e.g. in your root layout).",
31
+ );
32
+ }
33
+ return _config;
34
+ }
35
+
36
+ /**
37
+ * Logs a debug message to the console when `debug: true` is set in the config.
38
+ * All messages are prefixed with `[next-jwt-auth]` so they are easy to find
39
+ * and filter in the browser/server console.
40
+ *
41
+ * Safe to call before `Auth()` is initialized — silently no-ops if the
42
+ * config singleton has not been set yet.
43
+ *
44
+ * @example
45
+ * debugLog("Middleware: attempting token refresh", { path: "/dashboard" });
46
+ */
47
+ export function debugLog(message: string, data?: unknown): void {
48
+ if (!_config?.debug) return;
49
+ if (data !== undefined) {
50
+ console.log(`[next-jwt-auth] ${message}`, data);
51
+ } else {
52
+ console.log(`[next-jwt-auth] ${message}`);
53
+ }
54
+ }
@@ -0,0 +1,57 @@
1
+ import type {
2
+ AuthConfig,
3
+ AuthPages,
4
+ CookieOptions,
5
+ ResolvedAuthConfig,
6
+ ResolvedCookieNames,
7
+ } from "../types";
8
+
9
+ const DEFAULT_COOKIE_OPTIONS = {
10
+ secure: process.env.NODE_ENV === "production",
11
+ sameSite: "lax" as const,
12
+ path: "/",
13
+ domain: undefined as string | undefined,
14
+ };
15
+
16
+ const DEFAULT_PAGES: Required<AuthPages> = {
17
+ signIn: "/login",
18
+ home: "/",
19
+ };
20
+
21
+ const DEFAULT_REFRESH_THRESHOLD_SECONDS = 60;
22
+ const DEFAULT_COOKIE_BASE_NAME = "auth-session";
23
+
24
+ function resolveCookieNames(
25
+ cookieOptions?: CookieOptions,
26
+ ): ResolvedCookieNames {
27
+ const baseName = cookieOptions?.name ?? DEFAULT_COOKIE_BASE_NAME;
28
+ return {
29
+ accessToken: `${baseName}.access`,
30
+ refreshToken: `${baseName}.refresh`,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Merges the user-provided config with defaults and returns a fully
36
+ * resolved config object that every internal module can rely on.
37
+ */
38
+ export function createAuthConfig(config: AuthConfig): ResolvedAuthConfig {
39
+ return {
40
+ adapter: config.adapter,
41
+ cookieNames: resolveCookieNames(config.cookies),
42
+ cookieOptions: {
43
+ secure: config.cookies?.secure ?? DEFAULT_COOKIE_OPTIONS.secure,
44
+ sameSite: config.cookies?.sameSite ?? DEFAULT_COOKIE_OPTIONS.sameSite,
45
+ path: config.cookies?.path ?? DEFAULT_COOKIE_OPTIONS.path,
46
+ domain: config.cookies?.domain ?? DEFAULT_COOKIE_OPTIONS.domain,
47
+ },
48
+ refreshThresholdSeconds:
49
+ config.refresh?.refreshThresholdSeconds ??
50
+ DEFAULT_REFRESH_THRESHOLD_SECONDS,
51
+ pages: {
52
+ signIn: config.pages?.signIn ?? DEFAULT_PAGES.signIn,
53
+ home: config.pages?.home ?? DEFAULT_PAGES.home,
54
+ },
55
+ debug: config.debug ?? false,
56
+ };
57
+ }
@@ -0,0 +1,92 @@
1
+ import { cookies } from "next/headers";
2
+ import type { ResolvedAuthConfig, TokenPair } from "../types";
3
+ import { getTokenExpiry } from "./jwt";
4
+
5
+ /**
6
+ * Writes both access and refresh token cookies to the response.
7
+ * Each cookie's maxAge is derived from the token's own `exp` claim.
8
+ * Called after login and token refresh.
9
+ */
10
+ export async function setTokenCookies(
11
+ tokens: TokenPair,
12
+ config: ResolvedAuthConfig,
13
+ ): Promise<void> {
14
+ const cookieStore = await cookies();
15
+
16
+ const accessExpiry = getTokenExpiry(tokens.accessToken);
17
+ const refreshExpiry = getTokenExpiry(tokens.refreshToken);
18
+
19
+ const baseOptions = {
20
+ httpOnly: true,
21
+ secure: config.cookieOptions.secure,
22
+ sameSite: config.cookieOptions.sameSite,
23
+ path: config.cookieOptions.path,
24
+ ...(config.cookieOptions.domain
25
+ ? { domain: config.cookieOptions.domain }
26
+ : {}),
27
+ };
28
+
29
+ cookieStore.set(config.cookieNames.accessToken, tokens.accessToken, {
30
+ ...baseOptions,
31
+ ...(accessExpiry && !accessExpiry.isExpired
32
+ ? { maxAge: accessExpiry.maxAgeSeconds }
33
+ : {}),
34
+ });
35
+
36
+ cookieStore.set(config.cookieNames.refreshToken, tokens.refreshToken, {
37
+ ...baseOptions,
38
+ ...(refreshExpiry && !refreshExpiry.isExpired
39
+ ? { maxAge: refreshExpiry.maxAgeSeconds }
40
+ : {}),
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Reads the token pair from cookies.
46
+ * Returns null if either cookie is missing.
47
+ */
48
+ export async function getTokensFromCookies(
49
+ config: ResolvedAuthConfig,
50
+ ): Promise<TokenPair | null> {
51
+ const cookieStore = await cookies();
52
+
53
+ const accessToken = cookieStore.get(config.cookieNames.accessToken)?.value;
54
+ const refreshToken = cookieStore.get(config.cookieNames.refreshToken)?.value;
55
+
56
+ if (!accessToken || !refreshToken) return null;
57
+ return { accessToken, refreshToken };
58
+ }
59
+
60
+ /**
61
+ * Deletes both token cookies, effectively ending the session.
62
+ */
63
+ export async function clearTokenCookies(
64
+ config: ResolvedAuthConfig,
65
+ ): Promise<void> {
66
+ const cookieStore = await cookies();
67
+ cookieStore.delete(config.cookieNames.accessToken);
68
+ cookieStore.delete(config.cookieNames.refreshToken);
69
+ }
70
+
71
+ /**
72
+ * Updates only the access token cookie.
73
+ * Used during silent refresh when only the access token changes.
74
+ */
75
+ export async function updateAccessTokenCookie(
76
+ accessToken: string,
77
+ config: ResolvedAuthConfig,
78
+ ): Promise<void> {
79
+ const cookieStore = await cookies();
80
+ const expiry = getTokenExpiry(accessToken);
81
+
82
+ cookieStore.set(config.cookieNames.accessToken, accessToken, {
83
+ httpOnly: true,
84
+ secure: config.cookieOptions.secure,
85
+ sameSite: config.cookieOptions.sameSite,
86
+ path: config.cookieOptions.path,
87
+ ...(config.cookieOptions.domain
88
+ ? { domain: config.cookieOptions.domain }
89
+ : {}),
90
+ ...(expiry && !expiry.isExpired ? { maxAge: expiry.maxAgeSeconds } : {}),
91
+ });
92
+ }
@@ -0,0 +1,14 @@
1
+ export { createAuthConfig } from "./config";
2
+ export {
3
+ decodeJwt,
4
+ getTokenExpiry,
5
+ isTokenValid,
6
+ getSecondsUntilExpiry,
7
+ } from "./jwt";
8
+ export type { TokenExpiryInfo } from "./jwt";
9
+ export {
10
+ setTokenCookies,
11
+ clearTokenCookies,
12
+ getTokensFromCookies,
13
+ updateAccessTokenCookie,
14
+ } from "./cookies";