@sourceregistry/sveltekit-oidc 1.3.1 → 1.4.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 CHANGED
@@ -19,10 +19,7 @@ npm install sveltekit-oidc
19
19
 
20
20
  ```ts
21
21
  // src/lib/server/auth.ts
22
- import {
23
- createInMemoryBackChannelLogoutStore,
24
- createOIDC
25
- } from 'sveltekit-oidc/server';
22
+ import { createOIDC } from 'sveltekit-oidc/server';
26
23
 
27
24
  export const oidc = createOIDC({
28
25
  issuer: 'https://your-idp.example.com',
@@ -32,22 +29,8 @@ export const oidc = createOIDC({
32
29
  clientAuthMethod: 'client_secret_basic',
33
30
  loginPath: '/auth/login',
34
31
  redirectPath: '/auth/callback',
35
- scope: ['openid', 'profile', 'email', 'offline_access'],
36
- clockSkewSeconds: 30,
37
- fetchUserInfo: true,
38
- backChannelLogoutStore: createInMemoryBackChannelLogoutStore(),
39
- transformSession(session) {
40
- return {
41
- ...session,
42
- groups: session.groups,
43
- user: session.user
44
- ? {
45
- ...session.user,
46
- groups: session.groups
47
- }
48
- : session.user
49
- };
50
- },
32
+ scope: 'openid profile email offline_access',
33
+ backChannelLogoutStore: 'memory',
51
34
  cookieOptions: {
52
35
  secure: process.env.NODE_ENV === 'production'
53
36
  }
@@ -183,19 +166,17 @@ export const oidc = createOIDC({
183
166
  });
184
167
  ```
185
168
 
186
- Extract the inferred type with `OIDCInferClaims` and apply it to `App.Locals` so `event.locals.oidc` is typed everywhere:
169
+ Wire `App.Locals` so `event.locals.oidc` is fully typed everywhere — `OIDCLocals` infers everything directly from the instance:
187
170
 
188
171
  ```ts
189
172
  // src/app.d.ts
190
- import type { OIDCHandleLocals, OIDCInferClaims } from 'sveltekit-oidc/server';
173
+ import type { OIDCLocals } from 'sveltekit-oidc/server';
191
174
  import { oidc } from '$lib/server/auth';
192
175
 
193
- export type AppClaims = OIDCInferClaims<typeof oidc>;
194
-
195
176
  declare global {
196
177
  namespace App {
197
178
  interface Locals {
198
- oidc?: OIDCHandleLocals<AppClaims>;
179
+ oidc?: OIDCLocals<typeof oidc>;
199
180
  }
200
181
  }
201
182
  }
@@ -203,7 +184,16 @@ declare global {
203
184
  export {};
204
185
  ```
205
186
 
206
- Now `event.locals.oidc?.claims?.roles` is `string[]` and `?.tenant` is `string | undefined` in every hook, load function, and action.
187
+ Now `event.locals.oidc?.claims?.roles` is `string[]` and `?.tenant` is `string | undefined` in every hook, load function, and action — no separate type aliases needed.
188
+
189
+ When you need the claim type explicitly (e.g. in a Svelte component), use `OIDCInferClaims`:
190
+
191
+ ```ts
192
+ import type { OIDCInferClaims } from 'sveltekit-oidc/server';
193
+ import { oidc } from '$lib/server/auth';
194
+
195
+ type AppClaims = OIDCInferClaims<typeof oidc>;
196
+ ```
207
197
 
208
198
  `OIDCContext` is a generic component, but Svelte doesn't support passing explicit type arguments in markup — `TClaims` is inferred from the `session` prop instead. Since `data.session` comes from `oidc.getPublicSession(event)`, it's already typed `OIDCPublicSession<AppClaims> | null`, so the inference flows through automatically:
209
199
 
@@ -261,20 +251,17 @@ export const oidc = createOIDC({
261
251
  });
262
252
  ```
263
253
 
264
- Extract the inferred type with `OIDCInferSession` and apply it alongside `OIDCInferClaims`:
254
+ `app.d.ts` stays a one-liner `OIDCLocals` picks up both `TClaims` and `TSession` from the instance:
265
255
 
266
256
  ```ts
267
257
  // src/app.d.ts
268
- import type { OIDCHandleLocals, OIDCInferClaims, OIDCInferSession } from 'sveltekit-oidc/server';
258
+ import type { OIDCLocals } from 'sveltekit-oidc/server';
269
259
  import { oidc } from '$lib/server/auth';
