oidc-spa 8.6.18 → 8.7.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.
Files changed (153) hide show
  1. package/README.md +2 -2
  2. package/backend.d.ts +3 -20
  3. package/backend.js +50 -242
  4. package/backend.js.map +1 -1
  5. package/core/OidcMetadata.d.ts +2 -2
  6. package/core/OidcMetadata.js.map +1 -1
  7. package/core/createOidc.d.ts +2 -4
  8. package/core/createOidc.js +46 -6
  9. package/core/createOidc.js.map +1 -1
  10. package/core/dpop.d.ts +20 -0
  11. package/core/dpop.js +389 -0
  12. package/core/dpop.js.map +1 -0
  13. package/core/earlyInit.js +2 -0
  14. package/core/earlyInit.js.map +1 -1
  15. package/core/evtIsUserActive.d.ts +8 -1
  16. package/core/evtIsUserActive.js +4 -2
  17. package/core/evtIsUserActive.js.map +1 -1
  18. package/core/oidcClientTsUserToTokens.d.ts +1 -0
  19. package/core/oidcClientTsUserToTokens.js +15 -5
  20. package/core/oidcClientTsUserToTokens.js.map +1 -1
  21. package/core/tokenExfiltrationDefense.js +49 -6
  22. package/core/tokenExfiltrationDefense.js.map +1 -1
  23. package/esm/angular.d.ts +2 -0
  24. package/esm/angular.mjs.map +1 -1
  25. package/esm/backend.d.ts +3 -20
  26. package/esm/backend.mjs +50 -242
  27. package/esm/backend.mjs.map +1 -1
  28. package/esm/core/OidcMetadata.d.ts +2 -2
  29. package/esm/core/OidcMetadata.mjs.map +1 -1
  30. package/esm/core/createOidc.d.ts +2 -4
  31. package/esm/core/createOidc.mjs +46 -6
  32. package/esm/core/createOidc.mjs.map +1 -1
  33. package/esm/core/dpop.d.ts +20 -0
  34. package/esm/core/dpop.mjs +384 -0
  35. package/esm/core/dpop.mjs.map +1 -0
  36. package/esm/core/earlyInit.mjs +2 -0
  37. package/esm/core/earlyInit.mjs.map +1 -1
  38. package/esm/core/evtIsUserActive.d.ts +8 -1
  39. package/esm/core/evtIsUserActive.mjs +4 -2
  40. package/esm/core/evtIsUserActive.mjs.map +1 -1
  41. package/esm/core/oidcClientTsUserToTokens.d.ts +1 -0
  42. package/esm/core/oidcClientTsUserToTokens.mjs +15 -5
  43. package/esm/core/oidcClientTsUserToTokens.mjs.map +1 -1
  44. package/esm/core/tokenExfiltrationDefense.mjs +49 -6
  45. package/esm/core/tokenExfiltrationDefense.mjs.map +1 -1
  46. package/esm/react-spa/createOidcSpaApi.mjs +2 -1
  47. package/esm/react-spa/createOidcSpaApi.mjs.map +1 -1
  48. package/esm/react-spa/types.d.ts +2 -0
  49. package/esm/server/createOidcSpaUtils.d.ts +5 -0
  50. package/esm/server/createOidcSpaUtils.mjs +639 -0
  51. package/esm/server/createOidcSpaUtils.mjs.map +1 -0
  52. package/esm/server/index.d.ts +2 -0
  53. package/esm/server/index.mjs +3 -0
  54. package/esm/server/index.mjs.map +1 -0
  55. package/esm/server/types.d.ts +79 -0
  56. package/esm/server/types.mjs +2 -0
  57. package/esm/server/types.mjs.map +1 -0
  58. package/esm/server/utilsBuilder.d.ts +10 -0
  59. package/esm/server/utilsBuilder.mjs +13 -0
  60. package/esm/server/utilsBuilder.mjs.map +1 -0
  61. package/esm/tanstack-start/react/accessTokenValidation_rfc9068.d.ts +1 -1
  62. package/esm/tanstack-start/react/accessTokenValidation_rfc9068.mjs +102 -94
  63. package/esm/tanstack-start/react/accessTokenValidation_rfc9068.mjs.map +1 -1
  64. package/esm/tanstack-start/react/createOidcSpaApi.d.ts +2 -2
  65. package/esm/tanstack-start/react/createOidcSpaApi.mjs +60 -51
  66. package/esm/tanstack-start/react/createOidcSpaApi.mjs.map +1 -1
  67. package/esm/tanstack-start/react/index.d.ts +1 -1
  68. package/esm/tanstack-start/react/index.mjs +2 -2
  69. package/esm/tanstack-start/react/index.mjs.map +1 -1
  70. package/esm/tanstack-start/react/types.d.ts +36 -11
  71. package/esm/tanstack-start/react/{apiBuilder.d.ts → utilsBuilder.d.ts} +9 -9
  72. package/esm/tanstack-start/react/{apiBuilder.mjs → utilsBuilder.mjs} +6 -6
  73. package/esm/tanstack-start/react/utilsBuilder.mjs.map +1 -0
  74. package/esm/tools/generateES256DPoPProof.d.ts +8 -0
  75. package/esm/tools/generateES256DPoPProof.mjs +48 -0
  76. package/esm/tools/generateES256DPoPProof.mjs.map +1 -0
  77. package/esm/tools/getServerDateNow.d.ts +5 -0
  78. package/esm/tools/getServerDateNow.mjs +7 -0
  79. package/esm/tools/getServerDateNow.mjs.map +1 -0
  80. package/esm/tools/startCountdown.mjs +9 -3
  81. package/esm/tools/startCountdown.mjs.map +1 -1
  82. package/esm/vendor/{backend → server}/evt.mjs +84 -140
  83. package/esm/vendor/{backend → server}/jose.mjs +5 -27
  84. package/esm/vendor/{backend → server}/tsafe.d.ts +1 -0
  85. package/esm/vendor/{backend → server}/tsafe.mjs +6 -0
  86. package/esm/vendor/{backend → server}/zod.mjs +196 -50
  87. package/package.json +6 -1
  88. package/react-spa/createOidcSpaApi.js +2 -1
  89. package/react-spa/createOidcSpaApi.js.map +1 -1
  90. package/react-spa/types.d.ts +2 -0
  91. package/server/createOidcSpaUtils.d.ts +5 -0
  92. package/server/createOidcSpaUtils.js +642 -0
  93. package/server/createOidcSpaUtils.js.map +1 -0
  94. package/server/index.d.ts +2 -0
  95. package/server/index.js +6 -0
  96. package/server/index.js.map +1 -0
  97. package/server/types.d.ts +79 -0
  98. package/server/types.js +3 -0
  99. package/server/types.js.map +1 -0
  100. package/server/utilsBuilder.d.ts +10 -0
  101. package/server/utilsBuilder.js +16 -0
  102. package/server/utilsBuilder.js.map +1 -0
  103. package/src/angular.ts +3 -0
  104. package/src/backend.ts +63 -364
  105. package/src/core/OidcMetadata.ts +4 -2
  106. package/src/core/createOidc.ts +61 -9
  107. package/src/core/dpop.ts +583 -0
  108. package/src/core/earlyInit.ts +3 -0
  109. package/src/core/evtIsUserActive.ts +11 -5
  110. package/src/core/oidcClientTsUserToTokens.ts +18 -4
  111. package/src/core/tokenExfiltrationDefense.ts +60 -5
  112. package/src/react-spa/createOidcSpaApi.ts +2 -1
  113. package/src/react-spa/types.tsx +3 -0
  114. package/src/server/createOidcSpaUtils.ts +848 -0
  115. package/src/server/index.ts +4 -0
  116. package/src/server/types.tsx +99 -0
  117. package/src/server/utilsBuilder.ts +41 -0
  118. package/src/tanstack-start/react/accessTokenValidation_rfc9068.ts +134 -124
  119. package/src/tanstack-start/react/createOidcSpaApi.ts +73 -69
  120. package/src/tanstack-start/react/index.ts +2 -2
  121. package/src/tanstack-start/react/types.tsx +44 -12
  122. package/src/tanstack-start/react/{apiBuilder.ts → utilsBuilder.ts} +14 -14
  123. package/src/tools/generateES256DPoPProof.ts +74 -0
  124. package/src/tools/getServerDateNow.ts +11 -0
  125. package/src/tools/startCountdown.ts +10 -3
  126. package/src/vendor/{backend → server}/tsafe.ts +1 -0
  127. package/tools/generateES256DPoPProof.d.ts +8 -0
  128. package/tools/generateES256DPoPProof.js +51 -0
  129. package/tools/generateES256DPoPProof.js.map +1 -0
  130. package/tools/getServerDateNow.d.ts +5 -0
  131. package/tools/getServerDateNow.js +10 -0
  132. package/tools/getServerDateNow.js.map +1 -0
  133. package/tools/startCountdown.js +9 -3
  134. package/tools/startCountdown.js.map +1 -1
  135. package/vendor/server/evt.js +3 -0
  136. package/vendor/server/jose.js +3 -0
  137. package/vendor/{backend → server}/tsafe.d.ts +1 -0
  138. package/vendor/server/tsafe.js +2 -0
  139. package/vendor/server/zod.js +3 -0
  140. package/esm/tanstack-start/react/apiBuilder.mjs.map +0 -1
  141. package/vendor/backend/evt.js +0 -3
  142. package/vendor/backend/jose.js +0 -3
  143. package/vendor/backend/tsafe.js +0 -2
  144. package/vendor/backend/zod.js +0 -3
  145. /package/esm/vendor/{backend → server}/evt.d.ts +0 -0
  146. /package/esm/vendor/{backend → server}/jose.d.ts +0 -0
  147. /package/esm/vendor/{backend → server}/zod.d.ts +0 -0
  148. /package/src/vendor/{backend → server}/evt.ts +0 -0
  149. /package/src/vendor/{backend → server}/jose.ts +0 -0
  150. /package/src/vendor/{backend → server}/zod.ts +0 -0
  151. /package/vendor/{backend → server}/evt.d.ts +0 -0
  152. /package/vendor/{backend → server}/jose.d.ts +0 -0
  153. /package/vendor/{backend → server}/zod.d.ts +0 -0
