oidc-spa 8.1.10 → 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.
- package/backend.d.ts +27 -6
- package/backend.js +124 -139
- package/backend.js.map +1 -1
- package/core/Oidc.d.ts +28 -4
- package/core/createOidc.d.ts +12 -3
- package/core/createOidc.js +1 -1
- package/core/createOidc.js.map +1 -1
- package/core/earlyInit.d.ts +1 -0
- package/core/earlyInit.js +11 -4
- package/core/earlyInit.js.map +1 -1
- package/core/loginOrGoToAuthServer.js +8 -3
- package/core/loginOrGoToAuthServer.js.map +1 -1
- package/core/oidcClientTsUserToTokens.d.ts +1 -1
- package/core/oidcClientTsUserToTokens.js.map +1 -1
- package/core/requiredPostHydrationReplaceNavigationUrl.d.ts +6 -0
- package/core/requiredPostHydrationReplaceNavigationUrl.js +12 -0
- package/core/requiredPostHydrationReplaceNavigationUrl.js.map +1 -0
- package/entrypoint.d.ts +1 -0
- package/entrypoint.js +3 -1
- package/entrypoint.js.map +1 -1
- package/esm/angular.d.ts +14 -4
- package/esm/angular.js +155 -10
- package/esm/angular.js.map +1 -1
- package/esm/backend.d.ts +48 -0
- package/esm/backend.js +259 -0
- package/esm/backend.js.map +1 -0
- package/esm/core/Oidc.d.ts +28 -4
- package/esm/core/createOidc.d.ts +12 -3
- package/esm/core/createOidc.js +1 -1
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/earlyInit.d.ts +1 -0
- package/esm/core/earlyInit.js +11 -4
- package/esm/core/earlyInit.js.map +1 -1
- package/esm/core/loginOrGoToAuthServer.js +8 -3
- package/esm/core/loginOrGoToAuthServer.js.map +1 -1
- package/esm/core/oidcClientTsUserToTokens.d.ts +1 -1
- package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.d.ts +6 -0
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.js +8 -0
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.js.map +1 -0
- package/esm/entrypoint.d.ts +1 -0
- package/esm/entrypoint.js +1 -0
- package/esm/entrypoint.js.map +1 -1
- package/esm/mock/oidc.d.ts +1 -1
- package/esm/mock/oidc.js.map +1 -1
- package/esm/react/react.d.ts +1 -1
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.d.ts +12 -0
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js +95 -0
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js.map +1 -0
- package/esm/tanstack-start/react/apiBuilder.d.ts +27 -0
- package/esm/tanstack-start/react/apiBuilder.js +58 -0
- package/esm/tanstack-start/react/apiBuilder.js.map +1 -0
- package/esm/tanstack-start/react/createOidcSpaApi.d.ts +9 -0
- package/esm/tanstack-start/react/createOidcSpaApi.js +678 -0
- package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -0
- package/esm/tanstack-start/react/index.d.ts +3 -0
- package/esm/tanstack-start/react/index.js +4 -0
- package/esm/tanstack-start/react/index.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.d.ts +4 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.js +8 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.d.ts +4 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.js +76 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.d.ts +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.js +11 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.d.ts +2 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.js +3 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.js.map +1 -0
- package/esm/tanstack-start/react/types.d.ts +355 -0
- package/esm/tanstack-start/react/types.js +2 -0
- package/esm/tanstack-start/react/types.js.map +1 -0
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.d.ts +2 -0
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js +25 -0
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js.map +1 -0
- package/esm/tools/GetterOrDirectValue.d.ts +1 -0
- package/esm/tools/GetterOrDirectValue.js +2 -0
- package/esm/tools/GetterOrDirectValue.js.map +1 -0
- package/esm/tools/ZodSchemaLike.d.ts +3 -0
- package/esm/tools/ZodSchemaLike.js +2 -0
- package/esm/tools/ZodSchemaLike.js.map +1 -0
- package/esm/tools/inferIsViteDev.d.ts +1 -0
- package/esm/tools/inferIsViteDev.js +6 -0
- package/esm/tools/inferIsViteDev.js.map +1 -0
- package/esm/tools/infer_import_meta_env_BASE_URL.d.ts +1 -0
- package/esm/tools/infer_import_meta_env_BASE_URL.js +15 -0
- package/esm/tools/infer_import_meta_env_BASE_URL.js.map +1 -0
- package/esm/tools/tsafe/uncapitalize.d.ts +2 -0
- package/esm/tools/tsafe/uncapitalize.js +5 -0
- package/esm/tools/tsafe/uncapitalize.js.map +1 -0
- package/esm/vendor/backend/evt.d.ts +2 -0
- package/esm/vendor/backend/evt.js +3286 -0
- package/esm/vendor/backend/jose.d.ts +1 -0
- package/esm/vendor/backend/jose.js +3546 -0
- package/esm/vendor/backend/tsafe.d.ts +5 -0
- package/esm/vendor/backend/tsafe.js +68 -0
- package/esm/vendor/backend/zod.d.ts +1 -0
- package/esm/vendor/backend/zod.js +4023 -0
- package/esm/vendor/frontend/worker-timers.js +261 -1
- package/mock/oidc.d.ts +1 -1
- package/mock/oidc.js.map +1 -1
- package/package.json +40 -4
- package/react/react.d.ts +1 -1
- package/src/angular.ts +224 -9
- package/src/backend.ts +201 -166
- package/src/core/Oidc.ts +41 -11
- package/src/core/createOidc.ts +12 -3
- package/src/core/earlyInit.ts +19 -4
- package/src/core/loginOrGoToAuthServer.ts +11 -3
- package/src/core/oidcClientTsUserToTokens.ts +2 -2
- package/src/core/requiredPostHydrationReplaceNavigationUrl.ts +11 -0
- package/src/entrypoint.ts +1 -0
- package/src/mock/oidc.ts +2 -2
- package/src/react/react.tsx +1 -1
- package/src/tanstack-start/react/accessTokenValidation_rfc9068.ts +135 -0
- package/src/tanstack-start/react/apiBuilder.ts +151 -0
- package/src/tanstack-start/react/createOidcSpaApi.tsx +1009 -0
- package/src/tanstack-start/react/index.ts +5 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.ts +8 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.tsx +110 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.ts +13 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.ts +2 -0
- package/src/tanstack-start/react/types.tsx +415 -0
- package/src/tanstack-start/react/withHandlingOidcPostLoginNavigation.tsx +35 -0
- package/src/tools/GetterOrDirectValue.ts +1 -0
- package/src/tools/ZodSchemaLike.ts +3 -0
- package/src/tools/getThisCodebaseRootDirPath_cjs.ts +19 -0
- package/src/tools/inferIsViteDev.ts +6 -0
- package/src/tools/infer_import_meta_env_BASE_URL.ts +19 -0
- package/src/tools/tsafe/uncapitalize.ts +4 -0
- package/src/vendor/backend/jose.ts +1 -0
- package/src/vendor/build-runtime/babel.ts +6 -0
- package/src/vendor/build-runtime/magic-string.ts +3 -0
- package/src/vite-plugin/detectProjectType.ts +20 -0
- package/src/vite-plugin/excludeModuleExportFromOptimizedDeps.ts +20 -0
- package/src/vite-plugin/handleClientEntrypoint.ts +260 -0
- package/src/vite-plugin/index.ts +1 -0
- package/src/vite-plugin/transformCreateFileRoute.ts +240 -0
- package/src/vite-plugin/vite-plugin.ts +54 -0
- package/tools/GetterOrDirectValue.d.ts +1 -0
- package/tools/GetterOrDirectValue.js +3 -0
- package/tools/GetterOrDirectValue.js.map +1 -0
- package/tools/ZodSchemaLike.d.ts +3 -0
- package/tools/ZodSchemaLike.js +3 -0
- package/tools/ZodSchemaLike.js.map +1 -0
- package/tools/getThisCodebaseRootDirPath_cjs.d.ts +2 -0
- package/tools/getThisCodebaseRootDirPath_cjs.js +53 -0
- package/tools/getThisCodebaseRootDirPath_cjs.js.map +1 -0
- package/tools/tsafe/uncapitalize.d.ts +2 -0
- package/tools/tsafe/uncapitalize.js +8 -0
- package/tools/tsafe/uncapitalize.js.map +1 -0
- package/vendor/backend/jose.d.ts +1 -0
- package/vendor/backend/jose.js +3 -0
- package/vendor/build-runtime/babel.d.ts +6 -0
- package/vendor/build-runtime/babel.js +3 -0
- package/vendor/build-runtime/magic-string.d.ts +2 -0
- package/vendor/build-runtime/magic-string.js +2 -0
- package/vendor/frontend/oidc-client-ts.js +0 -2
- package/vite-plugin/detectProjectType.d.ts +10 -0
- package/vite-plugin/detectProjectType.js +15 -0
- package/vite-plugin/detectProjectType.js.map +1 -0
- package/vite-plugin/excludeModuleExportFromOptimizedDeps.d.ts +4 -0
- package/vite-plugin/excludeModuleExportFromOptimizedDeps.js +50 -0
- package/vite-plugin/excludeModuleExportFromOptimizedDeps.js.map +1 -0
- package/vite-plugin/handleClientEntrypoint.d.ts +10 -0
- package/vite-plugin/handleClientEntrypoint.js +211 -0
- package/vite-plugin/handleClientEntrypoint.js.map +1 -0
- package/vite-plugin/index.d.ts +1 -0
- package/vite-plugin/index.js +6 -0
- package/vite-plugin/index.js.map +1 -0
- package/vite-plugin/transformCreateFileRoute.d.ts +10 -0
- package/vite-plugin/transformCreateFileRoute.js +173 -0
- package/vite-plugin/transformCreateFileRoute.js.map +1 -0
- package/vite-plugin/vite-plugin.d.ts +5 -0
- package/vite-plugin/vite-plugin.js +46 -0
- package/vite-plugin/vite-plugin.js.map +1 -0
- package/src/vendor/backend/jsonwebtoken.ts +0 -1
- package/src/vendor/backend/node-fetch.ts +0 -2
- package/src/vendor/backend/node-jose.ts +0 -1
- package/vendor/backend/jsonwebtoken.d.ts +0 -1
- package/vendor/backend/jsonwebtoken.js +0 -3
- package/vendor/backend/node-fetch.d.ts +0 -2
- package/vendor/backend/node-fetch.js +0 -2
- package/vendor/backend/node-jose.d.ts +0 -1
- package/vendor/backend/node-jose.js +0 -3
package/src/backend.ts
CHANGED
|
@@ -1,20 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
|
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
|
-
|
|
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?:
|
|
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<
|
|
43
|
-
|
|
44
|
-
): Promise<OidcBackend<DecodedAccessToken>> {
|
|
45
|
-
const { issuerUri, decodedAccessTokenSchema
|
|
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<
|
|
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:
|
|
153
|
+
let alg: string;
|
|
95
154
|
|
|
96
155
|
{
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
let jwtHeader: string;
|
|
156
|
+
let header: ReturnType<typeof decodeProtectedHeader>;
|
|
100
157
|
|
|
101
158
|
try {
|
|
102
|
-
|
|
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
|
|
164
|
+
errorMessage: "Failed to decode the JWT header"
|
|
108
165
|
};
|
|
109
166
|
}
|
|
110
167
|
|
|
111
|
-
|
|
168
|
+
const { kid: kidFromHeader, alg: algFromHeader } = header;
|
|
112
169
|
|
|
113
|
-
|
|
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: "
|
|
174
|
+
errorMessage: "The decoded JWT header does not have a kid property"
|
|
120
175
|
};
|
|
121
176
|
}
|
|
122
177
|
|
|
123
|
-
|
|
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
|
|
182
|
+
errorMessage: "The decoded JWT header does not specify an algorithm"
|
|
142
183
|
};
|
|
143
184
|
}
|
|
144
185
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 =
|
|
177
|
-
alg =
|
|
206
|
+
kid = kidFromHeader;
|
|
207
|
+
alg = algFromHeader;
|
|
178
208
|
}
|
|
179
209
|
|
|
180
|
-
|
|
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
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
accessToken,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
|
|
288
|
+
type PublicSigningKeys = {
|
|
289
|
+
keyResolver: ReturnType<typeof createLocalJWKSet>;
|
|
290
|
+
kidSet: Set<string>;
|
|
257
291
|
};
|
|
258
292
|
|
|
259
|
-
async function fetchPublicSigningKeys(params: { issuerUri: string }): Promise<
|
|
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
|
-
|
|
329
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
369
|
-
|
|
398
|
+
if (key.use !== undefined && key.use !== "sig") {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
370
401
|
|
|
371
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
412
|
+
signatureKeys.length !== 0,
|
|
387
413
|
`No public signing key found at ${jwks_uri}, ${JSON.stringify(jwks, null, 2)}`
|
|
388
414
|
);
|
|
389
415
|
|
|
390
|
-
|
|
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.
|
|
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<
|
|
95
|
-
|
|
96
|
-
|
|
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:
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
}
|
package/src/core/createOidc.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
221
|
+
DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_OidcCoreSpec,
|
|
213
222
|
AutoLogin extends boolean = false
|
|
214
223
|
>(
|
|
215
224
|
params: ParamsOfCreateOidc<DecodedIdToken, AutoLogin>
|
package/src/core/earlyInit.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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
|
-
|
|
223
|
+
if (isPostLoginRedirectManual) {
|
|
224
|
+
setOidcRequiredPostHydrationReplaceNavigationUrl({ rootRelativeRedirectUrl });
|
|
225
|
+
} else {
|
|
226
|
+
history.replaceState({}, "", rootRelativeRedirectUrl);
|
|
227
|
+
}
|
|
213
228
|
|
|
214
229
|
return { shouldLoadApp: true };
|
|
215
230
|
}
|