270
260
 
271
- export type AppClaims = OIDCInferClaims<typeof oidc>;
272
- export type AppSession = OIDCInferSession<typeof oidc>;
273
-
274
261
  declare global {
275
262
  namespace App {
276
263
  interface Locals {
277
- oidc?: OIDCHandleLocals<AppClaims, AppSession>;
264
+ oidc?: OIDCLocals<typeof oidc>;
278
265
  }
279
266
  }
280
267
  }
@@ -284,6 +271,16 @@ export {};
284
271
 
285
272
  Now `event.locals.oidc?.session?.tenantId` is `string` and `?.permissions` is `string[]` everywhere — and `oidc.requireAuth(event)` / `handleCallback`'s `onsuccess` resolve to `AppSession` directly.
286
273
 
274
+ Use `OIDCInferClaims` / `OIDCInferSession` when you need the types explicitly:
275
+
276
+ ```ts
277
+ import type { OIDCInferClaims, OIDCInferSession } from 'sveltekit-oidc/server';
278
+ import { oidc } from '$lib/server/auth';
279
+
280
+ type AppClaims = OIDCInferClaims<typeof oidc>;
281
+ type AppSession = OIDCInferSession<typeof oidc>;
282
+ ```
283
+
287
284
  If `transformSession` is omitted, `TSession` defaults to `OIDCSession<TClaims>` — existing setups keep working unchanged.
288
285
 
289
286
  ## Example App
@@ -309,4 +306,4 @@ Set these environment variables to enable it:
309
306
  - Use `transformClaims`, `transformUser`, and `transformSession` to project provider-specific claims into your own session shape.
310
307
  - `check_session_iframe` monitoring only runs when `monitorSession` is enabled, the provider advertises that endpoint, and the session includes `session_state`.
311
308
  - Refresh token handling is automatic when a valid refresh token is present.