@@ -0,0 +1,2 @@
1
+ export type * from "./types";
2
+ export declare const oidcSpa: import("./utilsBuilder").OidcSpaUtilsBuilder<import("./types").DecodedAccessToken_RFC9068, never>;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.oidcSpa = void 0;
4
+ const utilsBuilder_1 = require("./utilsBuilder");
5
+ exports.oidcSpa = utilsBuilder_1.oidcSpaUtilsBuilder;
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/server/index.ts"],"names":[],"mappings":";;;AACA,iDAAqD;AAExC,QAAA,OAAO,GAAG,kCAAmB,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Claims defined by RFC 9068: "JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens"
3
+ * https://datatracker.ietf.org/doc/html/rfc9068
4
+ *
5
+ * These tokens are intended for consumption by resource servers.
6
+ */
7
+ export type DecodedAccessToken_RFC9068 = {
8
+ iss: string;
9
+ sub: string;
10
+ aud: string | string[];
11
+ exp: number;
12
+ iat: number;
13
+ client_id?: string;
14
+ scope?: string;
15
+ jti?: string;
16
+ nbf?: number;
17
+ auth_time?: number;
18
+ cnf?: Record<string, unknown>;
19
+ [key: string]: unknown;
20
+ };
21
+ export type ValidateAndDecodeAccessToken<DecodedAccessToken> = (params: {
22
+ request: {
23
+ method: string;
24
+ url: string;
25
+ headers: Record<"Authorization" | "DPoP", string | null | undefined>;
26
+ };
27
+ }) => Promise<ValidateAndDecodeAccessToken.ReturnType<DecodedAccessToken>>;
28
+ export declare namespace ValidateAndDecodeAccessToken {
29
+ type ReturnType<DecodedAccessToken> = (ReturnType.Success<DecodedAccessToken> & {
30
+ errorCause?: never;
31
+ debugErrorMessage?: never;
32
+ }) | (ReturnType.Errored & {
33
+ decodedAccessToken?: never;
34
+ decodedAccessToken_original?: never;
35
+ accessToken?: never;
36
+ });
37
+ namespace ReturnType {
38
+ type Success<DecodedAccessToken> = {
39
+ isSuccess: true;
40
+ decodedAccessToken: DecodedAccessToken;
41
+ decodedAccessToken_original: DecodedAccessToken_RFC9068;
42
+ accessToken: string;
43
+ };
44
+ type Errored = {
45
+ isSuccess: false;
46
+ errorCause: "missing Authorization header" | "validation error" | "validation error - access token expired" | "validation error - invalid signature";
47
+ debugErrorMessage: string;
48
+ };
49
+ }
50
+ }
51
+ export type ParamsOfBootstrap<DecodedAccessToken> = ParamsOfBootstrap.Real | ParamsOfBootstrap.Mock<DecodedAccessToken>;
52
+ export declare namespace ParamsOfBootstrap {
53
+ type Real = {
54
+ implementation: "real";
55
+ issuerUri: string;
56
+ expectedAudience: string | undefined;
57
+ };
58
+ type Mock<DecodedAccessToken> = Mock.DecodeOnly | Mock.UseStaticIdentity<DecodedAccessToken>;
59
+ namespace Mock {
60
+ type Common = {
61
+ implementation: "mock";
62
+ };
63
+ export type DecodeOnly = Common & {
64
+ behavior: "decode only";
65
+ };
66
+ export type UseStaticIdentity<DecodedAccessToken> = Common & {
67
+ behavior: "use static identity";
68
+ decodedAccessToken_mock: DecodedAccessToken;
69
+ decodedAccessToken_original_mock?: DecodedAccessToken_RFC9068;
70
+ accessToken_mock?: string;
71
+ };
72
+ export {};
73
+ }
74
+ }
75
+ export type OidcSpaUtils<DecodedAccessToken> = {
76
+ bootstrapAuth: (params: ParamsOfBootstrap<DecodedAccessToken>) => Promise<void>;
77
+ validateAndDecodeAccessToken: ValidateAndDecodeAccessToken<DecodedAccessToken>;
78
+ ofTypeDecodedAccessToken: DecodedAccessToken;
79
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/server/types.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { OidcSpaUtils } from "./types";
2
+ import type { ZodSchemaLike } from "../tools/ZodSchemaLike";
3
+ import type { DecodedAccessToken_RFC9068 } from "./types";
4
+ export type OidcSpaUtilsBuilder<DecodedAccessToken extends Record<string, unknown> = DecodedAccessToken_RFC9068, ExcludedMethod extends "withExpectedDecodedAccessTokenShape" | "createUtils" = never> = Omit<{
5
+ withExpectedDecodedAccessTokenShape: <DecodedAccessToken extends Record<string, unknown>>(params: {
6
+ decodedAccessTokenSchema: ZodSchemaLike<DecodedAccessToken_RFC9068, DecodedAccessToken>;
7
+ }) => OidcSpaUtilsBuilder<DecodedAccessToken, ExcludedMethod | "withExpectedDecodedAccessTokenShape">;
8
+ createUtils: () => OidcSpaUtils<DecodedAccessToken>;
9
+ }, ExcludedMethod>;
10
+ export declare const oidcSpaUtilsBuilder: OidcSpaUtilsBuilder<DecodedAccessToken_RFC9068, never>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.oidcSpaUtilsBuilder = void 0;
4
+ const createOidcSpaUtils_1 = require("./createOidcSpaUtils");
5
+ function createOidcSpaUtilsBuilder(params) {
6
+ return {
7
+ withExpectedDecodedAccessTokenShape: ({ decodedAccessTokenSchema }) => createOidcSpaUtilsBuilder({ decodedAccessTokenSchema }),
8
+ createUtils: () => (0, createOidcSpaUtils_1.createOidcSpaUtils)({
9
+ decodedAccessTokenSchema: params.decodedAccessTokenSchema
10
+ })
11
+ };
12
+ }
13
+ exports.oidcSpaUtilsBuilder = createOidcSpaUtilsBuilder({
14
+ decodedAccessTokenSchema: undefined
15
+ });
16
+ //# sourceMappingURL=utilsBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilsBuilder.js","sourceRoot":"","sources":["../src/server/utilsBuilder.ts"],"names":[],"mappings":";;;AAEA,6DAA0D;AAqB1D,SAAS,yBAAyB,CAEhC,MAED;IACG,OAAO;QACH,mCAAmC,EAAE,CAAC,EAAE,wBAAwB,EAAE,EAAE,EAAE,CAClE,yBAAyB,CAAC,EAAE,wBAAwB,EAAE,CAAC;QAC3D,WAAW,EAAE,GAAG,EAAE,CACd,IAAA,uCAAkB,EAAqB;YACnC,wBAAwB,EAAE,MAAM,CAAC,wBAAwB;SAC5D,CAAC;KACT,CAAC;AACN,CAAC;AAEY,QAAA,mBAAmB,GAAG,yBAAyB,CAAC;IACzD,wBAAwB,EAAE,SAAS;CACtC,CAAC,CAAC"}
package/src/angular.ts CHANGED
@@ -169,6 +169,9 @@ export type ParamsOfProvide = {
169
169
  * Default is 60 seconds.
170
170
  */
171
171
  warnUserSecondsBeforeAutoLogout?: number;
172
+
173
+ /** Default: false */
174
+ dpop?: boolean;
172
175
  };
173
176
 
174
177
  assert<
package/src/backend.ts CHANGED
@@ -1,66 +1,9 @@
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";
9
- import { z } from "./vendor/backend/zod";
10
- import { Evt, throttleTime } from "./vendor/backend/evt";
1
+ import { assert, id } from "./vendor/server/tsafe";
11
2
  import type { ZodSchemaLike } from "./tools/ZodSchemaLike";
3
+ import { DecodedAccessToken_RFC9068 } from "./server/types";
4
+ import { oidcSpa } from "./server";
12
5
 
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
- };
38
-
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
- })();
6
+ export type { DecodedAccessToken_RFC9068 };
64
7
 
