oidc-spa 8.6.19 → 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.
- package/backend.d.ts +3 -20
- package/backend.js +50 -242
- package/backend.js.map +1 -1
- package/core/OidcMetadata.d.ts +2 -2
- package/core/OidcMetadata.js.map +1 -1
- package/core/createOidc.d.ts +2 -4
- package/core/createOidc.js +41 -3
- package/core/createOidc.js.map +1 -1
- package/core/dpop.d.ts +20 -0
- package/core/dpop.js +389 -0
- package/core/dpop.js.map +1 -0
- package/core/earlyInit.js +2 -0
- package/core/earlyInit.js.map +1 -1
- package/core/oidcClientTsUserToTokens.d.ts +1 -0
- package/core/oidcClientTsUserToTokens.js +15 -5
- package/core/oidcClientTsUserToTokens.js.map +1 -1
- package/core/tokenExfiltrationDefense.js +49 -6
- package/core/tokenExfiltrationDefense.js.map +1 -1
- package/esm/angular.d.ts +2 -0
- package/esm/angular.mjs.map +1 -1
- package/esm/backend.d.ts +3 -20
- package/esm/backend.mjs +50 -242
- package/esm/backend.mjs.map +1 -1
- package/esm/core/OidcMetadata.d.ts +2 -2
- package/esm/core/OidcMetadata.mjs.map +1 -1
- package/esm/core/createOidc.d.ts +2 -4
- package/esm/core/createOidc.mjs +41 -3
- package/esm/core/createOidc.mjs.map +1 -1
- package/esm/core/dpop.d.ts +20 -0
- package/esm/core/dpop.mjs +384 -0
- package/esm/core/dpop.mjs.map +1 -0
- package/esm/core/earlyInit.mjs +2 -0
- package/esm/core/earlyInit.mjs.map +1 -1
- package/esm/core/oidcClientTsUserToTokens.d.ts +1 -0
- package/esm/core/oidcClientTsUserToTokens.mjs +15 -5
- package/esm/core/oidcClientTsUserToTokens.mjs.map +1 -1
- package/esm/core/tokenExfiltrationDefense.mjs +49 -6
- package/esm/core/tokenExfiltrationDefense.mjs.map +1 -1
- package/esm/react-spa/createOidcSpaApi.mjs +2 -1
- package/esm/react-spa/createOidcSpaApi.mjs.map +1 -1
- package/esm/react-spa/types.d.ts +2 -0
- package/esm/server/createOidcSpaUtils.d.ts +5 -0
- package/esm/server/createOidcSpaUtils.mjs +639 -0
- package/esm/server/createOidcSpaUtils.mjs.map +1 -0
- package/esm/server/index.d.ts +2 -0
- package/esm/server/index.mjs +3 -0
- package/esm/server/index.mjs.map +1 -0
- package/esm/server/types.d.ts +79 -0
- package/esm/server/types.mjs +2 -0
- package/esm/server/types.mjs.map +1 -0
- package/esm/server/utilsBuilder.d.ts +10 -0
- package/esm/server/utilsBuilder.mjs +13 -0
- package/esm/server/utilsBuilder.mjs.map +1 -0
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.d.ts +1 -1
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.mjs +102 -94
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.mjs.map +1 -1
- package/esm/tanstack-start/react/createOidcSpaApi.d.ts +2 -2
- package/esm/tanstack-start/react/createOidcSpaApi.mjs +60 -51
- package/esm/tanstack-start/react/createOidcSpaApi.mjs.map +1 -1
- package/esm/tanstack-start/react/index.d.ts +1 -1
- package/esm/tanstack-start/react/index.mjs +2 -2
- package/esm/tanstack-start/react/index.mjs.map +1 -1
- package/esm/tanstack-start/react/types.d.ts +36 -11
- package/esm/tanstack-start/react/{apiBuilder.d.ts → utilsBuilder.d.ts} +9 -9
- package/esm/tanstack-start/react/{apiBuilder.mjs → utilsBuilder.mjs} +6 -6
- package/esm/tanstack-start/react/utilsBuilder.mjs.map +1 -0
- package/esm/tools/generateES256DPoPProof.d.ts +8 -0
- package/esm/tools/generateES256DPoPProof.mjs +48 -0
- package/esm/tools/generateES256DPoPProof.mjs.map +1 -0
- package/esm/tools/getServerDateNow.d.ts +5 -0
- package/esm/tools/getServerDateNow.mjs +7 -0
- package/esm/tools/getServerDateNow.mjs.map +1 -0
- package/esm/vendor/{backend → server}/evt.mjs +84 -140
- package/esm/vendor/{backend → server}/jose.mjs +5 -27
- package/esm/vendor/{backend → server}/tsafe.d.ts +1 -0
- package/esm/vendor/{backend → server}/tsafe.mjs +6 -0
- package/esm/vendor/{backend → server}/zod.mjs +196 -50
- package/package.json +6 -1
- package/react-spa/createOidcSpaApi.js +2 -1
- package/react-spa/createOidcSpaApi.js.map +1 -1
- package/react-spa/types.d.ts +2 -0
- package/server/createOidcSpaUtils.d.ts +5 -0
- package/server/createOidcSpaUtils.js +642 -0
- package/server/createOidcSpaUtils.js.map +1 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +6 -0
- package/server/index.js.map +1 -0
- package/server/types.d.ts +79 -0
- package/server/types.js +3 -0
- package/server/types.js.map +1 -0
- package/server/utilsBuilder.d.ts +10 -0
- package/server/utilsBuilder.js +16 -0
- package/server/utilsBuilder.js.map +1 -0
- package/src/angular.ts +3 -0
- package/src/backend.ts +63 -364
- package/src/core/OidcMetadata.ts +4 -2
- package/src/core/createOidc.ts +54 -6
- package/src/core/dpop.ts +583 -0
- package/src/core/earlyInit.ts +3 -0
- package/src/core/oidcClientTsUserToTokens.ts +18 -4
- package/src/core/tokenExfiltrationDefense.ts +60 -5
- package/src/react-spa/createOidcSpaApi.ts +2 -1
- package/src/react-spa/types.tsx +3 -0
- package/src/server/createOidcSpaUtils.ts +848 -0
- package/src/server/index.ts +4 -0
- package/src/server/types.tsx +99 -0
- package/src/server/utilsBuilder.ts +41 -0
- package/src/tanstack-start/react/accessTokenValidation_rfc9068.ts +134 -124
- package/src/tanstack-start/react/createOidcSpaApi.ts +73 -69
- package/src/tanstack-start/react/index.ts +2 -2
- package/src/tanstack-start/react/types.tsx +44 -12
- package/src/tanstack-start/react/{apiBuilder.ts → utilsBuilder.ts} +14 -14
- package/src/tools/generateES256DPoPProof.ts +74 -0
- package/src/tools/getServerDateNow.ts +11 -0
- package/src/vendor/{backend → server}/tsafe.ts +1 -0
- package/tools/generateES256DPoPProof.d.ts +8 -0
- package/tools/generateES256DPoPProof.js +51 -0
- package/tools/generateES256DPoPProof.js.map +1 -0
- package/tools/getServerDateNow.d.ts +5 -0
- package/tools/getServerDateNow.js +10 -0
- package/tools/getServerDateNow.js.map +1 -0
- package/vendor/server/evt.js +3 -0
- package/vendor/server/jose.js +3 -0
- package/vendor/{backend → server}/tsafe.d.ts +1 -0
- package/vendor/server/tsafe.js +2 -0
- package/vendor/server/zod.js +3 -0
- package/esm/tanstack-start/react/apiBuilder.mjs.map +0 -1
- package/vendor/backend/evt.js +0 -3
- package/vendor/backend/jose.js +0 -3
- package/vendor/backend/tsafe.js +0 -2
- package/vendor/backend/zod.js +0 -3
- /package/esm/vendor/{backend → server}/evt.d.ts +0 -0
- /package/esm/vendor/{backend → server}/jose.d.ts +0 -0
- /package/esm/vendor/{backend → server}/zod.d.ts +0 -0
- /package/src/vendor/{backend → server}/evt.ts +0 -0
- /package/src/vendor/{backend → server}/jose.ts +0 -0
- /package/src/vendor/{backend → server}/zod.ts +0 -0
- /package/vendor/{backend → server}/evt.d.ts +0 -0
- /package/vendor/{backend → server}/jose.d.ts +0 -0
- /package/vendor/{backend → server}/zod.d.ts +0 -0
package/server/index.js
ADDED
|
@@ -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
|
+
};
|
package/server/types.js
ADDED
|
@@ -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
package/src/backend.ts
CHANGED
|
@@ -1,66 +1,9 @@
|
|
|
1
|
-
import { assert,
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
}
|
package/src/core/OidcMetadata.ts
CHANGED
|
@@ -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<
|
|
293
|
+
return JSON.parse(value) as Partial<OidcMetadata>;
|
|
292
294
|
}
|
|
293
295
|
|
|
294
296
|
function setSessionStorage(params: { issuerUri: string; oidcMetadata: Partial<OidcMetadata> }): void {
|