@better-auth/core 1.7.0-beta.1 → 1.7.0-beta.10
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/dist/api/index.d.mts +44 -1
- package/dist/api/index.mjs +69 -4
- package/dist/context/global.mjs +1 -1
- package/dist/context/transaction.d.mts +7 -4
- package/dist/context/transaction.mjs +6 -3
- package/dist/db/adapter/factory.mjs +91 -3
- package/dist/db/adapter/get-id-field.mjs +1 -1
- package/dist/db/adapter/index.d.mts +87 -3
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/get-tables.mjs +2 -1
- package/dist/db/type.d.mts +17 -0
- package/dist/env/env-impl.mjs +1 -1
- package/dist/error/codes.d.mts +1 -0
- package/dist/error/codes.mjs +1 -0
- package/dist/error/index.d.mts +7 -0
- package/dist/index.d.mts +2 -2
- package/dist/instrumentation/api.mjs +12 -0
- package/dist/instrumentation/noop.mjs +42 -0
- package/dist/instrumentation/pure.index.d.mts +7 -0
- package/dist/instrumentation/pure.index.mjs +7 -0
- package/dist/instrumentation/tracer.mjs +6 -3
- package/dist/oauth2/authorization-params.d.mts +12 -0
- package/dist/oauth2/authorization-params.mjs +12 -0
- package/dist/oauth2/basic-credentials.d.mts +30 -0
- package/dist/oauth2/basic-credentials.mjs +64 -0
- package/dist/oauth2/client-assertion.d.mts +38 -22
- package/dist/oauth2/client-assertion.mjs +63 -28
- package/dist/oauth2/client-credentials-token.d.mts +19 -40
- package/dist/oauth2/client-credentials-token.mjs +20 -31
- package/dist/oauth2/create-authorization-url.d.mts +11 -1
- package/dist/oauth2/create-authorization-url.mjs +27 -7
- package/dist/oauth2/dpop.d.mts +142 -0
- package/dist/oauth2/dpop.mjs +246 -0
- package/dist/oauth2/index.d.mts +14 -9
- package/dist/oauth2/index.mjs +12 -8
- package/dist/oauth2/oauth-provider.d.mts +144 -10
- package/dist/oauth2/refresh-access-token.d.mts +20 -40
- package/dist/oauth2/refresh-access-token.mjs +38 -36
- package/dist/oauth2/reject-redirects.mjs +65 -0
- package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
- package/dist/oauth2/token-endpoint-auth.mjs +89 -0
- package/dist/oauth2/utils.d.mts +23 -1
- package/dist/oauth2/utils.mjs +48 -2
- package/dist/oauth2/validate-authorization-code.d.mts +17 -52
- package/dist/oauth2/validate-authorization-code.mjs +28 -34
- package/dist/oauth2/verify-id-token.d.mts +27 -0
- package/dist/oauth2/verify-id-token.mjs +62 -0
- package/dist/oauth2/verify.d.mts +88 -15
- package/dist/oauth2/verify.mjs +203 -25
- package/dist/social-providers/apple.d.mts +16 -4
- package/dist/social-providers/apple.mjs +17 -24
- package/dist/social-providers/atlassian.d.mts +5 -1
- package/dist/social-providers/atlassian.mjs +6 -3
- package/dist/social-providers/cognito.d.mts +27 -3
- package/dist/social-providers/cognito.mjs +15 -25
- package/dist/social-providers/discord.d.mts +7 -3
- package/dist/social-providers/discord.mjs +16 -3
- package/dist/social-providers/dropbox.d.mts +5 -1
- package/dist/social-providers/dropbox.mjs +5 -4
- package/dist/social-providers/facebook.d.mts +24 -5
- package/dist/social-providers/facebook.mjs +54 -20
- package/dist/social-providers/figma.d.mts +5 -1
- package/dist/social-providers/figma.mjs +4 -3
- package/dist/social-providers/github.d.mts +6 -2
- package/dist/social-providers/github.mjs +5 -4
- package/dist/social-providers/gitlab.d.mts +5 -1
- package/dist/social-providers/gitlab.mjs +3 -2
- package/dist/social-providers/google.d.mts +53 -5
- package/dist/social-providers/google.mjs +69 -25
- package/dist/social-providers/huggingface.d.mts +5 -1
- package/dist/social-providers/huggingface.mjs +3 -2
- package/dist/social-providers/index.d.mts +221 -45
- package/dist/social-providers/index.mjs +2 -2
- package/dist/social-providers/kakao.d.mts +5 -1
- package/dist/social-providers/kakao.mjs +3 -2
- package/dist/social-providers/kick.d.mts +5 -1
- package/dist/social-providers/kick.mjs +3 -2
- package/dist/social-providers/line.d.mts +8 -2
- package/dist/social-providers/line.mjs +5 -6
- package/dist/social-providers/linear.d.mts +5 -1
- package/dist/social-providers/linear.mjs +3 -2
- package/dist/social-providers/linkedin.d.mts +7 -3
- package/dist/social-providers/linkedin.mjs +4 -3
- package/dist/social-providers/microsoft-entra-id.d.mts +41 -7
- package/dist/social-providers/microsoft-entra-id.mjs +37 -28
- package/dist/social-providers/naver.d.mts +5 -1
- package/dist/social-providers/naver.mjs +3 -2
- package/dist/social-providers/notion.d.mts +5 -1
- package/dist/social-providers/notion.mjs +5 -2
- package/dist/social-providers/paybin.d.mts +5 -1
- package/dist/social-providers/paybin.mjs +4 -3
- package/dist/social-providers/paypal.d.mts +6 -2
- package/dist/social-providers/paypal.mjs +19 -14
- package/dist/social-providers/polar.d.mts +5 -1
- package/dist/social-providers/polar.mjs +3 -2
- package/dist/social-providers/railway.d.mts +5 -1
- package/dist/social-providers/railway.mjs +3 -2
- package/dist/social-providers/reddit.d.mts +5 -1
- package/dist/social-providers/reddit.mjs +7 -5
- package/dist/social-providers/roblox.d.mts +6 -2
- package/dist/social-providers/roblox.mjs +12 -2
- package/dist/social-providers/salesforce.d.mts +5 -1
- package/dist/social-providers/salesforce.mjs +4 -3
- package/dist/social-providers/slack.d.mts +6 -2
- package/dist/social-providers/slack.mjs +11 -8
- package/dist/social-providers/spotify.d.mts +5 -1
- package/dist/social-providers/spotify.mjs +3 -2
- package/dist/social-providers/tiktok.d.mts +5 -1
- package/dist/social-providers/tiktok.mjs +14 -2
- package/dist/social-providers/twitch.d.mts +5 -1
- package/dist/social-providers/twitch.mjs +3 -2
- package/dist/social-providers/twitter.d.mts +4 -1
- package/dist/social-providers/twitter.mjs +2 -1
- package/dist/social-providers/vercel.d.mts +5 -1
- package/dist/social-providers/vercel.mjs +3 -2
- package/dist/social-providers/vk.d.mts +5 -1
- package/dist/social-providers/vk.mjs +3 -2
- package/dist/social-providers/wechat.d.mts +5 -1
- package/dist/social-providers/wechat.mjs +8 -2
- package/dist/social-providers/zoom.d.mts +6 -2
- package/dist/social-providers/zoom.mjs +10 -17
- package/dist/types/context.d.mts +55 -6
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +182 -12
- package/dist/types/plugin-client.d.mts +12 -2
- package/dist/utils/async.d.mts +22 -0
- package/dist/utils/async.mjs +32 -0
- package/dist/utils/host.d.mts +147 -0
- package/dist/utils/host.mjs +298 -0
- package/dist/utils/ip.d.mts +28 -5
- package/dist/utils/ip.mjs +118 -4
- package/dist/utils/is-api-error.d.mts +6 -0
- package/dist/utils/is-api-error.mjs +8 -0
- package/dist/utils/redirect-uri.d.mts +20 -0
- package/dist/utils/redirect-uri.mjs +48 -0
- package/dist/utils/string.d.mts +5 -1
- package/dist/utils/string.mjs +20 -1
- package/dist/utils/url.d.mts +18 -1
- package/dist/utils/url.mjs +34 -4
- package/package.json +26 -16
- package/src/api/index.ts +121 -5
- package/src/context/transaction.ts +45 -12
- package/src/db/adapter/factory.ts +193 -7
- package/src/db/adapter/get-id-field.ts +2 -2
- package/src/db/adapter/index.ts +85 -2
- package/src/db/adapter/types.ts +2 -0
- package/src/db/get-tables.ts +9 -1
- package/src/db/schema/account.ts +4 -1
- package/src/db/schema/user.ts +3 -0
- package/src/db/type.ts +17 -0
- package/src/env/env-impl.ts +1 -2
- package/src/error/codes.ts +1 -0
- package/src/error/index.ts +9 -0
- package/src/instrumentation/api.ts +17 -0
- package/src/instrumentation/noop.ts +74 -0
- package/src/instrumentation/pure.index.ts +31 -0
- package/src/instrumentation/tracer.ts +8 -3
- package/src/oauth2/authorization-params.ts +28 -0
- package/src/oauth2/basic-credentials.ts +87 -0
- package/src/oauth2/client-assertion.ts +131 -58
- package/src/oauth2/client-credentials-token.ts +50 -74
- package/src/oauth2/create-authorization-url.ts +33 -7
- package/src/oauth2/dpop.ts +568 -0
- package/src/oauth2/index.ts +81 -11
- package/src/oauth2/oauth-provider.ts +153 -11
- package/src/oauth2/refresh-access-token.ts +82 -83
- package/src/oauth2/reject-redirects.ts +75 -0
- package/src/oauth2/token-endpoint-auth.ts +221 -0
- package/src/oauth2/utils.ts +72 -5
- package/src/oauth2/validate-authorization-code.ts +69 -89
- package/src/oauth2/verify-id-token.ts +115 -0
- package/src/oauth2/verify.ts +409 -78
- package/src/social-providers/apple.ts +30 -40
- package/src/social-providers/atlassian.ts +8 -1
- package/src/social-providers/cognito.ts +36 -39
- package/src/social-providers/discord.ts +22 -18
- package/src/social-providers/dropbox.ts +7 -5
- package/src/social-providers/facebook.ts +108 -52
- package/src/social-providers/figma.ts +8 -1
- package/src/social-providers/github.ts +5 -3
- package/src/social-providers/gitlab.ts +2 -0
- package/src/social-providers/google.ts +141 -39
- package/src/social-providers/huggingface.ts +8 -1
- package/src/social-providers/kakao.ts +2 -1
- package/src/social-providers/kick.ts +8 -1
- package/src/social-providers/line.ts +25 -27
- package/src/social-providers/linear.ts +8 -1
- package/src/social-providers/linkedin.ts +5 -3
- package/src/social-providers/microsoft-entra-id.ts +100 -55
- package/src/social-providers/naver.ts +2 -1
- package/src/social-providers/notion.ts +8 -1
- package/src/social-providers/paybin.ts +2 -0
- package/src/social-providers/paypal.ts +28 -17
- package/src/social-providers/polar.ts +8 -1
- package/src/social-providers/railway.ts +8 -1
- package/src/social-providers/reddit.ts +9 -4
- package/src/social-providers/roblox.ts +16 -11
- package/src/social-providers/salesforce.ts +8 -1
- package/src/social-providers/slack.ts +15 -9
- package/src/social-providers/spotify.ts +8 -1
- package/src/social-providers/tiktok.ts +22 -9
- package/src/social-providers/twitch.ts +2 -1
- package/src/social-providers/twitter.ts +1 -0
- package/src/social-providers/vercel.ts +8 -1
- package/src/social-providers/vk.ts +8 -1
- package/src/social-providers/wechat.ts +17 -2
- package/src/social-providers/zoom.ts +15 -19
- package/src/types/context.ts +57 -5
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +204 -14
- package/src/types/plugin-client.ts +16 -2
- package/src/utils/async.ts +53 -0
- package/src/utils/host.ts +425 -0
- package/src/utils/ip.ts +197 -13
- package/src/utils/is-api-error.ts +10 -0
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +37 -0
- package/src/utils/url.ts +38 -4
|
@@ -1,39 +1,32 @@
|
|
|
1
|
-
import { resolveAssertionParams } from "./client-assertion.mjs";
|
|
2
1
|
import { getOAuth2Tokens } from "./utils.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { assertResponseNotRedirect, fetchRefusingRedirects, noFollowRedirect } from "./reject-redirects.mjs";
|
|
3
|
+
import { applyTokenEndpointAuth } from "./token-endpoint-auth.mjs";
|
|
4
|
+
import { createRemoteJWKSet, customFetch, jwtVerify } from "jose";
|
|
6
5
|
//#region src/oauth2/validate-authorization-code.ts
|
|
7
|
-
async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication,
|
|
6
|
+
async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, tokenEndpointAuth, tokenEndpoint, deviceId, headers, additionalParams = {}, resource }) {
|
|
8
7
|
options = typeof options === "function" ? await options() : options;
|
|
9
|
-
|
|
10
|
-
if (!clientAssertion) throw new Error("private_key_jwt authentication requires a clientAssertion configuration");
|
|
11
|
-
const assertionParams = await resolveAssertionParams({
|
|
12
|
-
clientAssertion,
|
|
13
|
-
clientId: Array.isArray(options.clientId) ? options.clientId[0] : options.clientId,
|
|
14
|
-
tokenEndpoint
|
|
15
|
-
});
|
|
16
|
-
additionalParams = {
|
|
17
|
-
...additionalParams,
|
|
18
|
-
...assertionParams
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
return createAuthorizationCodeRequest({
|
|
8
|
+
const request = buildAuthorizationCodeRequest({
|
|
22
9
|
code,
|
|
23
10
|
codeVerifier,
|
|
24
11
|
redirectURI,
|
|
25
12
|
options,
|
|
26
|
-
authentication,
|
|
27
13
|
deviceId,
|
|
28
14
|
headers,
|
|
29
15
|
additionalParams,
|
|
30
16
|
resource
|
|
31
17
|
});
|
|
18
|
+
await applyTokenEndpointAuth({
|
|
19
|
+
body: request.body,
|
|
20
|
+
headers: request.headers,
|
|
21
|
+
options,
|
|
22
|
+
tokenEndpoint: tokenEndpoint ?? "",
|
|
23
|
+
grantType: "authorization_code",
|
|
24
|
+
tokenEndpointAuth,
|
|
25
|
+
authentication
|
|
26
|
+
});
|
|
27
|
+
return request;
|
|
32
28
|
}
|
|
33
|
-
|
|
34
|
-
* @deprecated use async'd authorizationCodeRequest instead
|
|
35
|
-
*/
|
|
36
|
-
function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, deviceId, headers, additionalParams = {}, resource }) {
|
|
29
|
+
function buildAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, deviceId, headers, additionalParams = {}, resource }) {
|
|
37
30
|
const body = new URLSearchParams();
|
|
38
31
|
const requestHeaders = {
|
|
39
32
|
"content-type": "application/x-www-form-urlencoded",
|
|
@@ -48,33 +41,27 @@ function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, optio
|
|
|
48
41
|
body.set("redirect_uri", options.redirectURI || redirectURI);
|
|
49
42
|
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
50
43
|
else for (const _resource of resource) body.append("resource", _resource);
|
|
51
|
-
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
52
|
-
if (authentication === "basic") requestHeaders["authorization"] = `Basic ${base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`)}`;
|
|
53
|
-
else {
|
|
54
|
-
body.set("client_id", primaryClientId);
|
|
55
|
-
if (authentication !== "private_key_jwt" && options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
56
|
-
}
|
|
57
44
|
for (const [key, value] of Object.entries(additionalParams)) if (!body.has(key)) body.append(key, value);
|
|
58
45
|
return {
|
|
59
46
|
body,
|
|
60
47
|
headers: requestHeaders
|
|
61
48
|
};
|
|
62
49
|
}
|
|
63
|
-
async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication,
|
|
50
|
+
async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, tokenEndpointAuth, deviceId, headers, additionalParams = {}, resource }) {
|
|
64
51
|
const { body, headers: requestHeaders } = await authorizationCodeRequest({
|
|
65
52
|
code,
|
|
66
53
|
codeVerifier,
|
|
67
54
|
redirectURI,
|
|
68
55
|
options,
|
|
69
56
|
authentication,
|
|
70
|
-
|
|
57
|
+
tokenEndpointAuth,
|
|
71
58
|
tokenEndpoint,
|
|
72
59
|
deviceId,
|
|
73
60
|
headers,
|
|
74
61
|
additionalParams,
|
|
75
62
|
resource
|
|
76
63
|
});
|
|
77
|
-
const { data, error } = await
|
|
64
|
+
const { data, error } = await fetchRefusingRedirects(tokenEndpoint, {
|
|
78
65
|
method: "POST",
|
|
79
66
|
body,
|
|
80
67
|
headers: requestHeaders
|
|
@@ -83,10 +70,17 @@ async function validateAuthorizationCode({ code, codeVerifier, redirectURI, opti
|
|
|
83
70
|
return getOAuth2Tokens(data);
|
|
84
71
|
}
|
|
85
72
|
async function validateToken(token, jwksEndpoint, options) {
|
|
86
|
-
return await jwtVerify(token, createRemoteJWKSet(new URL(jwksEndpoint)
|
|
73
|
+
return await jwtVerify(token, createRemoteJWKSet(new URL(jwksEndpoint), { [customFetch]: async (url, init) => {
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
...init,
|
|
76
|
+
...noFollowRedirect
|
|
77
|
+
});
|
|
78
|
+
assertResponseNotRedirect(String(url), response);
|
|
79
|
+
return response;
|
|
80
|
+
} }), {
|
|
87
81
|
audience: options?.audience,
|
|
88
82
|
issuer: options?.issuer
|
|
89
83
|
});
|
|
90
84
|
}
|
|
91
85
|
//#endregion
|
|
92
|
-
export { authorizationCodeRequest,
|
|
86
|
+
export { authorizationCodeRequest, validateAuthorizationCode, validateToken };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { OAuthProvider } from "./oauth-provider.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/oauth2/verify-id-token.d.ts
|
|
4
|
+
type ProviderWithIdTokenConfig = Pick<OAuthProvider, "idToken" | "options">;
|
|
5
|
+
/**
|
|
6
|
+
* Whether a provider can verify a client-submitted id_token.
|
|
7
|
+
*
|
|
8
|
+
* A provider supports id_token sign-in when it declares an {@link OAuthProvider.idToken}
|
|
9
|
+
* verification config, or when the integrator supplies a `verifyIdToken` override on the
|
|
10
|
+
* provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
|
|
11
|
+
* neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
|
|
12
|
+
*/
|
|
13
|
+
declare function supportsIdTokenSignIn(provider: ProviderWithIdTokenConfig): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Verify a client-submitted id_token against a provider's verification config.
|
|
16
|
+
*
|
|
17
|
+
* This is the single id_token verifier for every social provider. Providers no longer
|
|
18
|
+
* implement their own boolean `verifyIdToken`; they declare an {@link OAuthProvider.idToken}
|
|
19
|
+
* config and this function performs the cryptographic check. The contract is fail-closed: a
|
|
20
|
+
* provider without a config (and without an integrator `verifyIdToken` override) returns
|
|
21
|
+
* `false`, so a forged token can never be accepted by omission.
|
|
22
|
+
*
|
|
23
|
+
* @returns `true` only when the token is authentic for the provider.
|
|
24
|
+
*/
|
|
25
|
+
declare function verifyProviderIdToken(provider: ProviderWithIdTokenConfig, token: string, nonce?: string): Promise<boolean>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { supportsIdTokenSignIn, verifyProviderIdToken };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { decodeProtectedHeader, jwtVerify } from "jose";
|
|
2
|
+
//#region src/oauth2/verify-id-token.ts
|
|
3
|
+
async function sha256Hex(value) {
|
|
4
|
+
const data = new TextEncoder().encode(value);
|
|
5
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
6
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
7
|
+
}
|
|
8
|
+
async function nonceMatches(claimNonce, nonce, comparison = "exact") {
|
|
9
|
+
if (typeof claimNonce !== "string") return false;
|
|
10
|
+
if (claimNonce === nonce) return true;
|
|
11
|
+
if (comparison === "exact-or-sha256") return claimNonce === await sha256Hex(nonce);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether a provider can verify a client-submitted id_token.
|
|
16
|
+
*
|
|
17
|
+
* A provider supports id_token sign-in when it declares an {@link OAuthProvider.idToken}
|
|
18
|
+
* verification config, or when the integrator supplies a `verifyIdToken` override on the
|
|
19
|
+
* provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
|
|
20
|
+
* neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
|
|
21
|
+
*/
|
|
22
|
+
function supportsIdTokenSignIn(provider) {
|
|
23
|
+
const options = provider.options ?? {};
|
|
24
|
+
if (options.disableIdTokenSignIn) return false;
|
|
25
|
+
return Boolean(provider.idToken || options.verifyIdToken);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Verify a client-submitted id_token against a provider's verification config.
|
|
29
|
+
*
|
|
30
|
+
* This is the single id_token verifier for every social provider. Providers no longer
|
|
31
|
+
* implement their own boolean `verifyIdToken`; they declare an {@link OAuthProvider.idToken}
|
|
32
|
+
* config and this function performs the cryptographic check. The contract is fail-closed: a
|
|
33
|
+
* provider without a config (and without an integrator `verifyIdToken` override) returns
|
|
34
|
+
* `false`, so a forged token can never be accepted by omission.
|
|
35
|
+
*
|
|
36
|
+
* @returns `true` only when the token is authentic for the provider.
|
|
37
|
+
*/
|
|
38
|
+
async function verifyProviderIdToken(provider, token, nonce) {
|
|
39
|
+
const options = provider.options ?? {};
|
|
40
|
+
if (options.disableIdTokenSignIn) return false;
|
|
41
|
+
try {
|
|
42
|
+
if (options.verifyIdToken) return await options.verifyIdToken(token, nonce);
|
|
43
|
+
const config = provider.idToken;
|
|
44
|
+
if (!config) return false;
|
|
45
|
+
if ("verify" in config) return await config.verify(token, nonce);
|
|
46
|
+
if (token.split(".").length !== 3) return config.allowOpaqueToken === true;
|
|
47
|
+
const { alg } = decodeProtectedHeader(token);
|
|
48
|
+
const { payload } = await jwtVerify(token, config.jwks, {
|
|
49
|
+
issuer: config.issuer,
|
|
50
|
+
audience: config.audience,
|
|
51
|
+
algorithms: config.algorithms ?? (alg ? [alg] : void 0),
|
|
52
|
+
maxTokenAge: config.maxTokenAge
|
|
53
|
+
});
|
|
54
|
+
if (nonce && !await nonceMatches(payload.nonce, nonce, config.nonceComparison)) return false;
|
|
55
|
+
if (config.verifyClaims && !config.verifyClaims(payload)) return false;
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { supportsIdTokenSignIn, verifyProviderIdToken };
|
package/dist/oauth2/verify.d.mts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import { DpopReplayStore } from "./dpop.mjs";
|
|
1
2
|
import { JSONWebKeySet, JWTPayload, JWTVerifyOptions } from "jose";
|
|
2
3
|
|
|
3
4
|
//#region src/oauth2/verify.d.ts
|
|
5
|
+
type JwksFetchOptions = {
|
|
6
|
+
/** Jwks url or promise of a Jwks */jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
|
|
7
|
+
/**
|
|
8
|
+
* Stable object to cache the result of a function `jwksFetch` under,
|
|
9
|
+
* with the same TTL and kid-miss refetch rules as string sources.
|
|
10
|
+
* Without it, a function source is fetched on every verification.
|
|
11
|
+
*/
|
|
12
|
+
jwksCacheKey?: object;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
4
17
|
interface VerifyAccessTokenRemote {
|
|
5
18
|
/** Full url of the introspect endpoint. Should end with `/oauth2/introspect` */
|
|
6
19
|
introspectUrl: string;
|
|
@@ -14,29 +27,89 @@ interface VerifyAccessTokenRemote {
|
|
|
14
27
|
* is also still active.
|
|
15
28
|
*/
|
|
16
29
|
force?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Accept introspection responses that omit the `aud` claim even when a
|
|
32
|
+
* required `audience` is configured in `verifyOptions`.
|
|
33
|
+
*
|
|
34
|
+
* By default verification fails closed: if you configure an `audience` and
|
|
35
|
+
* the introspection response has no `aud` (or a mismatching one), the token
|
|
36
|
+
* is rejected. Some authorization servers legitimately omit `aud` from
|
|
37
|
+
* introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
|
|
38
|
+
* this if you trust the issuer to bind the token to this resource through
|
|
39
|
+
* another mechanism, as it skips the audience check in that case.
|
|
40
|
+
*
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
allowMissingAudience?: boolean;
|
|
44
|
+
}
|
|
45
|
+
interface VerifyAccessTokenOptions {
|
|
46
|
+
/** Verify options */
|
|
47
|
+
verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
48
|
+
/** Scopes to additionally verify. Token must include all but not exact. */
|
|
49
|
+
scopes?: string[];
|
|
50
|
+
/** Required to verify access token locally */
|
|
51
|
+
jwksUrl?: string;
|
|
52
|
+
/** If provided, can verify a token remotely */
|
|
53
|
+
remoteVerify?: VerifyAccessTokenRemote;
|
|
54
|
+
}
|
|
55
|
+
interface VerifyAccessTokenRequestOptions extends VerifyAccessTokenOptions {
|
|
56
|
+
dpop?: {
|
|
57
|
+
proofMaxAgeSeconds?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Store used to reject replayed DPoP proof `jti` values.
|
|
60
|
+
*
|
|
61
|
+
* Defaults to a process-local in-memory store, which is only safe for a
|
|
62
|
+
* single-instance deployment: it shares no state across instances and
|
|
63
|
+
* resets on cold start, so a captured proof can be replayed against
|
|
64
|
+
* another instance within the proof's lifetime. Supply a shared,
|
|
65
|
+
* persistent store (for example one backed by your database) for any
|
|
66
|
+
* multi-instance or serverless resource server.
|
|
67
|
+
*/
|
|
68
|
+
replayStore?: DpopReplayStore;
|
|
69
|
+
signingAlgorithms?: readonly string[];
|
|
70
|
+
};
|
|
17
71
|
}
|
|
72
|
+
interface ResourceRequestInput {
|
|
73
|
+
authorizationHeader: string | null | undefined;
|
|
74
|
+
dpopProofJwt?: string | null | undefined;
|
|
75
|
+
method: string;
|
|
76
|
+
url: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Builds a {@link ResourceRequestInput} from a standard `Request`, reading the
|
|
80
|
+
* `Authorization` and `DPoP` headers and the request method and URL. Resource
|
|
81
|
+
* servers share this so every entry point maps the wire request the same way.
|
|
82
|
+
*/
|
|
83
|
+
declare function requestToResourceInput(request: Request): ResourceRequestInput;
|
|
18
84
|
/**
|
|
19
85
|
* Performs local verification of an access token for your APIs.
|
|
20
86
|
*
|
|
21
87
|
* Can also be configured for remote verification.
|
|
22
88
|
*/
|
|
23
|
-
declare function verifyJwsAccessToken(token: string, opts: {
|
|
24
|
-
/**
|
|
25
|
-
verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
89
|
+
declare function verifyJwsAccessToken(token: string, opts: JwksFetchOptions & {
|
|
90
|
+
/** Verify options */verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
26
91
|
}): Promise<JWTPayload>;
|
|
27
|
-
declare function getJwks(token: string, opts:
|
|
28
|
-
/** Jwks url or promise of a Jwks */jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
|
|
29
|
-
}): Promise<JSONWebKeySet>;
|
|
92
|
+
declare function getJwks(token: string, opts: JwksFetchOptions): Promise<JSONWebKeySet>;
|
|
30
93
|
/**
|
|
31
|
-
* Performs local verification of
|
|
94
|
+
* Performs local verification of a bearer access token for your API.
|
|
32
95
|
*
|
|
33
|
-
* Can also be configured for remote verification.
|
|
96
|
+
* Can also be configured for remote verification. DPoP-bound access tokens
|
|
97
|
+
* require {@link verifyAccessTokenRequest}, because sender-constraining cannot
|
|
98
|
+
* be verified without the HTTP method, URL, Authorization scheme, DPoP proof,
|
|
99
|
+
* and access-token hash. This function rejects DPoP-bound tokens; reach for it
|
|
100
|
+
* only when you hold a raw token string and intentionally accept bearer tokens
|
|
101
|
+
* alone.
|
|
34
102
|
*/
|
|
35
|
-
declare function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
103
|
+
declare function verifyBearerToken(token: string, opts: VerifyAccessTokenOptions): Promise<JWTPayload>;
|
|
104
|
+
/**
|
|
105
|
+
* Verifies an HTTP resource request carrying an OAuth access token. This is the
|
|
106
|
+
* recommended resource-server entry point: it handles both bearer and
|
|
107
|
+
* DPoP-bound tokens, the bearer case being the request with no DPoP proof.
|
|
108
|
+
*
|
|
109
|
+
* It performs the same token validation as {@link verifyBearerToken}, then adds
|
|
110
|
+
* the RFC 9449 sender-constraint checks that need request context: authorization
|
|
111
|
+
* scheme, method, URL, DPoP proof, `ath`, and `cnf.jkt` binding.
|
|
112
|
+
*/
|
|
113
|
+
declare function verifyAccessTokenRequest(request: ResourceRequestInput, opts: VerifyAccessTokenRequestOptions): Promise<JWTPayload>;
|
|
41
114
|
//#endregion
|
|
42
|
-
export { getJwks,
|
|
115
|
+
export { ResourceRequestInput, VerifyAccessTokenOptions, VerifyAccessTokenRequestOptions, getJwks, requestToResourceInput, verifyAccessTokenRequest, verifyBearerToken, verifyJwsAccessToken };
|
package/dist/oauth2/verify.mjs
CHANGED
|
@@ -1,10 +1,76 @@
|
|
|
1
1
|
import { logger } from "../env/logger.mjs";
|
|
2
|
+
import { fetchRefusingRedirects } from "./reject-redirects.mjs";
|
|
3
|
+
import { createInMemoryDpopReplayStore, enforceDpopBinding, getDpopJktFromPayload, isDpopBindingError, parseAccessTokenAuthorization } from "./dpop.mjs";
|
|
2
4
|
import { APIError } from "better-call";
|
|
3
|
-
import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
4
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
5
|
+
import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
|
|
5
6
|
//#region src/oauth2/verify.ts
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const joseInfrastructureErrorCodes = new Set([
|
|
8
|
+
errors.JWKSTimeout.code,
|
|
9
|
+
errors.JWKSInvalid.code,
|
|
10
|
+
errors.JWKSMultipleMatchingKeys.code
|
|
11
|
+
]);
|
|
12
|
+
function isJoseInfrastructureError(error) {
|
|
13
|
+
return joseInfrastructureErrorCodes.has(error.code);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
const jwksCache = /* @__PURE__ */ new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Cache for function jwks sources, keyed by a caller-provided stable object.
|
|
21
|
+
* Entries are released with their key, so per-request keys cannot accumulate.
|
|
22
|
+
*/
|
|
23
|
+
const functionJwksCache = /* @__PURE__ */ new WeakMap();
|
|
24
|
+
/**
|
|
25
|
+
* How long a cached JWKS is trusted before it is refetched
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
const JWKS_CACHE_TTL_MS = 300 * 1e3;
|
|
30
|
+
const JWKS_NO_KID_REFETCH_COOLDOWN_MS = 30 * 1e3;
|
|
31
|
+
/**
|
|
32
|
+
* Returns the cached key set when it is within the TTL. When the token carries
|
|
33
|
+
* `kid`, the cached set must contain that key id; without `kid`, key selection
|
|
34
|
+
* is deferred to JOSE because RFC 7515 makes the header parameter optional.
|
|
35
|
+
*/
|
|
36
|
+
function getFreshJwksWithKid(cached, kid) {
|
|
37
|
+
if (!cached) return void 0;
|
|
38
|
+
if (Date.now() - cached.fetchedAt >= JWKS_CACHE_TTL_MS) return void 0;
|
|
39
|
+
if (kid && !cached.jwks.keys.some((jwk) => jwk.kid === kid)) return;
|
|
40
|
+
return cached.jwks;
|
|
41
|
+
}
|
|
42
|
+
function shouldRefetchCachedJwksWithoutKid(error, resolved) {
|
|
43
|
+
if (!(resolved.fromCache && !resolved.kid && (error instanceof errors.JWKSNoMatchingKey || error instanceof errors.JWSSignatureVerificationFailed))) return false;
|
|
44
|
+
if (!resolved.noKidRefetchedAt) return true;
|
|
45
|
+
return Date.now() - resolved.noKidRefetchedAt >= JWKS_NO_KID_REFETCH_COOLDOWN_MS;
|
|
46
|
+
}
|
|
47
|
+
async function fetchJwks(jwksFetch) {
|
|
48
|
+
const jwks = typeof jwksFetch === "string" ? await fetchRefusingRedirects(jwksFetch, { headers: { Accept: "application/json" } }).then(async (res) => {
|
|
49
|
+
if (res.error) throw new Error(`Jwks failed: ${res.error.message ?? res.error.statusText}`);
|
|
50
|
+
return res.data;
|
|
51
|
+
}) : await jwksFetch();
|
|
52
|
+
if (!jwks) throw new Error("No jwks found");
|
|
53
|
+
return jwks;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Builds a {@link ResourceRequestInput} from a standard `Request`, reading the
|
|
57
|
+
* `Authorization` and `DPoP` headers and the request method and URL. Resource
|
|
58
|
+
* servers share this so every entry point maps the wire request the same way.
|
|
59
|
+
*/
|
|
60
|
+
function requestToResourceInput(request) {
|
|
61
|
+
return {
|
|
62
|
+
authorizationHeader: request.headers.get("authorization"),
|
|
63
|
+
dpopProofJwt: request.headers.get("dpop"),
|
|
64
|
+
method: request.method,
|
|
65
|
+
url: request.url
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Process-local, single-instance replay store. See the warning on
|
|
70
|
+
* {@link VerifyAccessTokenRequestOptions.dpop.replayStore}; multi-instance
|
|
71
|
+
* resource servers must pass their own shared store.
|
|
72
|
+
*/
|
|
73
|
+
const defaultDpopReplayStore = createInMemoryDpopReplayStore();
|
|
8
74
|
/**
|
|
9
75
|
* Performs local verification of an access token for your APIs.
|
|
10
76
|
*
|
|
@@ -12,7 +78,17 @@ let jwks;
|
|
|
12
78
|
*/
|
|
13
79
|
async function verifyJwsAccessToken(token, opts) {
|
|
14
80
|
try {
|
|
15
|
-
const
|
|
81
|
+
const resolved = await getJwksForVerification(token, opts);
|
|
82
|
+
let jwt;
|
|
83
|
+
try {
|
|
84
|
+
jwt = await jwtVerify(token, createLocalJWKSet(resolved.jwks), opts.verifyOptions);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (shouldRefetchCachedJwksWithoutKid(error, resolved)) jwt = await jwtVerify(token, createLocalJWKSet((await getJwksForVerification(token, {
|
|
87
|
+
...opts,
|
|
88
|
+
forceRefresh: true
|
|
89
|
+
})).jwks), opts.verifyOptions);
|
|
90
|
+
else throw error;
|
|
91
|
+
}
|
|
16
92
|
if (jwt.payload.azp) jwt.payload.client_id = jwt.payload.azp;
|
|
17
93
|
return jwt.payload;
|
|
18
94
|
} catch (error) {
|
|
@@ -21,6 +97,9 @@ async function verifyJwsAccessToken(token, opts) {
|
|
|
21
97
|
}
|
|
22
98
|
}
|
|
23
99
|
async function getJwks(token, opts) {
|
|
100
|
+
return (await getJwksForVerification(token, opts)).jwks;
|
|
101
|
+
}
|
|
102
|
+
async function getJwksForVerification(token, opts) {
|
|
24
103
|
let jwtHeaders;
|
|
25
104
|
try {
|
|
26
105
|
jwtHeaders = decodeProtectedHeader(token);
|
|
@@ -28,22 +107,65 @@ async function getJwks(token, opts) {
|
|
|
28
107
|
if (error instanceof Error) throw error;
|
|
29
108
|
throw new Error(error);
|
|
30
109
|
}
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
110
|
+
const kid = jwtHeaders.kid;
|
|
111
|
+
if (typeof opts.jwksFetch !== "string") {
|
|
112
|
+
const cacheKey = opts.jwksCacheKey;
|
|
113
|
+
if (!cacheKey) {
|
|
114
|
+
const jwks = await opts.jwksFetch();
|
|
115
|
+
if (!jwks) throw new Error("No jwks found");
|
|
116
|
+
return {
|
|
117
|
+
jwks,
|
|
118
|
+
fromCache: false,
|
|
119
|
+
kid
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const cached = functionJwksCache.get(cacheKey);
|
|
123
|
+
const cachedJwks = opts.forceRefresh ? void 0 : getFreshJwksWithKid(cached, kid);
|
|
124
|
+
if (cachedJwks) return {
|
|
125
|
+
jwks: cachedJwks,
|
|
126
|
+
fromCache: true,
|
|
127
|
+
kid,
|
|
128
|
+
noKidRefetchedAt: cached?.noKidRefetchedAt
|
|
129
|
+
};
|
|
130
|
+
const jwks = await opts.jwksFetch();
|
|
37
131
|
if (!jwks) throw new Error("No jwks found");
|
|
132
|
+
const fetchedAt = Date.now();
|
|
133
|
+
functionJwksCache.set(cacheKey, {
|
|
134
|
+
jwks,
|
|
135
|
+
fetchedAt,
|
|
136
|
+
...opts.forceRefresh && !kid ? { noKidRefetchedAt: fetchedAt } : {}
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
jwks,
|
|
140
|
+
fromCache: false,
|
|
141
|
+
kid
|
|
142
|
+
};
|
|
38
143
|
}
|
|
39
|
-
|
|
144
|
+
const cacheKey = opts.jwksFetch;
|
|
145
|
+
const cached = jwksCache.get(cacheKey);
|
|
146
|
+
const cachedJwks = opts.forceRefresh ? void 0 : getFreshJwksWithKid(cached, kid);
|
|
147
|
+
if (!cachedJwks) {
|
|
148
|
+
const jwks = await fetchJwks(opts.jwksFetch);
|
|
149
|
+
const fetchedAt = Date.now();
|
|
150
|
+
jwksCache.set(cacheKey, {
|
|
151
|
+
jwks,
|
|
152
|
+
fetchedAt,
|
|
153
|
+
...opts.forceRefresh && !kid ? { noKidRefetchedAt: fetchedAt } : {}
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
jwks,
|
|
157
|
+
fromCache: false,
|
|
158
|
+
kid
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
jwks: cachedJwks,
|
|
163
|
+
fromCache: true,
|
|
164
|
+
kid,
|
|
165
|
+
noKidRefetchedAt: cached?.noKidRefetchedAt
|
|
166
|
+
};
|
|
40
167
|
}
|
|
41
|
-
|
|
42
|
-
* Performs local verification of an access token for your API.
|
|
43
|
-
*
|
|
44
|
-
* Can also be configured for remote verification.
|
|
45
|
-
*/
|
|
46
|
-
async function verifyAccessToken(token, opts) {
|
|
168
|
+
async function verifyAccessTokenPayload(token, opts) {
|
|
47
169
|
let payload;
|
|
48
170
|
if (opts.jwksUrl && !opts?.remoteVerify?.force) try {
|
|
49
171
|
payload = await verifyJwsAccessToken(token, {
|
|
@@ -51,13 +173,15 @@ async function verifyAccessToken(token, opts) {
|
|
|
51
173
|
verifyOptions: opts.verifyOptions
|
|
52
174
|
});
|
|
53
175
|
} catch (error) {
|
|
54
|
-
if (error instanceof Error) if (error.name === "TypeError" || error.name === "JWSInvalid") {} else if (error
|
|
55
|
-
else if (error
|
|
56
|
-
|
|
176
|
+
if (error instanceof Error) if (error.name === "TypeError" || error.name === "JWSInvalid") {} else if (error instanceof errors.JWTExpired) throw new APIError("UNAUTHORIZED", { message: "token expired" });
|
|
177
|
+
else if (error instanceof errors.JOSEError) {
|
|
178
|
+
if (isJoseInfrastructureError(error)) throw error;
|
|
179
|
+
throw new APIError("UNAUTHORIZED", { message: "invalid access token" });
|
|
180
|
+
} else throw error;
|
|
57
181
|
else throw new Error(error);
|
|
58
182
|
}
|
|
59
183
|
if (opts?.remoteVerify) {
|
|
60
|
-
const { data: introspect, error: introspectError } = await
|
|
184
|
+
const { data: introspect, error: introspectError } = await fetchRefusingRedirects(opts.remoteVerify.introspectUrl, {
|
|
61
185
|
method: "POST",
|
|
62
186
|
headers: {
|
|
63
187
|
Accept: "application/json",
|
|
@@ -75,8 +199,9 @@ async function verifyAccessToken(token, opts) {
|
|
|
75
199
|
if (!introspect.active) throw new APIError("UNAUTHORIZED", { message: "token inactive" });
|
|
76
200
|
try {
|
|
77
201
|
const unsecuredJwt = new UnsecuredJWT(introspect).encode();
|
|
78
|
-
const { audience: _audience, ...
|
|
79
|
-
|
|
202
|
+
const { audience: _audience, ...verifyOptionsNoAudience } = opts.verifyOptions;
|
|
203
|
+
const skipAudience = !introspect.aud && opts.remoteVerify.allowMissingAudience === true;
|
|
204
|
+
payload = UnsecuredJWT.decode(unsecuredJwt, skipAudience ? verifyOptionsNoAudience : opts.verifyOptions).payload;
|
|
80
205
|
} catch (error) {
|
|
81
206
|
throw new Error(error);
|
|
82
207
|
}
|
|
@@ -88,5 +213,58 @@ async function verifyAccessToken(token, opts) {
|
|
|
88
213
|
}
|
|
89
214
|
return payload;
|
|
90
215
|
}
|
|
216
|
+
function throwDpopUnauthorized(message, error) {
|
|
217
|
+
throw new APIError("UNAUTHORIZED", error ? {
|
|
218
|
+
message,
|
|
219
|
+
error,
|
|
220
|
+
error_description: message
|
|
221
|
+
} : { message });
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Performs local verification of a bearer access token for your API.
|
|
225
|
+
*
|
|
226
|
+
* Can also be configured for remote verification. DPoP-bound access tokens
|
|
227
|
+
* require {@link verifyAccessTokenRequest}, because sender-constraining cannot
|
|
228
|
+
* be verified without the HTTP method, URL, Authorization scheme, DPoP proof,
|
|
229
|
+
* and access-token hash. This function rejects DPoP-bound tokens; reach for it
|
|
230
|
+
* only when you hold a raw token string and intentionally accept bearer tokens
|
|
231
|
+
* alone.
|
|
232
|
+
*/
|
|
233
|
+
async function verifyBearerToken(token, opts) {
|
|
234
|
+
const payload = await verifyAccessTokenPayload(token, opts);
|
|
235
|
+
if (getDpopJktFromPayload(payload)) throwDpopUnauthorized("DPoP-bound access token requires verifyAccessTokenRequest", "invalid_token");
|
|
236
|
+
return payload;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Verifies an HTTP resource request carrying an OAuth access token. This is the
|
|
240
|
+
* recommended resource-server entry point: it handles both bearer and
|
|
241
|
+
* DPoP-bound tokens, the bearer case being the request with no DPoP proof.
|
|
242
|
+
*
|
|
243
|
+
* It performs the same token validation as {@link verifyBearerToken}, then adds
|
|
244
|
+
* the RFC 9449 sender-constraint checks that need request context: authorization
|
|
245
|
+
* scheme, method, URL, DPoP proof, `ath`, and `cnf.jkt` binding.
|
|
246
|
+
*/
|
|
247
|
+
async function verifyAccessTokenRequest(request, opts) {
|
|
248
|
+
const authorization = parseAccessTokenAuthorization(request.authorizationHeader);
|
|
249
|
+
if (!authorization?.token) throwDpopUnauthorized("missing authorization header");
|
|
250
|
+
if (authorization.scheme === "Unknown") throwDpopUnauthorized("authorization scheme must be Bearer or DPoP", "invalid_token");
|
|
251
|
+
const payload = await verifyAccessTokenPayload(authorization.token, opts);
|
|
252
|
+
try {
|
|
253
|
+
await enforceDpopBinding({
|
|
254
|
+
payload,
|
|
255
|
+
authorization,
|
|
256
|
+
proofJwt: request.dpopProofJwt,
|
|
257
|
+
method: request.method,
|
|
258
|
+
url: request.url,
|
|
259
|
+
replayStore: opts.dpop?.replayStore ?? defaultDpopReplayStore,
|
|
260
|
+
proofMaxAgeSeconds: opts.dpop?.proofMaxAgeSeconds,
|
|
261
|
+
signingAlgorithms: opts.dpop?.signingAlgorithms
|
|
262
|
+
});
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (isDpopBindingError(error)) throwDpopUnauthorized(error.message, error.code);
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
return payload;
|
|
268
|
+
}
|
|
91
269
|
//#endregion
|
|
92
|
-
export { getJwks,
|
|
270
|
+
export { getJwks, requestToResourceInput, verifyAccessTokenRequest, verifyBearerToken, verifyJwsAccessToken };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import * as jose from "jose";
|
|
3
|
+
|
|
2
4
|
//#region src/social-providers/apple.d.ts
|
|
3
5
|
interface AppleProfile {
|
|
4
6
|
/**
|
|
@@ -12,7 +14,7 @@ interface AppleProfile {
|
|
|
12
14
|
* The email address is either the user's real email address or the proxy
|
|
13
15
|
* address, depending on their status private email relay service.
|
|
14
16
|
*/
|
|
15
|
-
email
|
|
17
|
+
email?: string;
|
|
16
18
|
/**
|
|
17
19
|
* A string or Boolean value that indicates whether the service verifies
|
|
18
20
|
* the email. The value can either be a string ("true" or "false") or a
|
|
@@ -60,7 +62,7 @@ interface AppleNonConformUser {
|
|
|
60
62
|
email: string;
|
|
61
63
|
}
|
|
62
64
|
interface AppleOptions extends ProviderOptions<AppleProfile> {
|
|
63
|
-
clientId: string;
|
|
65
|
+
clientId: string | string[];
|
|
64
66
|
appBundleIdentifier?: string | undefined;
|
|
65
67
|
audience?: (string | string[]) | undefined;
|
|
66
68
|
}
|
|
@@ -70,7 +72,8 @@ declare const apple: (options: AppleOptions) => {
|
|
|
70
72
|
createAuthorizationURL({
|
|
71
73
|
state,
|
|
72
74
|
scopes,
|
|
73
|
-
redirectURI
|
|
75
|
+
redirectURI,
|
|
76
|
+
additionalParams
|
|
74
77
|
}: {
|
|
75
78
|
state: string;
|
|
76
79
|
codeVerifier: string;
|
|
@@ -78,6 +81,8 @@ declare const apple: (options: AppleOptions) => {
|
|
|
78
81
|
redirectURI: string;
|
|
79
82
|
display?: string | undefined;
|
|
80
83
|
loginHint?: string | undefined;
|
|
84
|
+
idTokenNonce?: string | undefined;
|
|
85
|
+
additionalParams?: Record<string, string> | undefined;
|
|
81
86
|
}): Promise<URL>;
|
|
82
87
|
validateAuthorizationCode: ({
|
|
83
88
|
code,
|
|
@@ -89,9 +94,16 @@ declare const apple: (options: AppleOptions) => {
|
|
|
89
94
|
codeVerifier?: string | undefined;
|
|
90
95
|
deviceId?: string | undefined;
|
|
91
96
|
}) => Promise<OAuth2Tokens>;
|
|
92
|
-
|
|
97
|
+
idToken: {
|
|
98
|
+
jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
|
|
99
|
+
issuer: string;
|
|
100
|
+
audience: string | string[];
|
|
101
|
+
maxTokenAge: string;
|
|
102
|
+
nonceComparison: "exact-or-sha256";
|
|
103
|
+
};
|
|
93
104
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
94
105
|
getUserInfo(token: OAuth2Tokens & {
|
|
106
|
+
expectedIdTokenNonce?: string | undefined;
|
|
95
107
|
user?: {
|
|
96
108
|
name?: {
|
|
97
109
|
firstName?: string;
|