oidc-spa 8.1.9 → 8.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/backend.d.ts +27 -6
  2. package/backend.js +124 -139
  3. package/backend.js.map +1 -1
  4. package/core/Oidc.d.ts +28 -4
  5. package/core/createOidc.d.ts +12 -3
  6. package/core/createOidc.js +1 -1
  7. package/core/createOidc.js.map +1 -1
  8. package/core/earlyInit.d.ts +1 -0
  9. package/core/earlyInit.js +11 -4
  10. package/core/earlyInit.js.map +1 -1
  11. package/core/iframeMessageProtection.js +16 -18
  12. package/core/iframeMessageProtection.js.map +1 -1
  13. package/core/loginOrGoToAuthServer.js +8 -3
  14. package/core/loginOrGoToAuthServer.js.map +1 -1
  15. package/core/loginSilent.js +4 -0
  16. package/core/loginSilent.js.map +1 -1
  17. package/core/oidcClientTsUserToTokens.d.ts +1 -1
  18. package/core/oidcClientTsUserToTokens.js.map +1 -1
  19. package/core/requiredPostHydrationReplaceNavigationUrl.d.ts +6 -0
  20. package/core/requiredPostHydrationReplaceNavigationUrl.js +12 -0
  21. package/core/requiredPostHydrationReplaceNavigationUrl.js.map +1 -0
  22. package/entrypoint.d.ts +1 -0
  23. package/entrypoint.js +3 -1
  24. package/entrypoint.js.map +1 -1
  25. package/esm/angular.d.ts +14 -4
  26. package/esm/angular.js +155 -10
  27. package/esm/angular.js.map +1 -1
  28. package/esm/backend.d.ts +48 -0
  29. package/esm/backend.js +259 -0
  30. package/esm/backend.js.map +1 -0
  31. package/esm/core/Oidc.d.ts +28 -4
  32. package/esm/core/createOidc.d.ts +12 -3
  33. package/esm/core/createOidc.js +1 -1
  34. package/esm/core/createOidc.js.map +1 -1
  35. package/esm/core/earlyInit.d.ts +1 -0
  36. package/esm/core/earlyInit.js +11 -4
  37. package/esm/core/earlyInit.js.map +1 -1
  38. package/esm/core/iframeMessageProtection.js +16 -18
  39. package/esm/core/iframeMessageProtection.js.map +1 -1
  40. package/esm/core/loginOrGoToAuthServer.js +8 -3
  41. package/esm/core/loginOrGoToAuthServer.js.map +1 -1
  42. package/esm/core/loginSilent.js +4 -0
  43. package/esm/core/loginSilent.js.map +1 -1
  44. package/esm/core/oidcClientTsUserToTokens.d.ts +1 -1
  45. package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
  46. package/esm/core/requiredPostHydrationReplaceNavigationUrl.d.ts +6 -0
  47. package/esm/core/requiredPostHydrationReplaceNavigationUrl.js +8 -0
  48. package/esm/core/requiredPostHydrationReplaceNavigationUrl.js.map +1 -0
  49. package/esm/entrypoint.d.ts +1 -0
  50. package/esm/entrypoint.js +1 -0
  51. package/esm/entrypoint.js.map +1 -1
  52. package/esm/mock/oidc.d.ts +1 -1
  53. package/esm/mock/oidc.js.map +1 -1
  54. package/esm/react/react.d.ts +1 -1
  55. package/esm/tanstack-start/react/accessTokenValidation_rfc9068.d.ts +12 -0
  56. package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js +95 -0
  57. package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js.map +1 -0
  58. package/esm/tanstack-start/react/apiBuilder.d.ts +27 -0
  59. package/esm/tanstack-start/react/apiBuilder.js +58 -0
  60. package/esm/tanstack-start/react/apiBuilder.js.map +1 -0
  61. package/esm/tanstack-start/react/createOidcSpaApi.d.ts +9 -0
  62. package/esm/tanstack-start/react/createOidcSpaApi.js +678 -0
  63. package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -0
  64. package/esm/tanstack-start/react/index.d.ts +3 -0
  65. package/esm/tanstack-start/react/index.js +4 -0
  66. package/esm/tanstack-start/react/index.js.map +1 -0
  67. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.d.ts +4 -0
  68. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.js +8 -0
  69. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.js.map +1 -0
  70. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.d.ts +4 -0
  71. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.js +76 -0
  72. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.js.map +1 -0
  73. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.d.ts +1 -0
  74. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.js +11 -0
  75. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.js.map +1 -0
  76. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.d.ts +2 -0
  77. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.js +3 -0
  78. package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.js.map +1 -0
  79. package/esm/tanstack-start/react/types.d.ts +355 -0
  80. package/esm/tanstack-start/react/types.js +2 -0
  81. package/esm/tanstack-start/react/types.js.map +1 -0
  82. package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.d.ts +2 -0
  83. package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js +25 -0
  84. package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js.map +1 -0
  85. package/esm/tools/GetterOrDirectValue.d.ts +1 -0
  86. package/esm/tools/GetterOrDirectValue.js +2 -0
  87. package/esm/tools/GetterOrDirectValue.js.map +1 -0
  88. package/esm/tools/ZodSchemaLike.d.ts +3 -0
  89. package/esm/tools/ZodSchemaLike.js +2 -0
  90. package/esm/tools/ZodSchemaLike.js.map +1 -0
  91. package/esm/tools/inferIsViteDev.d.ts +1 -0
  92. package/esm/tools/inferIsViteDev.js +6 -0
  93. package/esm/tools/inferIsViteDev.js.map +1 -0
  94. package/esm/tools/infer_import_meta_env_BASE_URL.d.ts +1 -0
  95. package/esm/tools/infer_import_meta_env_BASE_URL.js +15 -0
  96. package/esm/tools/infer_import_meta_env_BASE_URL.js.map +1 -0
  97. package/esm/tools/tsafe/uncapitalize.d.ts +2 -0
  98. package/esm/tools/tsafe/uncapitalize.js +5 -0
  99. package/esm/tools/tsafe/uncapitalize.js.map +1 -0
  100. package/esm/vendor/backend/evt.d.ts +2 -0
  101. package/esm/vendor/backend/evt.js +3286 -0
  102. package/esm/vendor/backend/jose.d.ts +1 -0
  103. package/esm/vendor/backend/jose.js +3546 -0
  104. package/esm/vendor/backend/tsafe.d.ts +5 -0
  105. package/esm/vendor/backend/tsafe.js +68 -0
  106. package/esm/vendor/backend/zod.d.ts +1 -0
  107. package/esm/vendor/backend/zod.js +4023 -0
  108. package/esm/vendor/frontend/worker-timers.js +261 -1
  109. package/mock/oidc.d.ts +1 -1
  110. package/mock/oidc.js.map +1 -1
  111. package/package.json +40 -4
  112. package/react/react.d.ts +1 -1
  113. package/src/angular.ts +224 -9
  114. package/src/backend.ts +201 -166
  115. package/src/core/Oidc.ts +41 -11
  116. package/src/core/createOidc.ts +12 -3
  117. package/src/core/earlyInit.ts +19 -4
  118. package/src/core/iframeMessageProtection.ts +14 -15
  119. package/src/core/loginOrGoToAuthServer.ts +11 -3
  120. package/src/core/loginSilent.ts +5 -0
  121. package/src/core/oidcClientTsUserToTokens.ts +2 -2
  122. package/src/core/requiredPostHydrationReplaceNavigationUrl.ts +11 -0
  123. package/src/entrypoint.ts +1 -0
  124. package/src/mock/oidc.ts +2 -2
  125. package/src/react/react.tsx +1 -1
  126. package/src/tanstack-start/react/accessTokenValidation_rfc9068.ts +135 -0
  127. package/src/tanstack-start/react/apiBuilder.ts +151 -0
  128. package/src/tanstack-start/react/createOidcSpaApi.tsx +1009 -0
  129. package/src/tanstack-start/react/index.ts +5 -0
  130. package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.ts +8 -0
  131. package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.tsx +110 -0
  132. package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.ts +13 -0
  133. package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.ts +2 -0
  134. package/src/tanstack-start/react/types.tsx +415 -0
  135. package/src/tanstack-start/react/withHandlingOidcPostLoginNavigation.tsx +35 -0
  136. package/src/tools/GetterOrDirectValue.ts +1 -0
  137. package/src/tools/ZodSchemaLike.ts +3 -0
  138. package/src/tools/getThisCodebaseRootDirPath_cjs.ts +19 -0
  139. package/src/tools/inferIsViteDev.ts +6 -0
  140. package/src/tools/infer_import_meta_env_BASE_URL.ts +19 -0
  141. package/src/tools/tsafe/uncapitalize.ts +4 -0
  142. package/src/vendor/backend/jose.ts +1 -0
  143. package/src/vendor/build-runtime/babel.ts +6 -0
  144. package/src/vendor/build-runtime/magic-string.ts +3 -0
  145. package/src/vite-plugin/detectProjectType.ts +20 -0
  146. package/src/vite-plugin/excludeModuleExportFromOptimizedDeps.ts +20 -0
  147. package/src/vite-plugin/handleClientEntrypoint.ts +260 -0
  148. package/src/vite-plugin/index.ts +1 -0
  149. package/src/vite-plugin/transformCreateFileRoute.ts +240 -0
  150. package/src/vite-plugin/vite-plugin.ts +54 -0
  151. package/tools/GetterOrDirectValue.d.ts +1 -0
  152. package/tools/GetterOrDirectValue.js +3 -0
  153. package/tools/GetterOrDirectValue.js.map +1 -0
  154. package/tools/ZodSchemaLike.d.ts +3 -0
  155. package/tools/ZodSchemaLike.js +3 -0
  156. package/tools/ZodSchemaLike.js.map +1 -0
  157. package/tools/getThisCodebaseRootDirPath_cjs.d.ts +2 -0
  158. package/tools/getThisCodebaseRootDirPath_cjs.js +53 -0
  159. package/tools/getThisCodebaseRootDirPath_cjs.js.map +1 -0
  160. package/tools/tsafe/uncapitalize.d.ts +2 -0
  161. package/tools/tsafe/uncapitalize.js +8 -0
  162. package/tools/tsafe/uncapitalize.js.map +1 -0
  163. package/vendor/backend/jose.d.ts +1 -0
  164. package/vendor/backend/jose.js +3 -0
  165. package/vendor/build-runtime/babel.d.ts +6 -0
  166. package/vendor/build-runtime/babel.js +3 -0
  167. package/vendor/build-runtime/magic-string.d.ts +2 -0
  168. package/vendor/build-runtime/magic-string.js +2 -0
  169. package/vendor/frontend/oidc-client-ts.js +0 -2
  170. package/vite-plugin/detectProjectType.d.ts +10 -0
  171. package/vite-plugin/detectProjectType.js +15 -0
  172. package/vite-plugin/detectProjectType.js.map +1 -0
  173. package/vite-plugin/excludeModuleExportFromOptimizedDeps.d.ts +4 -0
  174. package/vite-plugin/excludeModuleExportFromOptimizedDeps.js +50 -0
  175. package/vite-plugin/excludeModuleExportFromOptimizedDeps.js.map +1 -0
  176. package/vite-plugin/handleClientEntrypoint.d.ts +10 -0
  177. package/vite-plugin/handleClientEntrypoint.js +211 -0
  178. package/vite-plugin/handleClientEntrypoint.js.map +1 -0
  179. package/vite-plugin/index.d.ts +1 -0
  180. package/vite-plugin/index.js +6 -0
  181. package/vite-plugin/index.js.map +1 -0
  182. package/vite-plugin/transformCreateFileRoute.d.ts +10 -0
  183. package/vite-plugin/transformCreateFileRoute.js +173 -0
  184. package/vite-plugin/transformCreateFileRoute.js.map +1 -0
  185. package/vite-plugin/vite-plugin.d.ts +5 -0
  186. package/vite-plugin/vite-plugin.js +46 -0
  187. package/vite-plugin/vite-plugin.js.map +1 -0
  188. package/src/vendor/backend/jsonwebtoken.ts +0 -1
  189. package/src/vendor/backend/node-fetch.ts +0 -2
  190. package/src/vendor/backend/node-jose.ts +0 -1
  191. package/vendor/backend/jsonwebtoken.d.ts +0 -1
  192. package/vendor/backend/jsonwebtoken.js +0 -3
  193. package/vendor/backend/node-fetch.d.ts +0 -2
  194. package/vendor/backend/node-fetch.js +0 -2
  195. package/vendor/backend/node-jose.d.ts +0 -1
  196. package/vendor/backend/node-jose.js +0 -3