65
8
  export type ParamsOfCreateOidcBackend<DecodedAccessToken> = {
66
9
  issuerUri: string;
@@ -98,329 +41,85 @@ export namespace ResultOfAccessTokenVerify {
98
41
  };
99
42
  }
100
43
 
44
+ /** @deprecated: Use "oidc-spa/server" instead */
101
45
  export async function createOidcBackend<
102
46
  DecodedAccessToken extends Record<string, unknown> = DecodedAccessToken_RFC9068
103
47
  >(params: ParamsOfCreateOidcBackend<DecodedAccessToken>): Promise<OidcBackend<DecodedAccessToken>> {
104
48
  const { issuerUri, decodedAccessTokenSchema } = params;
105
49
 
106
- let publicSigningKeys = await fetchPublicSigningKeys({ issuerUri });
107
-
108
- const evtInvalidSignature = Evt.create<void>();
109
-
110
- evtInvalidSignature.pipe(throttleTime(3600_000)).attach(async () => {
111
- const publicSigningKeys_new = await (async function callee(
112
- count: number
113
- ): Promise<PublicSigningKeys | undefined> {
114
- let wrap: PublicSigningKeys | undefined;
115
-
116
- try {
117
- wrap = await fetchPublicSigningKeys({ issuerUri });
118
- } catch (error) {
119
- if (count === 9) {
120
- console.warn(
121
- `Failed to refresh public key and signing algorithm after ${count + 1} attempts`
122
- );
123
-
124
- return undefined;
125
- }
126
-
127
- const delayMs = 1000 * Math.pow(2, count);
128
-
129
- console.warn(
130
- `Failed to refresh public key and signing algorithm: ${String(
131
- error
132
- )}, retrying in ${delayMs}ms`
133
- );
50
+ const { bootstrapAuth, validateAndDecodeAccessToken } =
51
+ decodedAccessTokenSchema === undefined
52
+ ? oidcSpa.createUtils()
53
+ : oidcSpa.withExpectedDecodedAccessTokenShape({ decodedAccessTokenSchema }).createUtils();
134
54
 
135
- await new Promise(resolve => setTimeout(resolve, delayMs));
136
-
137
- return callee(count + 1);
138
- }
139
-
140
- return wrap;
141
- })(0);
142
-
143
- if (publicSigningKeys_new === undefined) {
144
- return;
145
- }
146
-
147
- publicSigningKeys = publicSigningKeys_new;
55
+ await bootstrapAuth({
56
+ implementation: "real",
57
+ issuerUri,
58
+ expectedAudience: undefined
148
59
  });
149
60
 
150
61
  return {
151
62
  verifyAndDecodeAccessToken: async ({ accessToken }) => {
152
- let kid: string;
153
- let alg: string;
154
-
155
- {
156
- let header: ReturnType<typeof decodeProtectedHeader>;
157
-
158
- try {
159
- header = decodeProtectedHeader(accessToken);
160
- } catch {
161
- return {
162
- isValid: false,
163
- errorCase: "invalid signature",
164
- errorMessage: "Failed to decode the JWT header"
165
- };
166
- }
167
-
168
- const { kid: kidFromHeader, alg: algFromHeader } = header;
169
-
170
- if (typeof kidFromHeader !== "string" || kidFromHeader.length === 0) {
171
- return {
172
- isValid: false,
173
- errorCase: "invalid signature",
174
- errorMessage: "The decoded JWT header does not have a kid property"
175
- };
176
- }
177
-
178
- if (typeof algFromHeader !== "string") {
179
- return {
180
- isValid: false,
181
- errorCase: "invalid signature",
182
- errorMessage: "The decoded JWT header does not specify an algorithm"
183
- };
184
- }
185
-
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
- };
204
- }
205
-
206
- kid = kidFromHeader;
207
- alg = algFromHeader;
208
- }
209
-
210
- if (!publicSigningKeys.kidSet.has(kid)) {
211
- return {
212
- isValid: false,
213
- errorCase: "invalid signature",
214
- errorMessage: `No public signing key found with kid ${kid}`
215
- };
216
- }
217
-
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
232
- });
63
+ const {
64
+ isSuccess,
65
+ errorCause,
66
+ debugErrorMessage,
67
+ decodedAccessToken,
68
+ decodedAccessToken_original
69
+ } = await validateAndDecodeAccessToken({
70
+ request: {
71
+ method: "GET",
72
+ url: "https://dummy.com",
73
+ headers: {
74
+ Authorization: `Bearer ${accessToken}`,
75
+ DPoP: undefined
76
+ }
233
77
  }
78
+ });
234
79
 
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
- }
243
-
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
- });
80
+ if (!isSuccess) {
81
+ switch (errorCause) {
82
+ case "missing Authorization header":
83
+ assert(false, "29330204");
84
+ case "validation error":
85
+ if (
86
+ debugErrorMessage.includes("shape") ||
87
+ debugErrorMessage.includes("schema")
88
+ ) {
89
+ return {
90
+ isValid: false,
91
+ errorCase: "does not respect schema",
92
+ errorMessage: debugErrorMessage
93
+ };
94
+ }
95
+
96
+ return {
97
+ isValid: false,
98
+ errorCase: "invalid signature",
99
+ errorMessage: debugErrorMessage
100
+ };
101
+
102
+ case "validation error - access token expired":
103
+ return {
104
+ isValid: false,
105
+ errorCase: "expired",
106
+ errorMessage: debugErrorMessage
107
+ };
108
+ case "validation error - invalid signature":
109
+ return {
110
+ isValid: false,
111
+ errorCase: "invalid signature",
112
+ errorMessage: debugErrorMessage
113
+ };
276
114
  }
277
115
  }
