@better-auth/core 1.6.2 → 1.7.0-beta.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/dist/context/global.mjs +1 -1
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/client-assertion.d.mts +59 -0
- package/dist/oauth2/client-assertion.mjs +64 -0
- package/dist/oauth2/client-credentials-token.d.mts +16 -13
- package/dist/oauth2/client-credentials-token.mjs +25 -11
- package/dist/oauth2/index.d.mts +2 -1
- package/dist/oauth2/index.mjs +2 -1
- package/dist/oauth2/refresh-access-token.d.mts +10 -3
- package/dist/oauth2/refresh-access-token.mjs +23 -10
- package/dist/oauth2/validate-authorization-code.d.mts +10 -3
- package/dist/oauth2/validate-authorization-code.mjs +22 -9
- package/dist/oauth2/verify.mjs +1 -1
- package/dist/social-providers/apple.mjs +1 -1
- package/dist/social-providers/cognito.mjs +1 -1
- package/dist/social-providers/facebook.mjs +1 -1
- package/dist/social-providers/google.mjs +1 -1
- package/dist/social-providers/line.mjs +1 -1
- package/dist/social-providers/microsoft-entra-id.mjs +1 -1
- package/dist/social-providers/paypal.mjs +1 -1
- package/package.json +1 -1
- package/src/oauth2/client-assertion.ts +136 -0
- package/src/oauth2/client-credentials-token.ts +53 -16
- package/src/oauth2/index.ts +10 -0
- package/src/oauth2/refresh-access-token.ts +37 -13
- package/src/oauth2/validate-authorization-code.ts +36 -12
package/dist/context/global.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ATTR_HTTP_RESPONSE_STATUS_CODE } from "./attributes.mjs";
|
|
2
2
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
3
3
|
//#region src/instrumentation/tracer.ts
|
|
4
|
-
const tracer = trace.getTracer("better-auth", "1.
|
|
4
|
+
const tracer = trace.getTracer("better-auth", "1.7.0-beta.0");
|
|
5
5
|
/**
|
|
6
6
|
* Better-auth uses `throw ctx.redirect(url)` for flow control (e.g. OAuth
|
|
7
7
|
* callbacks). These are APIErrors with 3xx status codes and should not be
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/oauth2/client-assertion.d.ts
|
|
2
|
+
/** Asymmetric signing algorithms compatible with private_key_jwt (RFC 7523). */
|
|
3
|
+
declare const ASSERTION_SIGNING_ALGORITHMS: readonly ["RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
4
|
+
type AssertionSigningAlgorithm = (typeof ASSERTION_SIGNING_ALGORITHMS)[number];
|
|
5
|
+
declare const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
6
|
+
interface ClientAssertionConfig {
|
|
7
|
+
/** Pre-signed JWT assertion string. If provided, signing is skipped. */
|
|
8
|
+
assertion?: string;
|
|
9
|
+
/** Private key in JWK format for signing. */
|
|
10
|
+
privateKeyJwk?: JsonWebKey;
|
|
11
|
+
/** Private key in PKCS#8 PEM format for signing. */
|
|
12
|
+
privateKeyPem?: string;
|
|
13
|
+
/** Key ID to include in the JWT header. */
|
|
14
|
+
kid?: string;
|
|
15
|
+
/** Asymmetric signing algorithm. Symmetric algorithms (HS256) and "none" are not allowed. @default "RS256" */
|
|
16
|
+
algorithm?: AssertionSigningAlgorithm;
|
|
17
|
+
/** Token endpoint URL (used as the JWT `aud` claim). */
|
|
18
|
+
tokenEndpoint?: string;
|
|
19
|
+
/** Assertion lifetime in seconds. @default 120 */
|
|
20
|
+
expiresIn?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Signs an RFC 7523 client assertion JWT for `private_key_jwt` authentication.
|
|
24
|
+
*
|
|
25
|
+
* The JWT contains: iss=clientId, sub=clientId, aud=tokenEndpoint,
|
|
26
|
+
* exp=now+120s, jti=unique, iat=now.
|
|
27
|
+
*/
|
|
28
|
+
declare function signClientAssertion({
|
|
29
|
+
clientId,
|
|
30
|
+
tokenEndpoint,
|
|
31
|
+
privateKeyJwk,
|
|
32
|
+
privateKeyPem,
|
|
33
|
+
kid,
|
|
34
|
+
algorithm,
|
|
35
|
+
expiresIn
|
|
36
|
+
}: {
|
|
37
|
+
clientId: string;
|
|
38
|
+
tokenEndpoint: string;
|
|
39
|
+
privateKeyJwk?: JsonWebKey;
|
|
40
|
+
privateKeyPem?: string;
|
|
41
|
+
kid?: string;
|
|
42
|
+
algorithm?: AssertionSigningAlgorithm;
|
|
43
|
+
expiresIn?: number;
|
|
44
|
+
}): Promise<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Resolves a ClientAssertionConfig into `client_assertion` + `client_assertion_type`
|
|
47
|
+
* params for injection into a token request body.
|
|
48
|
+
*/
|
|
49
|
+
declare function resolveAssertionParams({
|
|
50
|
+
clientAssertion,
|
|
51
|
+
clientId,
|
|
52
|
+
tokenEndpoint
|
|
53
|
+
}: {
|
|
54
|
+
clientAssertion: ClientAssertionConfig;
|
|
55
|
+
clientId: string;
|
|
56
|
+
tokenEndpoint?: string;
|
|
57
|
+
}): Promise<Record<string, string>>;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { ASSERTION_SIGNING_ALGORITHMS, AssertionSigningAlgorithm, CLIENT_ASSERTION_TYPE, ClientAssertionConfig, resolveAssertionParams, signClientAssertion };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SignJWT, importJWK, importPKCS8 } from "jose";
|
|
2
|
+
//#region src/oauth2/client-assertion.ts
|
|
3
|
+
/** Asymmetric signing algorithms compatible with private_key_jwt (RFC 7523). */
|
|
4
|
+
const ASSERTION_SIGNING_ALGORITHMS = [
|
|
5
|
+
"RS256",
|
|
6
|
+
"RS384",
|
|
7
|
+
"RS512",
|
|
8
|
+
"PS256",
|
|
9
|
+
"PS384",
|
|
10
|
+
"PS512",
|
|
11
|
+
"ES256",
|
|
12
|
+
"ES384",
|
|
13
|
+
"ES512",
|
|
14
|
+
"EdDSA"
|
|
15
|
+
];
|
|
16
|
+
const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
17
|
+
/**
|
|
18
|
+
* Signs an RFC 7523 client assertion JWT for `private_key_jwt` authentication.
|
|
19
|
+
*
|
|
20
|
+
* The JWT contains: iss=clientId, sub=clientId, aud=tokenEndpoint,
|
|
21
|
+
* exp=now+120s, jti=unique, iat=now.
|
|
22
|
+
*/
|
|
23
|
+
async function signClientAssertion({ clientId, tokenEndpoint, privateKeyJwk, privateKeyPem, kid, algorithm, expiresIn = 120 }) {
|
|
24
|
+
const resolvedKid = kid ?? privateKeyJwk?.kid;
|
|
25
|
+
const resolvedAlg = algorithm ?? privateKeyJwk?.alg ?? "RS256";
|
|
26
|
+
let key;
|
|
27
|
+
if (privateKeyJwk) key = await importJWK(privateKeyJwk, resolvedAlg);
|
|
28
|
+
else if (privateKeyPem) key = await importPKCS8(privateKeyPem, resolvedAlg);
|
|
29
|
+
else throw new Error("private_key_jwt requires either privateKeyJwk or privateKeyPem");
|
|
30
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
31
|
+
const jti = crypto.randomUUID();
|
|
32
|
+
const header = {
|
|
33
|
+
alg: resolvedAlg,
|
|
34
|
+
typ: "JWT"
|
|
35
|
+
};
|
|
36
|
+
if (resolvedKid) header.kid = resolvedKid;
|
|
37
|
+
return new SignJWT({}).setProtectedHeader(header).setIssuer(clientId).setSubject(clientId).setAudience(tokenEndpoint).setIssuedAt(now).setExpirationTime(now + expiresIn).setJti(jti).sign(key);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves a ClientAssertionConfig into `client_assertion` + `client_assertion_type`
|
|
41
|
+
* params for injection into a token request body.
|
|
42
|
+
*/
|
|
43
|
+
async function resolveAssertionParams({ clientAssertion, clientId, tokenEndpoint }) {
|
|
44
|
+
let assertion = clientAssertion.assertion;
|
|
45
|
+
if (!assertion) {
|
|
46
|
+
const audEndpoint = tokenEndpoint ?? clientAssertion.tokenEndpoint;
|
|
47
|
+
if (!audEndpoint) throw new Error("private_key_jwt requires a tokenEndpoint for the JWT audience claim");
|
|
48
|
+
assertion = await signClientAssertion({
|
|
49
|
+
clientId,
|
|
50
|
+
tokenEndpoint: audEndpoint,
|
|
51
|
+
privateKeyJwk: clientAssertion.privateKeyJwk,
|
|
52
|
+
privateKeyPem: clientAssertion.privateKeyPem,
|
|
53
|
+
kid: clientAssertion.kid,
|
|
54
|
+
algorithm: clientAssertion.algorithm,
|
|
55
|
+
expiresIn: clientAssertion.expiresIn
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
client_assertion: assertion,
|
|
60
|
+
client_assertion_type: CLIENT_ASSERTION_TYPE
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { ASSERTION_SIGNING_ALGORITHMS, CLIENT_ASSERTION_TYPE, resolveAssertionParams, signClientAssertion };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientAssertionConfig } from "./client-assertion.mjs";
|
|
1
2
|
import { AwaitableFunction } from "../types/helper.mjs";
|
|
2
3
|
import { OAuth2Tokens, ProviderOptions } from "./oauth-provider.mjs";
|
|
3
4
|
|
|
@@ -6,13 +7,15 @@ declare function clientCredentialsTokenRequest({
|
|
|
6
7
|
options,
|
|
7
8
|
scope,
|
|
8
9
|
authentication,
|
|
10
|
+
clientAssertion,
|
|
11
|
+
tokenEndpoint,
|
|
9
12
|
resource
|
|
10
13
|
}: {
|
|
11
|
-
options: AwaitableFunction<ProviderOptions
|
|
12
|
-
clientSecret: string;
|
|
13
|
-
}>;
|
|
14
|
+
options: AwaitableFunction<ProviderOptions>;
|
|
14
15
|
scope?: string | undefined;
|
|
15
|
-
authentication?: ("basic" | "post") | undefined;
|
|
16
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
17
|
+
clientAssertion?: ClientAssertionConfig | undefined; /** Token endpoint URL. Used as the JWT `aud` claim when signing assertions. */
|
|
18
|
+
tokenEndpoint?: string | undefined;
|
|
16
19
|
resource?: (string | string[]) | undefined;
|
|
17
20
|
}): Promise<{
|
|
18
21
|
body: URLSearchParams;
|
|
@@ -25,14 +28,14 @@ declare function createClientCredentialsTokenRequest({
|
|
|
25
28
|
options,
|
|
26
29
|
scope,
|
|
27
30
|
authentication,
|
|
28
|
-
resource
|
|
31
|
+
resource,
|
|
32
|
+
extraParams
|
|
29
33
|
}: {
|
|
30
|
-
options: ProviderOptions
|
|
31
|
-
clientSecret: string;
|
|
32
|
-
};
|
|
34
|
+
options: ProviderOptions;
|
|
33
35
|
scope?: string | undefined;
|
|
34
|
-
authentication?: ("basic" | "post") | undefined;
|
|
36
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
35
37
|
resource?: (string | string[]) | undefined;
|
|
38
|
+
extraParams?: Record<string, string> | undefined;
|
|
36
39
|
}): {
|
|
37
40
|
body: URLSearchParams;
|
|
38
41
|
headers: Record<string, any>;
|
|
@@ -42,14 +45,14 @@ declare function clientCredentialsToken({
|
|
|
42
45
|
tokenEndpoint,
|
|
43
46
|
scope,
|
|
44
47
|
authentication,
|
|
48
|
+
clientAssertion,
|
|
45
49
|
resource
|
|
46
50
|
}: {
|
|
47
|
-
options: AwaitableFunction<ProviderOptions
|
|
48
|
-
clientSecret: string;
|
|
49
|
-
}>;
|
|
51
|
+
options: AwaitableFunction<ProviderOptions>;
|
|
50
52
|
tokenEndpoint: string;
|
|
51
53
|
scope: string;
|
|
52
|
-
authentication?: ("basic" | "post") | undefined;
|
|
54
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
55
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
53
56
|
resource?: (string | string[]) | undefined;
|
|
54
57
|
}): Promise<OAuth2Tokens>;
|
|
55
58
|
//#endregion
|
|
@@ -1,19 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveAssertionParams } from "./client-assertion.mjs";
|
|
2
|
+
import { base64 } from "@better-auth/utils/base64";
|
|
2
3
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
4
|
//#region src/oauth2/client-credentials-token.ts
|
|
4
|
-
async function clientCredentialsTokenRequest({ options, scope, authentication, resource }) {
|
|
5
|
+
async function clientCredentialsTokenRequest({ options, scope, authentication, clientAssertion, tokenEndpoint, resource }) {
|
|
5
6
|
options = typeof options === "function" ? await options() : options;
|
|
7
|
+
let extraParams;
|
|
8
|
+
if (authentication === "private_key_jwt") {
|
|
9
|
+
if (!clientAssertion) throw new Error("private_key_jwt authentication requires a clientAssertion configuration");
|
|
10
|
+
extraParams = await resolveAssertionParams({
|
|
11
|
+
clientAssertion,
|
|
12
|
+
clientId: Array.isArray(options.clientId) ? options.clientId[0] : options.clientId,
|
|
13
|
+
tokenEndpoint
|
|
14
|
+
});
|
|
15
|
+
}
|
|
6
16
|
return createClientCredentialsTokenRequest({
|
|
7
17
|
options,
|
|
8
18
|
scope,
|
|
9
19
|
authentication,
|
|
10
|
-
resource
|
|
20
|
+
resource,
|
|
21
|
+
extraParams
|
|
11
22
|
});
|
|
12
23
|
}
|
|
13
24
|
/**
|
|
14
25
|
* @deprecated use async'd clientCredentialsTokenRequest instead
|
|
15
26
|
*/
|
|
16
|
-
function createClientCredentialsTokenRequest({ options, scope, authentication, resource }) {
|
|
27
|
+
function createClientCredentialsTokenRequest({ options, scope, authentication, resource, extraParams }) {
|
|
17
28
|
const body = new URLSearchParams();
|
|
18
29
|
const headers = {
|
|
19
30
|
"content-type": "application/x-www-form-urlencoded",
|
|
@@ -23,24 +34,27 @@ function createClientCredentialsTokenRequest({ options, scope, authentication, r
|
|
|
23
34
|
scope && body.set("scope", scope);
|
|
24
35
|
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
25
36
|
else for (const _resource of resource) body.append("resource", _resource);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} else {
|
|
30
|
-
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
37
|
+
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
38
|
+
if (authentication === "basic") headers["authorization"] = `Basic ${base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`)}`;
|
|
39
|
+
else {
|
|
31
40
|
body.set("client_id", primaryClientId);
|
|
32
|
-
body.set("client_secret", options.clientSecret);
|
|
41
|
+
if (authentication !== "private_key_jwt" && options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
42
|
+
}
|
|
43
|
+
if (extraParams) {
|
|
44
|
+
for (const [key, value] of Object.entries(extraParams)) if (!body.has(key)) body.append(key, value);
|
|
33
45
|
}
|
|
34
46
|
return {
|
|
35
47
|
body,
|
|
36
48
|
headers
|
|
37
49
|
};
|
|
38
50
|
}
|
|
39
|
-
async function clientCredentialsToken({ options, tokenEndpoint, scope, authentication, resource }) {
|
|
51
|
+
async function clientCredentialsToken({ options, tokenEndpoint, scope, authentication, clientAssertion, resource }) {
|
|
40
52
|
const { body, headers } = await clientCredentialsTokenRequest({
|
|
41
53
|
options,
|
|
42
54
|
scope,
|
|
43
55
|
authentication,
|
|
56
|
+
clientAssertion,
|
|
57
|
+
tokenEndpoint,
|
|
44
58
|
resource
|
|
45
59
|
});
|
|
46
60
|
const { data, error } = await betterFetch(tokenEndpoint, {
|
package/dist/oauth2/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ASSERTION_SIGNING_ALGORITHMS, AssertionSigningAlgorithm, CLIENT_ASSERTION_TYPE, ClientAssertionConfig, resolveAssertionParams, signClientAssertion } from "./client-assertion.mjs";
|
|
1
2
|
import { OAuth2Tokens, OAuth2UserInfo, OAuthProvider, ProviderOptions } from "./oauth-provider.mjs";
|
|
2
3
|
import { clientCredentialsToken, clientCredentialsTokenRequest, createClientCredentialsTokenRequest } from "./client-credentials-token.mjs";
|
|
3
4
|
import { createAuthorizationURL } from "./create-authorization-url.mjs";
|
|
@@ -5,4 +6,4 @@ import { createRefreshAccessTokenRequest, refreshAccessToken, refreshAccessToken
|
|
|
5
6
|
import { generateCodeChallenge, getOAuth2Tokens } from "./utils.mjs";
|
|
6
7
|
import { authorizationCodeRequest, createAuthorizationCodeRequest, validateAuthorizationCode, validateToken } from "./validate-authorization-code.mjs";
|
|
7
8
|
import { getJwks, verifyAccessToken, verifyJwsAccessToken } from "./verify.mjs";
|
|
8
|
-
export { type OAuth2Tokens, type OAuth2UserInfo, type OAuthProvider, type ProviderOptions, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, refreshAccessToken, refreshAccessTokenRequest, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
|
9
|
+
export { ASSERTION_SIGNING_ALGORITHMS, type AssertionSigningAlgorithm, CLIENT_ASSERTION_TYPE, type ClientAssertionConfig, type OAuth2Tokens, type OAuth2UserInfo, type OAuthProvider, type ProviderOptions, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, refreshAccessToken, refreshAccessTokenRequest, resolveAssertionParams, signClientAssertion, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
package/dist/oauth2/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { ASSERTION_SIGNING_ALGORITHMS, CLIENT_ASSERTION_TYPE, resolveAssertionParams, signClientAssertion } from "./client-assertion.mjs";
|
|
1
2
|
import { clientCredentialsToken, clientCredentialsTokenRequest, createClientCredentialsTokenRequest } from "./client-credentials-token.mjs";
|
|
2
3
|
import { generateCodeChallenge, getOAuth2Tokens } from "./utils.mjs";
|
|
3
4
|
import { createAuthorizationURL } from "./create-authorization-url.mjs";
|
|
4
5
|
import { createRefreshAccessTokenRequest, refreshAccessToken, refreshAccessTokenRequest } from "./refresh-access-token.mjs";
|
|
5
6
|
import { authorizationCodeRequest, createAuthorizationCodeRequest, validateAuthorizationCode, validateToken } from "./validate-authorization-code.mjs";
|
|
6
7
|
import { getJwks, verifyAccessToken, verifyJwsAccessToken } from "./verify.mjs";
|
|
7
|
-
export { authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, refreshAccessToken, refreshAccessTokenRequest, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
|
8
|
+
export { ASSERTION_SIGNING_ALGORITHMS, CLIENT_ASSERTION_TYPE, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, refreshAccessToken, refreshAccessTokenRequest, resolveAssertionParams, signClientAssertion, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientAssertionConfig } from "./client-assertion.mjs";
|
|
1
2
|
import { AwaitableFunction } from "../types/helper.mjs";
|
|
2
3
|
import { OAuth2Tokens, ProviderOptions } from "./oauth-provider.mjs";
|
|
3
4
|
|
|
@@ -6,12 +7,16 @@ declare function refreshAccessTokenRequest({
|
|
|
6
7
|
refreshToken,
|
|
7
8
|
options,
|
|
8
9
|
authentication,
|
|
10
|
+
clientAssertion,
|
|
11
|
+
tokenEndpoint,
|
|
9
12
|
extraParams,
|
|
10
13
|
resource
|
|
11
14
|
}: {
|
|
12
15
|
refreshToken: string;
|
|
13
16
|
options: AwaitableFunction<Partial<ProviderOptions>>;
|
|
14
|
-
authentication?: ("basic" | "post") | undefined;
|
|
17
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
18
|
+
clientAssertion?: ClientAssertionConfig | undefined; /** Token endpoint URL. Used as the JWT `aud` claim when signing assertions. */
|
|
19
|
+
tokenEndpoint?: string | undefined;
|
|
15
20
|
extraParams?: Record<string, string> | undefined;
|
|
16
21
|
resource?: (string | string[]) | undefined;
|
|
17
22
|
}): Promise<{
|
|
@@ -30,7 +35,7 @@ declare function createRefreshAccessTokenRequest({
|
|
|
30
35
|
}: {
|
|
31
36
|
refreshToken: string;
|
|
32
37
|
options: ProviderOptions;
|
|
33
|
-
authentication?: ("basic" | "post") | undefined;
|
|
38
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
34
39
|
extraParams?: Record<string, string> | undefined;
|
|
35
40
|
resource?: (string | string[]) | undefined;
|
|
36
41
|
}): {
|
|
@@ -42,12 +47,14 @@ declare function refreshAccessToken({
|
|
|
42
47
|
options,
|
|
43
48
|
tokenEndpoint,
|
|
44
49
|
authentication,
|
|
50
|
+
clientAssertion,
|
|
45
51
|
extraParams
|
|
46
52
|
}: {
|
|
47
53
|
refreshToken: string;
|
|
48
54
|
options: Partial<ProviderOptions>;
|
|
49
55
|
tokenEndpoint: string;
|
|
50
|
-
authentication?: ("basic" | "post") | undefined;
|
|
56
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
57
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
51
58
|
extraParams?: Record<string, string> | undefined;
|
|
52
59
|
}): Promise<OAuth2Tokens>;
|
|
53
60
|
//#endregion
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
import { resolveAssertionParams } from "./client-assertion.mjs";
|
|
1
2
|
import { base64 } from "@better-auth/utils/base64";
|
|
2
3
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
4
|
//#region src/oauth2/refresh-access-token.ts
|
|
4
|
-
async function refreshAccessTokenRequest({ refreshToken, options, authentication, extraParams, resource }) {
|
|
5
|
+
async function refreshAccessTokenRequest({ refreshToken, options, authentication, clientAssertion, tokenEndpoint, extraParams, resource }) {
|
|
5
6
|
options = typeof options === "function" ? await options() : options;
|
|
7
|
+
if (authentication === "private_key_jwt") {
|
|
8
|
+
if (!clientAssertion) throw new Error("private_key_jwt authentication requires a clientAssertion configuration");
|
|
9
|
+
const assertionParams = await resolveAssertionParams({
|
|
10
|
+
clientAssertion,
|
|
11
|
+
clientId: Array.isArray(options.clientId) ? options.clientId[0] : options.clientId,
|
|
12
|
+
tokenEndpoint
|
|
13
|
+
});
|
|
14
|
+
extraParams = {
|
|
15
|
+
...extraParams,
|
|
16
|
+
...assertionParams
|
|
17
|
+
};
|
|
18
|
+
}
|
|
6
19
|
return createRefreshAccessTokenRequest({
|
|
7
20
|
refreshToken,
|
|
8
21
|
options,
|
|
@@ -22,14 +35,12 @@ function createRefreshAccessTokenRequest({ refreshToken, options, authentication
|
|
|
22
35
|
};
|
|
23
36
|
body.set("grant_type", "refresh_token");
|
|
24
37
|
body.set("refresh_token", refreshToken);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} else {
|
|
30
|
-
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
38
|
+
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
39
|
+
if (authentication === "basic") if (primaryClientId) headers["authorization"] = "Basic " + base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`);
|
|
40
|
+
else headers["authorization"] = "Basic " + base64.encode(`:${options.clientSecret ?? ""}`);
|
|
41
|
+
else {
|
|
31
42
|
body.set("client_id", primaryClientId);
|
|
32
|
-
if (options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
43
|
+
if (authentication !== "private_key_jwt" && options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
33
44
|
}
|
|
34
45
|
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
35
46
|
else for (const _resource of resource) body.append("resource", _resource);
|
|
@@ -39,11 +50,13 @@ function createRefreshAccessTokenRequest({ refreshToken, options, authentication
|
|
|
39
50
|
headers
|
|
40
51
|
};
|
|
41
52
|
}
|
|
42
|
-
async function refreshAccessToken({ refreshToken, options, tokenEndpoint, authentication, extraParams }) {
|
|
43
|
-
const { body, headers } = await
|
|
53
|
+
async function refreshAccessToken({ refreshToken, options, tokenEndpoint, authentication, clientAssertion, extraParams }) {
|
|
54
|
+
const { body, headers } = await refreshAccessTokenRequest({
|
|
44
55
|
refreshToken,
|
|
45
56
|
options,
|
|
46
57
|
authentication,
|
|
58
|
+
clientAssertion,
|
|
59
|
+
tokenEndpoint,
|
|
47
60
|
extraParams
|
|
48
61
|
});
|
|
49
62
|
const { data, error } = await betterFetch(tokenEndpoint, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientAssertionConfig } from "./client-assertion.mjs";
|
|
1
2
|
import { AwaitableFunction } from "../types/helper.mjs";
|
|
2
3
|
import { OAuth2Tokens, ProviderOptions } from "./oauth-provider.mjs";
|
|
3
4
|
import * as jose from "jose";
|
|
@@ -9,6 +10,8 @@ declare function authorizationCodeRequest({
|
|
|
9
10
|
redirectURI,
|
|
10
11
|
options,
|
|
11
12
|
authentication,
|
|
13
|
+
clientAssertion,
|
|
14
|
+
tokenEndpoint,
|
|
12
15
|
deviceId,
|
|
13
16
|
headers,
|
|
14
17
|
additionalParams,
|
|
@@ -19,7 +22,9 @@ declare function authorizationCodeRequest({
|
|
|
19
22
|
options: AwaitableFunction<Partial<ProviderOptions>>;
|
|
20
23
|
codeVerifier?: string | undefined;
|
|
21
24
|
deviceId?: string | undefined;
|
|
22
|
-
authentication?: ("basic" | "post") | undefined;
|
|
25
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
26
|
+
clientAssertion?: ClientAssertionConfig | undefined; /** Token endpoint URL. Used as the JWT `aud` claim when signing assertions. */
|
|
27
|
+
tokenEndpoint?: string | undefined;
|
|
23
28
|
headers?: Record<string, string> | undefined;
|
|
24
29
|
additionalParams?: Record<string, string> | undefined;
|
|
25
30
|
resource?: (string | string[]) | undefined;
|
|
@@ -46,7 +51,7 @@ declare function createAuthorizationCodeRequest({
|
|
|
46
51
|
options: Partial<ProviderOptions>;
|
|
47
52
|
codeVerifier?: string | undefined;
|
|
48
53
|
deviceId?: string | undefined;
|
|
49
|
-
authentication?: ("basic" | "post") | undefined;
|
|
54
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
50
55
|
headers?: Record<string, string> | undefined;
|
|
51
56
|
additionalParams?: Record<string, string> | undefined;
|
|
52
57
|
resource?: (string | string[]) | undefined;
|
|
@@ -61,6 +66,7 @@ declare function validateAuthorizationCode({
|
|
|
61
66
|
options,
|
|
62
67
|
tokenEndpoint,
|
|
63
68
|
authentication,
|
|
69
|
+
clientAssertion,
|
|
64
70
|
deviceId,
|
|
65
71
|
headers,
|
|
66
72
|
additionalParams,
|
|
@@ -72,7 +78,8 @@ declare function validateAuthorizationCode({
|
|
|
72
78
|
codeVerifier?: string | undefined;
|
|
73
79
|
deviceId?: string | undefined;
|
|
74
80
|
tokenEndpoint: string;
|
|
75
|
-
authentication?: ("basic" | "post") | undefined;
|
|
81
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
82
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
76
83
|
headers?: Record<string, string> | undefined;
|
|
77
84
|
additionalParams?: Record<string, string> | undefined;
|
|
78
85
|
resource?: (string | string[]) | undefined;
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
+
import { resolveAssertionParams } from "./client-assertion.mjs";
|
|
1
2
|
import { getOAuth2Tokens } from "./utils.mjs";
|
|
3
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
2
4
|
import { base64 } from "@better-auth/utils/base64";
|
|
3
5
|
import { betterFetch } from "@better-fetch/fetch";
|
|
4
|
-
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
5
6
|
//#region src/oauth2/validate-authorization-code.ts
|
|
6
|
-
async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, deviceId, headers, additionalParams = {}, resource }) {
|
|
7
|
+
async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, clientAssertion, tokenEndpoint, deviceId, headers, additionalParams = {}, resource }) {
|
|
7
8
|
options = typeof options === "function" ? await options() : options;
|
|
9
|
+
if (authentication === "private_key_jwt") {
|
|
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
|
+
}
|
|
8
21
|
return createAuthorizationCodeRequest({
|
|
9
22
|
code,
|
|
10
23
|
codeVerifier,
|
|
@@ -35,13 +48,11 @@ function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, optio
|
|
|
35
48
|
body.set("redirect_uri", options.redirectURI || redirectURI);
|
|
36
49
|
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
37
50
|
else for (const _resource of resource) body.append("resource", _resource);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} else {
|
|
42
|
-
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
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 {
|
|
43
54
|
body.set("client_id", primaryClientId);
|
|
44
|
-
if (options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
55
|
+
if (authentication !== "private_key_jwt" && options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
45
56
|
}
|
|
46
57
|
for (const [key, value] of Object.entries(additionalParams)) if (!body.has(key)) body.append(key, value);
|
|
47
58
|
return {
|
|
@@ -49,13 +60,15 @@ function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, optio
|
|
|
49
60
|
headers: requestHeaders
|
|
50
61
|
};
|
|
51
62
|
}
|
|
52
|
-
async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, deviceId, headers, additionalParams = {}, resource }) {
|
|
63
|
+
async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, clientAssertion, deviceId, headers, additionalParams = {}, resource }) {
|
|
53
64
|
const { body, headers: requestHeaders } = await authorizationCodeRequest({
|
|
54
65
|
code,
|
|
55
66
|
codeVerifier,
|
|
56
67
|
redirectURI,
|
|
57
68
|
options,
|
|
58
69
|
authentication,
|
|
70
|
+
clientAssertion,
|
|
71
|
+
tokenEndpoint,
|
|
59
72
|
deviceId,
|
|
60
73
|
headers,
|
|
61
74
|
additionalParams,
|
package/dist/oauth2/verify.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger } from "../env/logger.mjs";
|
|
2
2
|
import { APIError } from "better-call";
|
|
3
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
4
3
|
import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
4
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
5
5
|
//#region src/oauth2/verify.ts
|
|
6
6
|
/** Last fetched jwks used locally in getJwks @internal */
|
|
7
7
|
let jwks;
|
|
@@ -2,8 +2,8 @@ import { APIError } from "../error/index.mjs";
|
|
|
2
2
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
3
3
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
4
4
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
5
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
6
5
|
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
6
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
7
7
|
//#region src/social-providers/apple.ts
|
|
8
8
|
const apple = (options) => {
|
|
9
9
|
const tokenEndpoint = "https://appleid.apple.com/auth/token";
|
|
@@ -3,8 +3,8 @@ import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
5
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
6
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
7
6
|
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
7
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
8
8
|
//#region src/social-providers/cognito.ts
|
|
9
9
|
const cognito = (options) => {
|
|
10
10
|
if (!options.domain || !options.region || !options.userPoolId) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
2
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
3
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
5
4
|
import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
|
|
5
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
6
6
|
//#region src/social-providers/facebook.ts
|
|
7
7
|
const facebook = (options) => {
|
|
8
8
|
return {
|
|
@@ -3,8 +3,8 @@ import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
5
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
6
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
7
6
|
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
7
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
8
8
|
//#region src/social-providers/google.ts
|
|
9
9
|
const google = (options) => {
|
|
10
10
|
return {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
2
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
3
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
5
4
|
import { decodeJwt } from "jose";
|
|
5
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
6
6
|
//#region src/social-providers/line.ts
|
|
7
7
|
/**
|
|
8
8
|
* LINE Login v2.1
|
|
@@ -3,9 +3,9 @@ import { APIError } from "../error/index.mjs";
|
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
5
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
6
|
+
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
6
7
|
import { base64 } from "@better-auth/utils/base64";
|
|
7
8
|
import { betterFetch } from "@better-fetch/fetch";
|
|
8
|
-
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
9
9
|
//#region src/social-providers/microsoft-entra-id.ts
|
|
10
10
|
const microsoft = (options) => {
|
|
11
11
|
const tenant = options.tenantId || "common";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { logger } from "../env/logger.mjs";
|
|
2
2
|
import { BetterAuthError } from "../error/index.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
|
+
import { decodeJwt } from "jose";
|
|
4
5
|
import { base64 } from "@better-auth/utils/base64";
|
|
5
6
|
import { betterFetch } from "@better-fetch/fetch";
|
|
6
|
-
import { decodeJwt } from "jose";
|
|
7
7
|
//#region src/social-providers/paypal.ts
|
|
8
8
|
const paypal = (options) => {
|
|
9
9
|
const isSandbox = (options.environment || "sandbox") === "sandbox";
|
package/package.json
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { JWTHeaderParameters } from "jose";
|
|
2
|
+
import { importJWK, importPKCS8, SignJWT } from "jose";
|
|
3
|
+
|
|
4
|
+
/** Asymmetric signing algorithms compatible with private_key_jwt (RFC 7523). */
|
|
5
|
+
export const ASSERTION_SIGNING_ALGORITHMS = [
|
|
6
|
+
"RS256",
|
|
7
|
+
"RS384",
|
|
8
|
+
"RS512",
|
|
9
|
+
"PS256",
|
|
10
|
+
"PS384",
|
|
11
|
+
"PS512",
|
|
12
|
+
"ES256",
|
|
13
|
+
"ES384",
|
|
14
|
+
"ES512",
|
|
15
|
+
"EdDSA",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type AssertionSigningAlgorithm =
|
|
19
|
+
(typeof ASSERTION_SIGNING_ALGORITHMS)[number];
|
|
20
|
+
|
|
21
|
+
export const CLIENT_ASSERTION_TYPE =
|
|
22
|
+
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
23
|
+
|
|
24
|
+
export interface ClientAssertionConfig {
|
|
25
|
+
/** Pre-signed JWT assertion string. If provided, signing is skipped. */
|
|
26
|
+
assertion?: string;
|
|
27
|
+
/** Private key in JWK format for signing. */
|
|
28
|
+
privateKeyJwk?: JsonWebKey;
|
|
29
|
+
/** Private key in PKCS#8 PEM format for signing. */
|
|
30
|
+
privateKeyPem?: string;
|
|
31
|
+
/** Key ID to include in the JWT header. */
|
|
32
|
+
kid?: string;
|
|
33
|
+
/** Asymmetric signing algorithm. Symmetric algorithms (HS256) and "none" are not allowed. @default "RS256" */
|
|
34
|
+
algorithm?: AssertionSigningAlgorithm;
|
|
35
|
+
/** Token endpoint URL (used as the JWT `aud` claim). */
|
|
36
|
+
tokenEndpoint?: string;
|
|
37
|
+
/** Assertion lifetime in seconds. @default 120 */
|
|
38
|
+
expiresIn?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Signs an RFC 7523 client assertion JWT for `private_key_jwt` authentication.
|
|
43
|
+
*
|
|
44
|
+
* The JWT contains: iss=clientId, sub=clientId, aud=tokenEndpoint,
|
|
45
|
+
* exp=now+120s, jti=unique, iat=now.
|
|
46
|
+
*/
|
|
47
|
+
export async function signClientAssertion({
|
|
48
|
+
clientId,
|
|
49
|
+
tokenEndpoint,
|
|
50
|
+
privateKeyJwk,
|
|
51
|
+
privateKeyPem,
|
|
52
|
+
kid,
|
|
53
|
+
algorithm,
|
|
54
|
+
expiresIn = 120,
|
|
55
|
+
}: {
|
|
56
|
+
clientId: string;
|
|
57
|
+
tokenEndpoint: string;
|
|
58
|
+
privateKeyJwk?: JsonWebKey;
|
|
59
|
+
privateKeyPem?: string;
|
|
60
|
+
kid?: string;
|
|
61
|
+
algorithm?: AssertionSigningAlgorithm;
|
|
62
|
+
expiresIn?: number;
|
|
63
|
+
}): Promise<string> {
|
|
64
|
+
// Fall back to JWK-embedded kid/alg when not explicitly provided (RFC 7517).
|
|
65
|
+
// JsonWebKey includes alg but not kid; access kid via index.
|
|
66
|
+
const jwk = privateKeyJwk as Record<string, unknown> | undefined;
|
|
67
|
+
const resolvedKid = kid ?? (jwk?.kid as string | undefined);
|
|
68
|
+
const resolvedAlg =
|
|
69
|
+
algorithm ??
|
|
70
|
+
(privateKeyJwk?.alg as AssertionSigningAlgorithm | undefined) ??
|
|
71
|
+
"RS256";
|
|
72
|
+
|
|
73
|
+
let key: Awaited<ReturnType<typeof importJWK>>;
|
|
74
|
+
if (privateKeyJwk) {
|
|
75
|
+
key = await importJWK(privateKeyJwk, resolvedAlg);
|
|
76
|
+
} else if (privateKeyPem) {
|
|
77
|
+
key = await importPKCS8(privateKeyPem, resolvedAlg);
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"private_key_jwt requires either privateKeyJwk or privateKeyPem",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const now = Math.floor(Date.now() / 1000);
|
|
85
|
+
const jti = crypto.randomUUID();
|
|
86
|
+
|
|
87
|
+
const header: JWTHeaderParameters = { alg: resolvedAlg, typ: "JWT" };
|
|
88
|
+
if (resolvedKid) header.kid = resolvedKid;
|
|
89
|
+
|
|
90
|
+
return new SignJWT({})
|
|
91
|
+
.setProtectedHeader(header)
|
|
92
|
+
.setIssuer(clientId)
|
|
93
|
+
.setSubject(clientId)
|
|
94
|
+
.setAudience(tokenEndpoint)
|
|
95
|
+
.setIssuedAt(now)
|
|
96
|
+
.setExpirationTime(now + expiresIn)
|
|
97
|
+
.setJti(jti)
|
|
98
|
+
.sign(key);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolves a ClientAssertionConfig into `client_assertion` + `client_assertion_type`
|
|
103
|
+
* params for injection into a token request body.
|
|
104
|
+
*/
|
|
105
|
+
export async function resolveAssertionParams({
|
|
106
|
+
clientAssertion,
|
|
107
|
+
clientId,
|
|
108
|
+
tokenEndpoint,
|
|
109
|
+
}: {
|
|
110
|
+
clientAssertion: ClientAssertionConfig;
|
|
111
|
+
clientId: string;
|
|
112
|
+
tokenEndpoint?: string;
|
|
113
|
+
}): Promise<Record<string, string>> {
|
|
114
|
+
let assertion = clientAssertion.assertion;
|
|
115
|
+
if (!assertion) {
|
|
116
|
+
const audEndpoint = tokenEndpoint ?? clientAssertion.tokenEndpoint;
|
|
117
|
+
if (!audEndpoint) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"private_key_jwt requires a tokenEndpoint for the JWT audience claim",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
assertion = await signClientAssertion({
|
|
123
|
+
clientId,
|
|
124
|
+
tokenEndpoint: audEndpoint,
|
|
125
|
+
privateKeyJwk: clientAssertion.privateKeyJwk,
|
|
126
|
+
privateKeyPem: clientAssertion.privateKeyPem,
|
|
127
|
+
kid: clientAssertion.kid,
|
|
128
|
+
algorithm: clientAssertion.algorithm,
|
|
129
|
+
expiresIn: clientAssertion.expiresIn,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
client_assertion: assertion,
|
|
134
|
+
client_assertion_type: CLIENT_ASSERTION_TYPE,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -1,25 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { base64 } from "@better-auth/utils/base64";
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
3
|
import type { AwaitableFunction } from "../types";
|
|
4
|
+
import type { ClientAssertionConfig } from "./client-assertion";
|
|
5
|
+
import { resolveAssertionParams } from "./client-assertion";
|
|
4
6
|
import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
|
|
5
7
|
|
|
6
8
|
export async function clientCredentialsTokenRequest({
|
|
7
9
|
options,
|
|
8
10
|
scope,
|
|
9
11
|
authentication,
|
|
12
|
+
clientAssertion,
|
|
13
|
+
tokenEndpoint,
|
|
10
14
|
resource,
|
|
11
15
|
}: {
|
|
12
|
-
options: AwaitableFunction<ProviderOptions
|
|
16
|
+
options: AwaitableFunction<ProviderOptions>;
|
|
13
17
|
scope?: string | undefined;
|
|
14
|
-
authentication?: ("basic" | "post") | undefined;
|
|
18
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
19
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
20
|
+
/** Token endpoint URL. Used as the JWT `aud` claim when signing assertions. */
|
|
21
|
+
tokenEndpoint?: string | undefined;
|
|
15
22
|
resource?: (string | string[]) | undefined;
|
|
16
23
|
}) {
|
|
17
24
|
options = typeof options === "function" ? await options() : options;
|
|
25
|
+
|
|
26
|
+
let extraParams: Record<string, string> | undefined;
|
|
27
|
+
if (authentication === "private_key_jwt") {
|
|
28
|
+
if (!clientAssertion) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"private_key_jwt authentication requires a clientAssertion configuration",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
34
|
+
? options.clientId[0]
|
|
35
|
+
: options.clientId;
|
|
36
|
+
extraParams = await resolveAssertionParams({
|
|
37
|
+
clientAssertion,
|
|
38
|
+
clientId: primaryClientId,
|
|
39
|
+
tokenEndpoint,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
18
43
|
return createClientCredentialsTokenRequest({
|
|
19
44
|
options,
|
|
20
45
|
scope,
|
|
21
46
|
authentication,
|
|
22
47
|
resource,
|
|
48
|
+
extraParams,
|
|
23
49
|
});
|
|
24
50
|
}
|
|
25
51
|
|
|
@@ -31,11 +57,13 @@ export function createClientCredentialsTokenRequest({
|
|
|
31
57
|
scope,
|
|
32
58
|
authentication,
|
|
33
59
|
resource,
|
|
60
|
+
extraParams,
|
|
34
61
|
}: {
|
|
35
|
-
options: ProviderOptions
|
|
62
|
+
options: ProviderOptions;
|
|
36
63
|
scope?: string | undefined;
|
|
37
|
-
authentication?: ("basic" | "post") | undefined;
|
|
64
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
38
65
|
resource?: (string | string[]) | undefined;
|
|
66
|
+
extraParams?: Record<string, string> | undefined;
|
|
39
67
|
}) {
|
|
40
68
|
const body = new URLSearchParams();
|
|
41
69
|
const headers: Record<string, any> = {
|
|
@@ -54,20 +82,25 @@ export function createClientCredentialsTokenRequest({
|
|
|
54
82
|
}
|
|
55
83
|
}
|
|
56
84
|
}
|
|
85
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
86
|
+
? options.clientId[0]
|
|
87
|
+
: options.clientId;
|
|
57
88
|
if (authentication === "basic") {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
: options.clientId;
|
|
61
|
-
const encodedCredentials = base64Url.encode(
|
|
62
|
-
`${primaryClientId}:${options.clientSecret}`,
|
|
89
|
+
const encodedCredentials = base64.encode(
|
|
90
|
+
`${primaryClientId}:${options.clientSecret ?? ""}`,
|
|
63
91
|
);
|
|
64
92
|
headers["authorization"] = `Basic ${encodedCredentials}`;
|
|
65
93
|
} else {
|
|
66
|
-
const primaryClientId = Array.isArray(options.clientId)
|
|
67
|
-
? options.clientId[0]
|
|
68
|
-
: options.clientId;
|
|
69
94
|
body.set("client_id", primaryClientId);
|
|
70
|
-
|
|
95
|
+
if (authentication !== "private_key_jwt" && options.clientSecret) {
|
|
96
|
+
body.set("client_secret", options.clientSecret);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (extraParams) {
|
|
101
|
+
for (const [key, value] of Object.entries(extraParams)) {
|
|
102
|
+
if (!body.has(key)) body.append(key, value);
|
|
103
|
+
}
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
return {
|
|
@@ -81,18 +114,22 @@ export async function clientCredentialsToken({
|
|
|
81
114
|
tokenEndpoint,
|
|
82
115
|
scope,
|
|
83
116
|
authentication,
|
|
117
|
+
clientAssertion,
|
|
84
118
|
resource,
|
|
85
119
|
}: {
|
|
86
|
-
options: AwaitableFunction<ProviderOptions
|
|
120
|
+
options: AwaitableFunction<ProviderOptions>;
|
|
87
121
|
tokenEndpoint: string;
|
|
88
122
|
scope: string;
|
|
89
|
-
authentication?: ("basic" | "post") | undefined;
|
|
123
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
124
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
90
125
|
resource?: (string | string[]) | undefined;
|
|
91
126
|
}): Promise<OAuth2Tokens> {
|
|
92
127
|
const { body, headers } = await clientCredentialsTokenRequest({
|
|
93
128
|
options,
|
|
94
129
|
scope,
|
|
95
130
|
authentication,
|
|
131
|
+
clientAssertion,
|
|
132
|
+
tokenEndpoint,
|
|
96
133
|
resource,
|
|
97
134
|
});
|
|
98
135
|
|
package/src/oauth2/index.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
AssertionSigningAlgorithm,
|
|
3
|
+
ClientAssertionConfig,
|
|
4
|
+
} from "./client-assertion";
|
|
5
|
+
export {
|
|
6
|
+
ASSERTION_SIGNING_ALGORITHMS,
|
|
7
|
+
CLIENT_ASSERTION_TYPE,
|
|
8
|
+
resolveAssertionParams,
|
|
9
|
+
signClientAssertion,
|
|
10
|
+
} from "./client-assertion";
|
|
1
11
|
export {
|
|
2
12
|
clientCredentialsToken,
|
|
3
13
|
clientCredentialsTokenRequest,
|
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
import { base64 } from "@better-auth/utils/base64";
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
3
|
import type { AwaitableFunction } from "../types";
|
|
4
|
+
import type { ClientAssertionConfig } from "./client-assertion";
|
|
5
|
+
import { resolveAssertionParams } from "./client-assertion";
|
|
4
6
|
import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
|
|
5
7
|
|
|
6
8
|
export async function refreshAccessTokenRequest({
|
|
7
9
|
refreshToken,
|
|
8
10
|
options,
|
|
9
11
|
authentication,
|
|
12
|
+
clientAssertion,
|
|
13
|
+
tokenEndpoint,
|
|
10
14
|
extraParams,
|
|
11
15
|
resource,
|
|
12
16
|
}: {
|
|
13
17
|
refreshToken: string;
|
|
14
18
|
options: AwaitableFunction<Partial<ProviderOptions>>;
|
|
15
|
-
authentication?: ("basic" | "post") | undefined;
|
|
19
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
20
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
21
|
+
/** Token endpoint URL. Used as the JWT `aud` claim when signing assertions. */
|
|
22
|
+
tokenEndpoint?: string | undefined;
|
|
16
23
|
extraParams?: Record<string, string> | undefined;
|
|
17
24
|
resource?: (string | string[]) | undefined;
|
|
18
25
|
}) {
|
|
19
26
|
options = typeof options === "function" ? await options() : options;
|
|
27
|
+
|
|
28
|
+
if (authentication === "private_key_jwt") {
|
|
29
|
+
if (!clientAssertion) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"private_key_jwt authentication requires a clientAssertion configuration",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
35
|
+
? options.clientId[0]
|
|
36
|
+
: options.clientId;
|
|
37
|
+
const assertionParams = await resolveAssertionParams({
|
|
38
|
+
clientAssertion,
|
|
39
|
+
clientId: primaryClientId,
|
|
40
|
+
tokenEndpoint,
|
|
41
|
+
});
|
|
42
|
+
extraParams = { ...extraParams, ...assertionParams };
|
|
43
|
+
}
|
|
44
|
+
|
|
20
45
|
return createRefreshAccessTokenRequest({
|
|
21
46
|
refreshToken,
|
|
22
47
|
options,
|
|
@@ -38,7 +63,7 @@ export function createRefreshAccessTokenRequest({
|
|
|
38
63
|
}: {
|
|
39
64
|
refreshToken: string;
|
|
40
65
|
options: ProviderOptions;
|
|
41
|
-
authentication?: ("basic" | "post") | undefined;
|
|
66
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
42
67
|
extraParams?: Record<string, string> | undefined;
|
|
43
68
|
resource?: (string | string[]) | undefined;
|
|
44
69
|
}) {
|
|
@@ -50,12 +75,10 @@ export function createRefreshAccessTokenRequest({
|
|
|
50
75
|
|
|
51
76
|
body.set("grant_type", "refresh_token");
|
|
52
77
|
body.set("refresh_token", refreshToken);
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
79
|
+
? options.clientId[0]
|
|
80
|
+
: options.clientId;
|
|
55
81
|
if (authentication === "basic") {
|
|
56
|
-
const primaryClientId = Array.isArray(options.clientId)
|
|
57
|
-
? options.clientId[0]
|
|
58
|
-
: options.clientId;
|
|
59
82
|
if (primaryClientId) {
|
|
60
83
|
headers["authorization"] =
|
|
61
84
|
"Basic " +
|
|
@@ -65,11 +88,8 @@ export function createRefreshAccessTokenRequest({
|
|
|
65
88
|
"Basic " + base64.encode(`:${options.clientSecret ?? ""}`);
|
|
66
89
|
}
|
|
67
90
|
} else {
|
|
68
|
-
const primaryClientId = Array.isArray(options.clientId)
|
|
69
|
-
? options.clientId[0]
|
|
70
|
-
: options.clientId;
|
|
71
91
|
body.set("client_id", primaryClientId);
|
|
72
|
-
if (options.clientSecret) {
|
|
92
|
+
if (authentication !== "private_key_jwt" && options.clientSecret) {
|
|
73
93
|
body.set("client_secret", options.clientSecret);
|
|
74
94
|
}
|
|
75
95
|
}
|
|
@@ -100,18 +120,22 @@ export async function refreshAccessToken({
|
|
|
100
120
|
options,
|
|
101
121
|
tokenEndpoint,
|
|
102
122
|
authentication,
|
|
123
|
+
clientAssertion,
|
|
103
124
|
extraParams,
|
|
104
125
|
}: {
|
|
105
126
|
refreshToken: string;
|
|
106
127
|
options: Partial<ProviderOptions>;
|
|
107
128
|
tokenEndpoint: string;
|
|
108
|
-
authentication?: ("basic" | "post") | undefined;
|
|
129
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
130
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
109
131
|
extraParams?: Record<string, string> | undefined;
|
|
110
132
|
}): Promise<OAuth2Tokens> {
|
|
111
|
-
const { body, headers } = await
|
|
133
|
+
const { body, headers } = await refreshAccessTokenRequest({
|
|
112
134
|
refreshToken,
|
|
113
135
|
options,
|
|
114
136
|
authentication,
|
|
137
|
+
clientAssertion,
|
|
138
|
+
tokenEndpoint,
|
|
115
139
|
extraParams,
|
|
116
140
|
});
|
|
117
141
|
|
|
@@ -2,6 +2,8 @@ import { base64 } from "@better-auth/utils/base64";
|
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
3
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
4
4
|
import type { AwaitableFunction } from "../types";
|
|
5
|
+
import type { ClientAssertionConfig } from "./client-assertion";
|
|
6
|
+
import { resolveAssertionParams } from "./client-assertion";
|
|
5
7
|
import type { ProviderOptions } from "./index";
|
|
6
8
|
import { getOAuth2Tokens } from "./index";
|
|
7
9
|
|
|
@@ -11,6 +13,8 @@ export async function authorizationCodeRequest({
|
|
|
11
13
|
redirectURI,
|
|
12
14
|
options,
|
|
13
15
|
authentication,
|
|
16
|
+
clientAssertion,
|
|
17
|
+
tokenEndpoint,
|
|
14
18
|
deviceId,
|
|
15
19
|
headers,
|
|
16
20
|
additionalParams = {},
|
|
@@ -21,12 +25,33 @@ export async function authorizationCodeRequest({
|
|
|
21
25
|
options: AwaitableFunction<Partial<ProviderOptions>>;
|
|
22
26
|
codeVerifier?: string | undefined;
|
|
23
27
|
deviceId?: string | undefined;
|
|
24
|
-
authentication?: ("basic" | "post") | undefined;
|
|
28
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
29
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
30
|
+
/** Token endpoint URL. Used as the JWT `aud` claim when signing assertions. */
|
|
31
|
+
tokenEndpoint?: string | undefined;
|
|
25
32
|
headers?: Record<string, string> | undefined;
|
|
26
33
|
additionalParams?: Record<string, string> | undefined;
|
|
27
34
|
resource?: (string | string[]) | undefined;
|
|
28
35
|
}) {
|
|
29
36
|
options = typeof options === "function" ? await options() : options;
|
|
37
|
+
|
|
38
|
+
if (authentication === "private_key_jwt") {
|
|
39
|
+
if (!clientAssertion) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"private_key_jwt authentication requires a clientAssertion configuration",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
45
|
+
? options.clientId[0]
|
|
46
|
+
: options.clientId;
|
|
47
|
+
const assertionParams = await resolveAssertionParams({
|
|
48
|
+
clientAssertion,
|
|
49
|
+
clientId: primaryClientId,
|
|
50
|
+
tokenEndpoint,
|
|
51
|
+
});
|
|
52
|
+
additionalParams = { ...additionalParams, ...assertionParams };
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
return createAuthorizationCodeRequest({
|
|
31
56
|
code,
|
|
32
57
|
codeVerifier,
|
|
@@ -59,7 +84,7 @@ export function createAuthorizationCodeRequest({
|
|
|
59
84
|
options: Partial<ProviderOptions>;
|
|
60
85
|
codeVerifier?: string | undefined;
|
|
61
86
|
deviceId?: string | undefined;
|
|
62
|
-
authentication?: ("basic" | "post") | undefined;
|
|
87
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
63
88
|
headers?: Record<string, string> | undefined;
|
|
64
89
|
additionalParams?: Record<string, string> | undefined;
|
|
65
90
|
resource?: (string | string[]) | undefined;
|
|
@@ -86,22 +111,17 @@ export function createAuthorizationCodeRequest({
|
|
|
86
111
|
}
|
|
87
112
|
}
|
|
88
113
|
}
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
115
|
+
? options.clientId[0]
|
|
116
|
+
: options.clientId;
|
|
91
117
|
if (authentication === "basic") {
|
|
92
|
-
const primaryClientId = Array.isArray(options.clientId)
|
|
93
|
-
? options.clientId[0]
|
|
94
|
-
: options.clientId;
|
|
95
118
|
const encodedCredentials = base64.encode(
|
|
96
119
|
`${primaryClientId}:${options.clientSecret ?? ""}`,
|
|
97
120
|
);
|
|
98
121
|
requestHeaders["authorization"] = `Basic ${encodedCredentials}`;
|
|
99
122
|
} else {
|
|
100
|
-
const primaryClientId = Array.isArray(options.clientId)
|
|
101
|
-
? options.clientId[0]
|
|
102
|
-
: options.clientId;
|
|
103
123
|
body.set("client_id", primaryClientId);
|
|
104
|
-
if (options.clientSecret) {
|
|
124
|
+
if (authentication !== "private_key_jwt" && options.clientSecret) {
|
|
105
125
|
body.set("client_secret", options.clientSecret);
|
|
106
126
|
}
|
|
107
127
|
}
|
|
@@ -123,6 +143,7 @@ export async function validateAuthorizationCode({
|
|
|
123
143
|
options,
|
|
124
144
|
tokenEndpoint,
|
|
125
145
|
authentication,
|
|
146
|
+
clientAssertion,
|
|
126
147
|
deviceId,
|
|
127
148
|
headers,
|
|
128
149
|
additionalParams = {},
|
|
@@ -134,7 +155,8 @@ export async function validateAuthorizationCode({
|
|
|
134
155
|
codeVerifier?: string | undefined;
|
|
135
156
|
deviceId?: string | undefined;
|
|
136
157
|
tokenEndpoint: string;
|
|
137
|
-
authentication?: ("basic" | "post") | undefined;
|
|
158
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
159
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
138
160
|
headers?: Record<string, string> | undefined;
|
|
139
161
|
additionalParams?: Record<string, string> | undefined;
|
|
140
162
|
resource?: (string | string[]) | undefined;
|
|
@@ -145,6 +167,8 @@ export async function validateAuthorizationCode({
|
|
|
145
167
|
redirectURI,
|
|
146
168
|
options,
|
|
147
169
|
authentication,
|
|
170
|
+
clientAssertion,
|
|
171
|
+
tokenEndpoint,
|
|
148
172
|
deviceId,
|
|
149
173
|
headers,
|
|
150
174
|
additionalParams,
|