package/src/backend.ts CHANGED
@@ -1,20 +1,76 @@
1
- import { fetch } from "./vendor/backend/node-fetch";
2
- import { assert, isAmong, id, type Equals, is, exclude } from "./vendor/backend/tsafe";
3
- import { JWK } from "./vendor/backend/node-jose";
4
- import * as jwt from "./vendor/backend/jsonwebtoken";
1
+ import { assert, isAmong, id, type Equals, is } from "./vendor/backend/tsafe";
2
+ import {
3
+ decodeProtectedHeader,
4
+ jwtVerify,
5
+ createLocalJWKSet,
6
+ errors,
7
+ type JWTPayload
8
+ } from "./vendor/backend/jose";
5
9
  import { z } from "./vendor/backend/zod";
6
- import { Evt } from "./vendor/backend/evt";
7
- import { throttleTime } from "./vendor/backend/evt";
10
+ import { Evt, throttleTime } from "./vendor/backend/evt";
11
+ import type { ZodSchemaLike } from "./tools/ZodSchemaLike";
12
+
13
+ /**
14
+ * Claims defined by RFC 9068: "JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens"
15
+ * https://datatracker.ietf.org/doc/html/rfc9068
16
+ *
17
+ * These tokens are intended for consumption by resource servers.
18
+ */
19
+ export type DecodedAccessToken_RFC9068 = {
20
+ // --- REQUIRED (MUST) ---
21
+ iss: string; // Issuer Identifier
22
+ sub: string; // Subject Identifier
23
+ aud: string | string[]; // Audience(s)
24
+ exp: number; // Expiration time (seconds since epoch)
25
+ iat: number; // Issued-at time (seconds since epoch)
26
+
27
+ // --- RECOMMENDED (SHOULD) ---
28
+ client_id?: string; // OAuth2 Client ID that requested the token
29
+ scope?: string; // Space-separated list of granted scopes
30
+ jti?: string; // Unique JWT ID (for replay detection)
31
+
32
+ // --- OPTIONAL / EXTENSION CLAIMS ---
33
+ nbf?: number; // Not-before time (standard JWT claim)
34
+ auth_time?: number; // Time of user authentication (optional)
35
+ cnf?: Record<string, unknown>; // Confirmation (e.g. proof-of-possession)
36
+ [key: string]: unknown; // Allow custom claims (e.g. roles, groups)
37
+ };
8
38
 