278
116
 
279
117
  return id<ResultOfAccessTokenVerify.Valid<DecodedAccessToken>>({
280
118
  isValid: true,
119
+ // @ts-expect-error
281
120
  decodedAccessToken,
282
121
  decodedAccessToken_original
283
122
  });
284
123
  }
285
124
  };
286
125
  }
287
-
288
- type PublicSigningKeys = {
289
- keyResolver: ReturnType<typeof createLocalJWKSet>;
290
- kidSet: Set<string>;
291
- };
292
-
293
- async function fetchPublicSigningKeys(params: { issuerUri: string }): Promise<PublicSigningKeys> {
294
- const { issuerUri } = params;
295
-
296
- const { jwks_uri } = await (async () => {
297
- const url = `${issuerUri.replace(/\/$/, "")}/.well-known/openid-configuration`;
298
-
299
- const response = await fetch(url);
300
-
301
- if (!response.ok) {
302
- throw new Error(
303
- `Failed to fetch openid configuration of the issuerUri: ${issuerUri} (${url}): ${response.statusText}`
304
- );
305
- }
306
-
307
- let data: unknown;
308
-
309
- try {
310
- data = await response.json();
311
- } catch (error) {
312
- throw new Error(`Failed to parse json from ${url}: ${String(error)}`);
313
- }
314
-
315
- {
316
- type WellKnownConfiguration = {
317
- jwks_uri: string;
318
- };
319
-
320
- const zWellKnownConfiguration = z.object({
321
- jwks_uri: z.string()
322
- });
323
-
324
- assert<Equals<WellKnownConfiguration, z.infer<typeof zWellKnownConfiguration>>>();
325
-
326
- try {
327
- zWellKnownConfiguration.parse(data);
328
- } catch {
329
- throw new Error(`${url} does not have a jwks_uri property`);
330
- }
331
-
332
- assert(is<WellKnownConfiguration>(data));
333
- }
334
-
335
- const { jwks_uri } = data;
336
-
337
- return { jwks_uri };
338
- })();
339
-
340
- const { jwks } = await (async () => {
341
- const response = await fetch(jwks_uri);
342
-
343
- if (!response.ok) {
344
- throw new Error(
345
- `Failed to fetch public key and algorithm from ${jwks_uri}: ${response.statusText}`
346
- );
347
- }
348
-
349
- let jwks: unknown;
350
-
351
- try {
352
- jwks = await response.json();
353
- } catch (error) {
354
- throw new Error(`Failed to parse json from ${jwks_uri}: ${String(error)}`);
355
- }
356
-
357
- {
358
- type Jwks = {
359
- keys: {
360
- kid: string;
361
- kty: string;
362
- use?: string;
363
- alg?: string;
364
- }[];
365
- };
366
-
367
- const zJwks = z.object({
368
- keys: z.array(
369
- z.object({
370
- kid: z.string(),
371
- kty: z.string(),
372
- use: z.string().optional(),
373
- alg: z.string().optional()
374
- })
375
- )
376
- });
377
-
378
- assert<Equals<Jwks, z.infer<typeof zJwks>>>();
379
-
380
- try {
381
- zJwks.parse(jwks);
382
- } catch {
383
- throw new Error(`${jwks_uri} does not have the expected shape`);
384
- }
385
-
386
- assert(is<Jwks>(jwks));
387
- }
388
-
389
- return { jwks };
390
- })();
391
-
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
- }
397
-
398
- if (key.use !== undefined && key.use !== "sig") {
399
- return false;
400
- }
401
-
402
- const supportedKty = ["RSA", "EC"] as const;
403
-
404
- if (!supportedKty.includes(key.kty as (typeof supportedKty)[number])) {
405
- return false;
406
- }
407
-
408
- return true;
409
- });
410
-
411
- assert(
412
- signatureKeys.length !== 0,
413
- `No public signing key found at ${jwks_uri}, ${JSON.stringify(jwks, null, 2)}`
414
- );
415
-
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
- };
426
- }
@@ -267,9 +267,11 @@ export type OidcMetadata = {
267
267
  * @see https://datatracker.ietf.org/doc/html/rfc8414
268
268
  */
269
269
  code_challenge_methods_supported: string[];
270
+
271
+ dpop_signing_alg_values_supported: string[];
270
272
  };
271
273
 
272
- assert<Equals<OidcMetadata, OidcClientTsOidcMetadata>>;
274
+ assert<Equals<Omit<OidcMetadata, "dpop_signing_alg_values_supported">, OidcClientTsOidcMetadata>>;
273
275
 
274
276
  export const WELL_KNOWN_PATH = "/.well-known/openid-configuration";
275
277
 
@@ -288,7 +290,7 @@ function readSessionStorage(params: { issuerUri: string }) {
288
290
  return undefined;
289
291
  }
290
292
 
291
- return JSON.parse(value) as Partial<OidcClientTsOidcMetadata>;
293
+ return JSON.parse(value) as Partial<OidcMetadata>;
292
294
  }
293
295
 
294
296
  function setSessionStorage(params: { issuerUri: string; oidcMetadata: Partial<OidcMetadata> }): void {