mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 23:53:17 -05:00
142 lines
3.6 KiB
TypeScript
142 lines
3.6 KiB
TypeScript
import { base64 } from "@better-auth/utils/base64";
|
|
import { betterFetch } from "@better-fetch/fetch";
|
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
import type { ProviderOptions } from "./index";
|
|
import { getOAuth2Tokens } from "./index";
|
|
|
|
export function createAuthorizationCodeRequest({
|
|
code,
|
|
codeVerifier,
|
|
redirectURI,
|
|
options,
|
|
authentication,
|
|
deviceId,
|
|
headers,
|
|
additionalParams = {},
|
|
resource,
|
|
}: {
|
|
code: string;
|
|
redirectURI: string;
|
|
options: Partial<ProviderOptions>;
|
|
codeVerifier?: string | undefined;
|
|
deviceId?: string | undefined;
|
|
authentication?: ("basic" | "post") | undefined;
|
|
headers?: Record<string, string> | undefined;
|
|
additionalParams?: Record<string, string> | undefined;
|
|
resource?: (string | string[]) | undefined;
|
|
}) {
|
|
const body = new URLSearchParams();
|
|
const requestHeaders: Record<string, any> = {
|
|
"content-type": "application/x-www-form-urlencoded",
|
|
accept: "application/json",
|
|
...headers,
|
|
};
|
|
body.set("grant_type", "authorization_code");
|
|
body.set("code", code);
|
|
codeVerifier && body.set("code_verifier", codeVerifier);
|
|
options.clientKey && body.set("client_key", options.clientKey);
|
|
deviceId && body.set("device_id", deviceId);
|
|
body.set("redirect_uri", options.redirectURI || redirectURI);
|
|
if (resource) {
|
|
if (typeof resource === "string") {
|
|
body.append("resource", resource);
|
|
} else {
|
|
for (const _resource of resource) {
|
|
body.append("resource", _resource);
|
|
}
|
|
}
|
|
}
|
|
// Use standard Base64 encoding for HTTP Basic Auth (OAuth2 spec, RFC 7617)
|
|
// Fixes compatibility with providers like Notion, Twitter, etc.
|
|
if (authentication === "basic") {
|
|
const primaryClientId = Array.isArray(options.clientId)
|
|
? options.clientId[0]
|
|
: options.clientId;
|
|
const encodedCredentials = base64.encode(
|
|
`${primaryClientId}:${options.clientSecret ?? ""}`,
|
|
);
|
|
requestHeaders["authorization"] = `Basic ${encodedCredentials}`;
|
|
} else {
|
|
const primaryClientId = Array.isArray(options.clientId)
|
|
? options.clientId[0]
|
|
: options.clientId;
|
|
body.set("client_id", primaryClientId);
|
|
if (options.clientSecret) {
|
|
body.set("client_secret", options.clientSecret);
|
|
}
|
|
}
|
|
|
|
for (const [key, value] of Object.entries(additionalParams)) {
|
|
if (!body.has(key)) body.append(key, value);
|
|
}
|
|
|
|
return {
|
|
body,
|
|
headers: requestHeaders,
|
|
};
|
|
}
|
|
|
|
export async function validateAuthorizationCode({
|
|
code,
|
|
codeVerifier,
|
|
redirectURI,
|
|
options,
|
|
tokenEndpoint,
|
|
authentication,
|
|
deviceId,
|
|
headers,
|
|
additionalParams = {},
|
|
resource,
|
|
}: {
|
|
code: string;
|
|
redirectURI: string;
|
|
options: Partial<ProviderOptions>;
|
|
codeVerifier?: string | undefined;
|
|
deviceId?: string | undefined;
|
|
tokenEndpoint: string;
|
|
authentication?: ("basic" | "post") | undefined;
|
|
headers?: Record<string, string> | undefined;
|
|
additionalParams?: Record<string, string> | undefined;
|
|
resource?: (string | string[]) | undefined;
|
|
}) {
|
|
const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
|
|
code,
|
|
codeVerifier,
|
|
redirectURI,
|
|
options,
|
|
authentication,
|
|
deviceId,
|
|
headers,
|
|
additionalParams,
|
|
resource,
|
|
});
|
|
|
|
const { data, error } = await betterFetch<object>(tokenEndpoint, {
|
|
method: "POST",
|
|
body: body,
|
|
headers: requestHeaders,
|
|
});
|
|
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
const tokens = getOAuth2Tokens(data);
|
|
return tokens;
|
|
}
|
|
|
|
export async function validateToken(
|
|
token: string,
|
|
jwksEndpoint: string,
|
|
options?: {
|
|
audience?: string | string[];
|
|
issuer?: string | string[];
|
|
},
|
|
) {
|
|
const jwks = createRemoteJWKSet(new URL(jwksEndpoint));
|
|
const verified = await jwtVerify(token, jwks, {
|
|
audience: options?.audience,
|
|
issuer: options?.issuer,
|
|
});
|
|
return verified;
|
|
}
|