9
- export type ParamsOfCreateOidcBackend<DecodedAccessToken extends Record<string, unknown>> = {
39
+ const zDecodedAccessToken_RFC9068 = (() => {
40
+ type TargetType = DecodedAccessToken_RFC9068;
41
+
42
+ const zTargetType = z
43
+ .object({
44
+ iss: z.string(),
45
+ sub: z.string(),
46
+ aud: z.union([z.string(), z.array(z.string())]),
47
+ exp: z.number(),
48
+ iat: z.number(),
49
+ client_id: z.string().optional(),
50
+ scope: z.string().optional(),
51
+ jti: z.string().optional(),
52
+ nbf: z.number().optional(),
53
+ auth_time: z.number().optional(),
54
+ cnf: z.record(z.unknown()).optional()
55
+ })
56
+ .catchall(z.unknown());
57
+
58
+ type InferredType = z.infer<typeof zTargetType>;
59
+
60
+ assert<Equals<TargetType, InferredType>>;
61
+
62
+ return id<z.ZodType<TargetType>>(zTargetType);
63
+ })();
64
+
65
+ export type ParamsOfCreateOidcBackend<DecodedAccessToken> = {
10
66
  issuerUri: string;
11
- decodedAccessTokenSchema?: { parse: (data: unknown) => DecodedAccessToken };
67
+ decodedAccessTokenSchema?: ZodSchemaLike<DecodedAccessToken_RFC9068, DecodedAccessToken>;
12
68
  };
13
69
 
14
70
  export type OidcBackend<DecodedAccessToken extends Record<string, unknown>> = {
15
71
  verifyAndDecodeAccessToken(params: {
16
72
  accessToken: string;
17
- }): ResultOfAccessTokenVerify<DecodedAccessToken>;
73
+ }): Promise<ResultOfAccessTokenVerify<DecodedAccessToken>>;
18
74
  };
19
75
 
20
76
  export type ResultOfAccessTokenVerify<DecodedAccessToken> =
@@ -24,7 +80,9 @@ export type ResultOfAccessTokenVerify<DecodedAccessToken> =
24
80
  export namespace ResultOfAccessTokenVerify {
25
81
  export type Valid<DecodedAccessToken> = {
26
82
  isValid: true;
83
+
27
84
  decodedAccessToken: DecodedAccessToken;
85
+ decodedAccessToken_original: DecodedAccessToken_RFC9068;
28
86
 
29
87
  errorCase?: never;
30
88
  errorMessage?: never;
@@ -36,13 +94,14 @@ export namespace ResultOfAccessTokenVerify {
36
94
  errorMessage: string;
37
95
 
38
96
  decodedAccessToken?: never;
97
+ decodedAccessToken_original?: never;
39
98
  };
40
99
  }
41
100
 
42
- export async function createOidcBackend<DecodedAccessToken extends Record<string, unknown>>(
43
- params: ParamsOfCreateOidcBackend<DecodedAccessToken>
44
- ): Promise<OidcBackend<DecodedAccessToken>> {
45
- const { issuerUri, decodedAccessTokenSchema = z.record(z.unknown()) } = params;
101
+ export async function createOidcBackend<
102
+ DecodedAccessToken extends Record<string, unknown> = DecodedAccessToken_RFC9068
103
+ >(params: ParamsOfCreateOidcBackend<DecodedAccessToken>): Promise<OidcBackend<DecodedAccessToken>> {
104
+ const { issuerUri, decodedAccessTokenSchema } = params;
46
105
 
47
106
  let publicSigningKeys = await fetchPublicSigningKeys({ issuerUri });
48
107
 
@@ -51,8 +110,8 @@ export async function createOidcBackend<DecodedAccessToken extends Record<string
51
110
  evtInvalidSignature.pipe(throttleTime(3600_000)).attach(async () => {
52
111
  const publicSigningKeys_new = await (async function callee(
53
112
  count: number
54
- ): Promise<PublicSigningKey[] | undefined> {
55
- let wrap;
113
+ ): Promise<PublicSigningKeys | undefined> {
114
+ let wrap: PublicSigningKeys | undefined;
56
115
 
57
116
  try {
58
117
  wrap = await fetchPublicSigningKeys({ issuerUri });
@@ -89,99 +148,66 @@ export async function createOidcBackend<DecodedAccessToken extends Record<string
89
148
  });
90
149
 
91
150
  return {
92
- verifyAndDecodeAccessToken: ({ accessToken }) => {
151
+ verifyAndDecodeAccessToken: async ({ accessToken }) => {
93
152
  let kid: string;
94
- let alg: jwt.Algorithm;
153
+ let alg: string;
95
154
 
96
155
  {
97
- const jwtHeader_b64 = accessToken.split(".")[0];
98
-
99
- let jwtHeader: string;
156
+ let header: ReturnType<typeof decodeProtectedHeader>;
100
157
 
101
158
  try {
102
- jwtHeader = Buffer.from(jwtHeader_b64, "base64").toString("utf8");
159
+ header = decodeProtectedHeader(accessToken);
103
160
  } catch {
104
161
  return {
105
162
  isValid: false,
106
163
  errorCase: "invalid signature",
107
- errorMessage: "Failed to decode the JWT header as a base64 string"
164
+ errorMessage: "Failed to decode the JWT header"
108
165
  };
109
166
  }
110
167
 
111
- let decodedHeader: unknown;
168
+ const { kid: kidFromHeader, alg: algFromHeader } = header;
112
169
 
113
- try {
114
- decodedHeader = JSON.parse(jwtHeader);
115
- } catch {
170
+ if (typeof kidFromHeader !== "string" || kidFromHeader.length === 0) {
116
171
  return {
117
172
  isValid: false,
118
173
  errorCase: "invalid signature",
119
- errorMessage: "Failed to parse the JWT header as a JSON"
174
+ errorMessage: "The decoded JWT header does not have a kid property"
120
175
  };
121
176
  }
122
177
 
123
- type DecodedHeader = {
124
- kid: string;
125
- alg: string;
126
- };
127
-
128
- const zDecodedHeader = z.object({
129
- kid: z.string(),
130
- alg: z.string()
131
- });
132
-
133
- assert<Equals<DecodedHeader, z.infer<typeof zDecodedHeader>>>();
134
-
135
- try {
136
- zDecodedHeader.parse(decodedHeader);
137
- } catch {
178
+ if (typeof algFromHeader !== "string") {
138
179
  return {
139
180
  isValid: false,
140
181
  errorCase: "invalid signature",
141
- errorMessage: "The decoded JWT header does not have a kid property"
182
+ errorMessage: "The decoded JWT header does not specify an algorithm"
142
183
  };
143
184
  }
144
185
 
145
- assert(is<DecodedHeader>(decodedHeader));
146
-
147
- {
148
- const supportedAlgs = [
149
- "RS256",
150
- "RS384",
151
- "RS512",
152
- "ES256",
153
- "ES384",
154
- "ES512",
155
- "PS256",
156
- "PS384",
157
- "PS512"
158
- ] as const;
159
-
160
- assert<
161
- Equals<
162
- (typeof supportedAlgs)[number] | "none" | "HS256" | "HS384" | "HS512",
163
- jwt.Algorithm
164
- >
165
- >();
166
-
167
- if (!isAmong(supportedAlgs, decodedHeader.alg)) {
168
- return {
169
- isValid: false,
170
- errorCase: "invalid signature",
171
- errorMessage: `Unsupported or too week algorithm ${decodedHeader.alg}`
172
- };
173
- }
186
+ const supportedAlgs = [
187
+ "RS256",
188
+ "RS384",
189
+ "RS512",
190
+ "ES256",
191
+ "ES384",
192
+ "ES512",
193
+ "PS256",
194
+ "PS384",
195
+ "PS512"
196
+ ] as const;
197
+
198
+ if (!isAmong(supportedAlgs, algFromHeader as (typeof supportedAlgs)[number])) {
199
+ return {
200
+ isValid: false,
201
+ errorCase: "invalid signature",
202
+ errorMessage: `Unsupported or too weak algorithm ${algFromHeader}`
203
+ };
174
204
  }
175
205
 
176
- kid = decodedHeader.kid;
177
- alg = decodedHeader.alg;
206
+ kid = kidFromHeader;
207
+ alg = algFromHeader;
178
208
  }
179
209
 
180
- const publicSigningKey = publicSigningKeys.find(
181
- publicSigningKey => publicSigningKey.kid === kid
182
- );
183
-
184
- if (publicSigningKey === undefined) {
210
+ if (!publicSigningKeys.kidSet.has(kid)) {
185
211
  return {
186
212
  isValid: false,
187
213
  errorCase: "invalid signature",
@@ -189,74 +215,82 @@ export async function createOidcBackend<DecodedAccessToken extends Record<string
189
215
  };
190
216
  }
191
217
 
192
- let result = id<ResultOfAccessTokenVerify<DecodedAccessToken> | undefined>(undefined);
193
-
194
- jwt.verify(
195
- accessToken,
196
- publicSigningKey.publicKey,
197
- { algorithms: [alg] },
198
- (err, decoded) => {
199
- invalid: {
200
- if (!err) {
201
- break invalid;
202
- }
203
-
204
- if (err.name === "TokenExpiredError") {
205
- result = id<ResultOfAccessTokenVerify.Invalid>({
206
- isValid: false,
207
- errorCase: "expired",
208
- errorMessage: err.message
209
- });
210
- return;
211
- }
212
-
213
- evtInvalidSignature.post();
214
-
215
- result = id<ResultOfAccessTokenVerify.Invalid>({
216
- isValid: false,
217
- errorCase: "invalid signature",
218
- errorMessage: err.message
219
- });
220
-
221
- return;
222
- }
223
-
224
- let decodedAccessToken: DecodedAccessToken;
225
-
226
- try {
227
- decodedAccessToken = decodedAccessTokenSchema.parse(
228
- decoded
229
- ) as DecodedAccessToken;
230
- } catch (error) {
231
- result = id<ResultOfAccessTokenVerify.Invalid>({
232
- isValid: false,
233
- errorCase: "does not respect schema",
234
- errorMessage: String(error)
235
- });
236
-
237
- return;
238
- }
239
-
240
- result = id<ResultOfAccessTokenVerify.Valid<DecodedAccessToken>>({
241
- isValid: true,
242
- decodedAccessToken: decodedAccessToken
218
+ let payload: JWTPayload;
219
+
220
+ try {
221
+ const verification = await jwtVerify(accessToken, publicSigningKeys.keyResolver, {
222
+ algorithms: [alg]
223
+ });
224
+
225
+ payload = verification.payload;
226
+ } catch (error) {
227
+ if (error instanceof errors.JWTExpired) {
228
+ return id<ResultOfAccessTokenVerify.Invalid>({
229
+ isValid: false,
230
+ errorCase: "expired",
231
+ errorMessage: error.message
243
232
  });
244
233
  }
245
- );
246
234
 
247
- assert(result !== undefined, "0522e6");
235
+ evtInvalidSignature.post();
236
+
237
+ return id<ResultOfAccessTokenVerify.Invalid>({
238
+ isValid: false,
239
+ errorCase: "invalid signature",
240
+ errorMessage: error instanceof Error ? error.message : String(error)
241
+ });
242
+ }
248
243
 
249
- return result;
244
+ const decodedAccessToken_unknown = payload as unknown;
245
+
246
+ try {
247
+ zDecodedAccessToken_RFC9068.parse(decodedAccessToken_unknown);
248
+ } catch (error) {
249
+ return id<ResultOfAccessTokenVerify.Invalid>({
250
+ isValid: false,
251
+ errorCase: "does not respect schema",
252
+ errorMessage: [
253
+ `The decoded access token does not satisfies`,
254
+ `the shape mandated by RFC9068: ${String(error)}`
255
+ ].join(" ")
256
+ });
257
+ }
258
+
259
+ assert(is<DecodedAccessToken_RFC9068>(decodedAccessToken_unknown));
260
+
261
+ const decodedAccessToken_original = decodedAccessToken_unknown;
262
+
263
+ let decodedAccessToken: DecodedAccessToken;
264
+
265
+ if (decodedAccessTokenSchema === undefined) {
266
+ decodedAccessToken = decodedAccessToken_original as unknown as DecodedAccessToken;
267
+ } else {
268
+ try {
269
+ decodedAccessToken = decodedAccessTokenSchema.parse(decodedAccessToken_original);
270
+ } catch (error) {
271
+ return id<ResultOfAccessTokenVerify.Invalid>({
272
+ isValid: false,
273
+ errorCase: "does not respect schema",
274
+ errorMessage: String(error)
275
+ });
276
+ }
277
+ }
278
+
279
+ return id<ResultOfAccessTokenVerify.Valid<DecodedAccessToken>>({
280
+ isValid: true,
281
+ decodedAccessToken,
282
+ decodedAccessToken_original
283
+ });
250
284
  }
251
285
  };
252
286
  }
253
287
 
254
- type PublicSigningKey = {
255
- kid: string;
256
- publicKey: string;
288
+ type PublicSigningKeys = {
289
+ keyResolver: ReturnType<typeof createLocalJWKSet>;
290
+ kidSet: Set<string>;
257
291
  };
258
292
 
259
- async function fetchPublicSigningKeys(params: { issuerUri: string }): Promise<PublicSigningKey[]> {
293
+ async function fetchPublicSigningKeys(params: { issuerUri: string }): Promise<PublicSigningKeys> {
260
294
  const { issuerUri } = params;
261
295
 
262
296
  const { jwks_uri } = await (async () => {
@@ -325,9 +359,8 @@ async function fetchPublicSigningKeys(params: { issuerUri: string }): Promise<Pu
325
359
  keys: {
326
360
  kid: string;
327
361
  kty: string;
328
- e?: string;
329
- n?: string;
330
- use: string;
362
+ use?: string;
363
+ alg?: string;
331
364
  }[];
332
365
  };
333
366
 
@@ -336,9 +369,8 @@ async function fetchPublicSigningKeys(params: { issuerUri: string }): Promise<Pu
336
369
  z.object({
337
370
  kid: z.string(),
338
371
  kty: z.string(),
339
- e: z.string().optional(),
340
- n: z.string().optional(),
341
- use: z.string()
372
+ use: z.string().optional(),
373
+ alg: z.string().optional()
342
374
  })
343
375
  )
344
376
  });
@@ -357,35 +389,38 @@ async function fetchPublicSigningKeys(params: { issuerUri: string }): Promise<Pu
357
389
  return { jwks };
358
390
  })();
359
391
 
360
- const publicSigningKeys: PublicSigningKey[] = await Promise.all(
361
- jwks.keys
362
- .filter(({ use }) => use === "sig")
363
- .map(({ kid, kty, e, n }) => {
364
- if (kty !== "RSA") {
365
- return undefined;
366
- }
392
+ //const signatureKeys = jwks.keys.filter((key): key is JWKS["keys"][number] & { kid: string } => {
393
+ const signatureKeys = jwks.keys.filter(key => {
394
+ if (typeof key.kid !== "string" || key.kid.length === 0) {
395
+ return false;
396
+ }
367
397
 
368
- assert(e !== undefined, "e is undefined");
369
- assert(n !== undefined, "n is undefined");
398
+ if (key.use !== undefined && key.use !== "sig") {
399
+ return false;
400
+ }
370
401
 
371
- return { kid, e, n };
372
- })
373
- .filter(exclude(undefined))
374
- .map(async ({ kid, e, n }) => {
375
- const key = await JWK.asKey({ kty: "RSA", e, n });
376
- const publicKey = key.toPEM(false);
402
+ const supportedKty = ["RSA", "EC"] as const;
377
403
 
378
- return {
379
- kid,
380
- publicKey
381
- };
382
- })
383
- );
404
+ if (!supportedKty.includes(key.kty as (typeof supportedKty)[number])) {
405
+ return false;
406
+ }
407
+
408
+ return true;
409
+ });
384
410
 
385
411
  assert(
386
- publicSigningKeys.length !== 0,
412
+ signatureKeys.length !== 0,
387
413
  `No public signing key found at ${jwks_uri}, ${JSON.stringify(jwks, null, 2)}`
388
414
  );
389
415
 
390
- return publicSigningKeys;
416
+ const kidSet = new Set(signatureKeys.map(({ kid }) => kid));
417
+
418
+ const keyResolver = createLocalJWKSet({
419
+ keys: signatureKeys
420
+ });
421
+
422
+ return {
423
+ keyResolver,
424
+ kidSet
425
+ };
391
426
  }
package/src/core/Oidc.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { OidcInitializationError } from "./OidcInitializationError";
2
2
 
3
3
  export declare type Oidc<
4
- DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base
4
+ DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_OidcCoreSpec
5
5
  > = Oidc.LoggedIn<DecodedIdToken> | Oidc.NotLoggedIn;
6
6
 
7
7
  export declare namespace Oidc {
@@ -91,9 +91,9 @@ export declare namespace Oidc {
91
91
  isNewBrowserSession: boolean;
92
92
  };
93
93
 
94
- export type Tokens<DecodedIdToken extends Record<string, unknown> = Tokens.DecodedIdToken_base> =
95
- | Tokens.WithRefreshToken<DecodedIdToken>
96
- | Tokens.WithoutRefreshToken<DecodedIdToken>;
94
+ export type Tokens<
95
+ DecodedIdToken extends Record<string, unknown> = Tokens.DecodedIdToken_OidcCoreSpec
96
+ > = Tokens.WithRefreshToken<DecodedIdToken> | Tokens.WithoutRefreshToken<DecodedIdToken>;
97
97
 
98
98
  export namespace Tokens {
99
99
  export type Common<DecodedIdToken> = {
@@ -112,7 +112,7 @@ export declare namespace Oidc {
112
112
  *
113
113
  * `decodedIdToken_original` is the actual decoded payload of the id_token, untransformed.
114
114
  * */
115
- decodedIdToken_original: DecodedIdToken_base;
115
+ decodedIdToken_original: DecodedIdToken_OidcCoreSpec;
116
116
  /** Millisecond epoch in the server's time, read from id_token's JWT, iat claim value */
117
117
  issuedAtTime: number;
118
118
 
@@ -132,12 +132,42 @@ export declare namespace Oidc {
132
132
  refreshTokenExpirationTime?: never;
133
133
  };
134
134
 
135
- export type DecodedIdToken_base = {
136
- iss: string;
137
- sub: string;
138
- aud: string | string[];
139
- exp: number;
140
- iat: number;
135
+ export type DecodedIdToken_OidcCoreSpec = {
136
+ // REQUIRED
137
+ iss: string; // Issuer Identifier
138
+ sub: string; // Subject Identifier
139
+ aud: string | string[]; // Audience(s)
140
+ exp: number; // Expiration time (Unix seconds)
141
+ iat: number; // Issued-at time (Unix seconds)
142
+
143
+ // CONDITIONAL
144
+ auth_time?: number; // Authentication time
145
+ nonce?: string; // Nonce
146
+ acr?: string; // Authentication Context Class Reference
147
+ amr?: string[]; // Authentication Methods References
148
+ azp?: string; // Authorized party (for multiple audiences)
149
+
150
+ // OPTIONAL standard user claims (OpenID §5.1)
151
+ name?: string;
152
+ given_name?: string;
153
+ family_name?: string;
154
+ middle_name?: string;
155
+ nickname?: string;
156
+ preferred_username?: string;
157
+ profile?: string;
158
+ picture?: string;
159
+ website?: string;
160
+ email?: string;
161
+ email_verified?: boolean;
162
+ gender?: string;
163
+ birthdate?: string;
164
+ zoneinfo?: string;
165
+ locale?: string;
166
+ phone_number?: string;
167
+ phone_number_verified?: boolean;
168
+ address?: Record<string, unknown>;
169
+ updated_at?: number;
170
+
141
171
  [claimName: string]: unknown;
142
172
  };
143
173
  }
@@ -56,7 +56,7 @@ import { getIsValidRemoteJson } from "../tools/getIsValidRemoteJson";
56
56
  const VERSION = "{{OIDC_SPA_VERSION}}";
57
57
 
58
58
  export type ParamsOfCreateOidc<
59
- DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base,
59
+ DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_OidcCoreSpec,
60
60
  AutoLogin extends boolean = false
61
61
  > = {
62
62
  /**
@@ -67,7 +67,13 @@ export type ParamsOfCreateOidc<
67
67
  */
68
68
  homeUrl: string;
69
69
 
70
+ /**
71
+ * See: https://docs.oidc-spa.dev/v/v8/providers-configuration/provider-configuration
72
+ */
70
73
  issuerUri: string;
74
+ /**
75
+ * See: https://docs.oidc-spa.dev/v/v8/providers-configuration/provider-configuration
76
+ */
71
77
  clientId: string;
72
78
  /**
73
79
  * The scopes being requested from the OIDC/OAuth2 provider (default: `["profile"]`
@@ -122,7 +128,7 @@ export type ParamsOfCreateOidc<
122
128
  postLoginRedirectUrl?: string;
123
129
 
124
130
  decodedIdTokenSchema?: {
125
- parse: (decodedIdToken_original: Oidc.Tokens.DecodedIdToken_base) => DecodedIdToken;
131
+ parse: (decodedIdToken_original: Oidc.Tokens.DecodedIdToken_OidcCoreSpec) => DecodedIdToken;
126
132
  };
127
133
 
128
134
  /**
@@ -132,6 +138,9 @@ export type ParamsOfCreateOidc<
132
138
  * WARNING: It should be configured on the identity server side
133
139
  * as it's the authoritative source for security policies and not the client.
134
140
  * If you don't provide this parameter it will be inferred from the refresh token expiration time.
141
+ * Some provider however don't issue a refresh token or do not correctly set the
142
+ * expiration time. This parameter enable you to hard code the value to compensate
143
+ * the shortcoming of your auth server.
135
144
  * */
136
145
  idleSessionLifetimeInSeconds?: number;
137
146
 
@@ -209,7 +218,7 @@ globalContext.evtRequestToPersistTokens.subscribe(() => {
209
218
 
210
219
  /** @see: https://docs.oidc-spa.dev/v/v8/usage */
211
220
  export async function createOidc<
212
- DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base,
221
+ DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_OidcCoreSpec,
213
222
  AutoLogin extends boolean = false
214
223
  >(
215
224
  params: ParamsOfCreateOidc<DecodedIdToken, AutoLogin>
@@ -6,6 +6,7 @@ import {
6
6
  postEncryptedAuthResponseToParent,
7
7
  preventSessionStorageSetItemOfPublicKeyByThirdParty
8
8
  } from "./iframeMessageProtection";
9
+ import { setOidcRequiredPostHydrationReplaceNavigationUrl } from "./requiredPostHydrationReplaceNavigationUrl";
9
10
  import { isBrowser } from "../tools/isBrowser";
10
11
 
11
12
  let hasEarlyInitBeenCalled = false;
@@ -16,6 +17,7 @@ export function oidcEarlyInit(params: {
16
17
  // NOTE: Made optional just to avoid breaking change.
17
18
  // Will be made mandatory next major.
18
19
  freezeWebSocket?: boolean;
20
+ isPostLoginRedirectManual?: boolean;
19
21
  }) {
20
22
  if (hasEarlyInitBeenCalled) {
21
23
  throw new Error("oidc-spa: oidcEarlyInit() Should be called only once");
@@ -29,9 +31,14 @@ export function oidcEarlyInit(params: {
29
31
 
30
32
  captureApisForIframeProtection();
31
33
 
32
- const { freezeFetch, freezeXMLHttpRequest, freezeWebSocket = false } = params ?? {};
34
+ const {
35
+ freezeFetch,
36
+ freezeXMLHttpRequest,
37
+ freezeWebSocket = false,
38
+ isPostLoginRedirectManual = false
39
+ } = params ?? {};
33
40
 
34
- const { shouldLoadApp } = handleOidcCallback();
41
+ const { shouldLoadApp } = handleOidcCallback({ isPostLoginRedirectManual });
35
42
 
36
43
  if (shouldLoadApp) {
37
44
  if (freezeXMLHttpRequest) {
@@ -112,7 +119,11 @@ export function getRootRelativeOriginalLocationHref() {
112
119
  return rootRelativeOriginalLocationHref;
113
120
  }
114
121
 
115
- function handleOidcCallback(): { shouldLoadApp: boolean } {
122
+ function handleOidcCallback(params: { isPostLoginRedirectManual?: boolean }): {
123
+ shouldLoadApp: boolean;
124
+ } {
125
+ const { isPostLoginRedirectManual } = params;
126
+
116
127
  const location_urlObj = new URL(window.location.href);
117
128
 
118
129
  const locationHrefAssessment = (() => {
@@ -209,7 +220,11 @@ function handleOidcCallback(): { shouldLoadApp: boolean } {
209
220
  return stateData.rootRelativeRedirectUrl;
210
221
  })();
211
222
 
212
- history.replaceState({}, "", rootRelativeRedirectUrl);
223
+ if (isPostLoginRedirectManual) {
224
+ setOidcRequiredPostHydrationReplaceNavigationUrl({ rootRelativeRedirectUrl });
225
+ } else {
226
+ history.replaceState({}, "", rootRelativeRedirectUrl);
227
+ }
213
228
 
214
229
  return { shouldLoadApp: true };
215
230
  }