312
- - `event.locals.oidc` is attached by the hook and typed via `OIDCHandleLocals<TClaims>`; see [Typed Custom Claims](#typed-custom-claims) for wiring your own claim shapes through `app.d.ts`.
309
+ - `event.locals.oidc` is attached by the hook; wire it in `app.d.ts` with `OIDCLocals<typeof oidc>` — see [Typed Custom Claims](#typed-custom-claims).
@@ -6,7 +6,34 @@ import { asAuthorizationHeader, createClientSecretJwtAssertion, createPrivateKey
6
6
  import { normalizeTokens, shouldRefresh } from './session.js';
7
7
  import { absoluteUrl, base64UrlEncode, buildCookieOptions, collectGroups, createPKCEPair, normalizeIssuer, normalizeScope, parseProviderError, toPublicSession } from './utils.js';
8
8
  export { createInMemoryBackChannelLogoutStore, createInMemorySessionStore } from './store.js';
9
+ import { createInMemoryBackChannelLogoutStore, createInMemorySessionStore } from './store.js';
10
+ function buildLogger(logger) {
11
+ const noop = () => { };
12
+ if (logger === false)
13
+ return { debug: noop, info: noop, warn: noop, error: noop };
14
+ if (logger)
15
+ return {
16
+ debug: (...a) => logger.debug?.(...a),
17
+ info: (...a) => logger.info?.(...a),
18
+ warn: (...a) => logger.warn?.(...a),
19
+ error: (...a) => logger.error?.(...a),
20
+ };
21
+ const p = '[sveltekit-oidc]';
22
+ return {
23
+ debug: (...a) => console.debug(p, ...a),
24
+ info: (...a) => console.info(p, ...a),
25
+ warn: (...a) => console.warn(p, ...a),
26
+ error: (...a) => console.error(p, ...a),
27
+ };
28
+ }
9
29
  export function createOIDC(options) {
30
+ const log = buildLogger(options.logger);
31
+ const sessionStore = options.sessionStore === 'memory'
32
+ ? createInMemorySessionStore()
33
+ : options.sessionStore;
34
+ const backChannelLogoutStore = options.backChannelLogoutStore === 'memory'
35
+ ? createInMemoryBackChannelLogoutStore()
36
+ : options.backChannelLogoutStore;
10
37
  const cookieOptions = buildCookieOptions(options.cookieOptions);
11
38
  const clockSkewSeconds = options.clockSkewSeconds ?? 30;
12
39
  const refreshToleranceSeconds = options.refreshToleranceSeconds ?? 30;
@@ -20,7 +47,7 @@ export function createOIDC(options) {
20
47
  let metadataPromise;
21
48
  let jwksPromise;
22
49
  async function readPersistedSession(cookies) {
23
- if (!options.sessionStore) {
50
+ if (!sessionStore) {
24
51
  const session = cookieStore.readSession(cookies);
25
52
  return session ? { session } : null;
26
53
  }
@@ -28,7 +55,7 @@ export function createOIDC(options) {
28
55
  if (!reference?.id) {
29
56
  return null;
30
57
  }
31
- const session = await options.sessionStore.get(reference.id);
58
+ const session = await sessionStore.get(reference.id);
32
59
  if (!session) {
33
60
  cookieStore.clearSessionReference(cookies);
34
61
  return null;
@@ -39,22 +66,22 @@ export function createOIDC(options) {
39
66
  };
40
67
  }
41
68
  async function writePersistedSession(cookies, session, sessionId) {
42
- if (!options.sessionStore) {
69
+ if (!sessionStore) {
43
70
  cookieStore.writeSession(cookies, session);
44
71
  return;
45
72
  }
46
73
  const id = sessionId ?? base64UrlEncode(randomBytes(24));
47
- await options.sessionStore.set(id, session);
74
+ await sessionStore.set(id, session);
48
75
  cookieStore.writeSessionReference(cookies, { id });
49
76
  }
50
77
  async function clearPersistedSession(cookies, sessionId) {
51
- if (!options.sessionStore) {
78
+ if (!sessionStore) {
52
79
  cookieStore.clearSession(cookies);
53
80
  return;
54
81
  }
55
82
  const id = sessionId ?? cookieStore.readSessionReference(cookies)?.id;
56
83
  if (id) {
57
- await options.sessionStore.delete(id);
84
+ await sessionStore.delete(id);
58
85
  }
59
86
  cookieStore.clearSessionReference(cookies);
60
87
  }
@@ -237,10 +264,10 @@ export function createOIDC(options) {
237
264
  });
238
265
  }
239
266
  async function isRevoked(session) {
240
- if (!session || !options.backChannelLogoutStore) {
267
+ if (!session || !backChannelLogoutStore) {
241
268
  return false;
242
269
  }
243
- return options.backChannelLogoutStore.isRevoked(session);
270
+ return backChannelLogoutStore.isRevoked(session);
244
271
  }
245
272
  async function maybeRefreshSession(event, persisted) {
246
273
  const session = persisted?.session ?? null;
@@ -285,7 +312,8 @@ export function createOIDC(options) {
285
312
  await writePersistedSession(event.cookies, finalSession, persisted?.id);
286
313
  return finalSession;
287
314
  }
288
- catch {
315
+ catch (err) {
316
+ log.error('Token refresh failed — clearing session', err);
289
317
  await clearPersistedSession(event.cookies, persisted?.id);
290
318
  return null;
291
319
  }
@@ -337,7 +365,8 @@ export function createOIDC(options) {
337
365
  const code = event.url.searchParams.get('code');
338
366
  if (!stateCookie || !state || !code || stateCookie.state !== state) {
339
367
  cookieStore.clearState(event.cookies);
340
- throw error(400, { message: 'Invalid or expired OIDC callback state' });
368
+ log.warn('Invalid or expired callback state — restarting login flow');
369
+ return signIn(event);
341
370
  }
342
371
  cookieStore.clearState(event.cookies);
343
372
  const tokenResponse = await exchangeCode({
@@ -415,7 +444,7 @@ export function createOIDC(options) {
415
444
  if (!metadata.backchannel_logout_supported) {
416
445
  throw error(400, { message: 'Provider does not advertise back-channel logout support' });
417
446
  }
418
- if (!options.backChannelLogoutStore) {
447
+ if (!backChannelLogoutStore) {
419
448
  throw error(500, {
420
449
  message: 'backChannelLogoutStore is required for back-channel logout with cookie sessions'
421
450
  });
@@ -426,7 +455,7 @@ export function createOIDC(options) {
426
455
  throw error(400, { message: 'logout_token is required' });
427
456
  }
428
457
  const claims = await validateBackChannelLogoutToken(logoutToken);
429
- await options.backChannelLogoutStore.revoke({
458
+ await backChannelLogoutStore.revoke({
430
459
  issuer: claims.iss,
431
460
  clientId: options.clientId,
432
461
  sid: claims.sid,
@@ -133,6 +133,12 @@ export type OIDCSessionManagementConfig = {
133
133
  backChannelLogoutSupported: boolean;
134
134
  backChannelLogoutSessionSupported: boolean;
135
135
  };
136
+ export type OIDCLogger = {
137
+ debug?: (...args: unknown[]) => void;
138
+ info?: (...args: unknown[]) => void;
139
+ warn?: (...args: unknown[]) => void;
140
+ error?: (...args: unknown[]) => void;
141
+ };
136
142
  export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSession extends OIDCSession<TClaims> = OIDCSession<TClaims>> = {
137
143
  issuer?: string;
138
144
  discoveryUrl?: string;
@@ -144,7 +150,7 @@ export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessio
144
150
  loginPath?: string;
145
151
  redirectPath?: string;
146
152
  postLogoutRedirectUri?: string;
147
- scope?: string[];
153
+ scope?: string | string[];
148
154
  audience?: string;
149
155
  fetchUserInfo?: boolean;
150
156
  sessionCookieName?: string;
@@ -155,8 +161,8 @@ export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessio
155
161
  refreshToleranceSeconds?: number;
156
162
  defaultLoginRedirect?: string;
157
163
  defaultLogoutRedirect?: string;
158
- sessionStore?: OIDCSessionStore<TClaims, TSession>;
159
- backChannelLogoutStore?: OIDCBackChannelLogoutStore<TClaims, TSession>;
164
+ sessionStore?: OIDCSessionStore<TClaims, TSession> | 'memory';
165
+ backChannelLogoutStore?: OIDCBackChannelLogoutStore<TClaims, TSession> | 'memory';
160
166
  transformClaims?: (claims: OIDCUserClaims) => MaybePromise<TClaims>;
161
167
  transformUser?: (user: OIDCUserClaims | undefined, context: {
162
168
  claims?: TClaims;
@@ -169,11 +175,12 @@ export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessio
169
175
  isRefresh: boolean;
170
176
  }) => MaybePromise<TSession>;
171
177
  endpoints?: Partial<OIDCDiscoveryDocument>;
178
+ logger?: OIDCLogger | false;
172
179
  };
173
180
  export type OIDCLoginOptions = {
174
181
  returnTo?: string;
175
182
  prompt?: 'login' | 'consent' | 'none' | 'select_account';
176
- scope?: string[];
183
+ scope?: string | string[];
177
184
  extraParams?: Record<string, string>;
178
185
  };
179
186
  export type OIDCLogoutOptions = {
@@ -256,3 +263,4 @@ export type OIDCInstance<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessi
256
263
  };
257
264
  export type OIDCInferClaims<T> = T extends OIDCInstance<infer TClaims> ? TClaims : OIDCUserClaims;
258
265
  export type OIDCInferSession<T> = T extends OIDCInstance<infer _TClaims, infer TSession> ? TSession : OIDCSession<OIDCUserClaims>;
266
+ export type OIDCLocals<T extends OIDCInstance<any, any>> = OIDCHandleLocals<OIDCInferClaims<T>, OIDCInferSession<T>>;
@@ -6,7 +6,7 @@ export declare function createPKCEPair(length?: number): {
6
6
  challenge: string;
7
7
  };
8
8
  export declare function normalizeIssuer(issuer: string): string;
9
- export declare function normalizeScope(scope?: string[]): string[];
9
+ export declare function normalizeScope(scope?: string | string[]): string[];
10
10
  export declare function normalizeStringArray(value: unknown): string[];
11
11
  export declare function collectGroups<TClaims extends OIDCUserClaims = OIDCUserClaims>(...sources: Array<TClaims | undefined>): string[];
12
12
  export declare function createSignedValue(value: string, secret: string): string;
@@ -13,7 +13,10 @@ export function normalizeIssuer(issuer) {
13
13
  return issuer.endsWith('/') ? issuer.slice(0, -1) : issuer;
14
14
  }
15
15
  export function normalizeScope(scope) {
16
- return scope?.length ? [...new Set(scope)] : ['openid', 'profile', 'email'];
16
+ if (!scope)
17
+ return ['openid', 'profile', 'email'];
18
+ const arr = typeof scope === 'string' ? scope.trim().split(/\s+/) : scope;
19
+ return arr.length ? [...new Set(arr)] : ['openid', 'profile', 'email'];
17
20
  }
18
21
  export function normalizeStringArray(value) {
19
22
  if (!value)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sourceregistry/sveltekit-oidc",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "OIDC authentication helpers for SvelteKit applications",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {