@auth/core
Version:
Authentication for the Web.
95 lines (94 loc) • 4.11 kB
JavaScript
import * as checks from "../callback/oauth/checks.js";
import * as o from "oauth4webapi";
import { customFetch } from "../../symbols.js";
/**
* Generates an authorization/request token URL.
*
* [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/)
*/
export async function getAuthorizationUrl(query, options) {
const { logger, provider } = options;
let url = provider.authorization?.url;
let as;
// Falls back to authjs.dev if the user only passed params
if (!url || url.host === "authjs.dev") {
// If url is undefined, we assume that issuer is always defined
// We check this in assert.ts
const issuer = new URL(provider.issuer);
const discoveryResponse = await o.discoveryRequest(issuer, {
[]: provider[customFetch],
// TODO: move away from allowing insecure HTTP requests
[]: true,
});
const as = await o
.processDiscoveryResponse(issuer, discoveryResponse)
.catch((error) => {
if (!(error instanceof TypeError) || error.message !== "Invalid URL")
throw error;
throw new TypeError(`Discovery request responded with an invalid issuer. expected: ${issuer}`);
});
if (!as.authorization_endpoint) {
throw new TypeError("Authorization server did not provide an authorization endpoint.");
}
url = new URL(as.authorization_endpoint);
}
const authParams = url.searchParams;
let redirect_uri = provider.callbackUrl;
let data;
if (!options.isOnRedirectProxy && provider.redirectProxyUrl) {
redirect_uri = provider.redirectProxyUrl;
data = provider.callbackUrl;
logger.debug("using redirect proxy", { redirect_uri, data });
}
const params = Object.assign({
response_type: "code",
// clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it?
client_id: provider.clientId,
redirect_uri,
// @ts-expect-error TODO:
...provider.authorization?.params,
}, Object.fromEntries(provider.authorization?.url.searchParams ?? []), query);
for (const k in params)
authParams.set(k, params[k]);
const cookies = [];
if (
// Otherwise "POST /redirect_uri" wouldn't include the cookies
provider.authorization?.url.searchParams.get("response_mode") ===
"form_post") {
options.cookies.state.options.sameSite = "none";
options.cookies.state.options.secure = true;
options.cookies.nonce.options.sameSite = "none";
options.cookies.nonce.options.secure = true;
}
const state = await checks.state.create(options, data);
if (state) {
authParams.set("state", state.value);
cookies.push(state.cookie);
}
if (provider.checks?.includes("pkce")) {
if (as && !as.code_challenge_methods_supported?.includes("S256")) {
// We assume S256 PKCE support, if the server does not advertise that,
// a random `nonce` must be used for CSRF protection.
if (provider.type === "oidc")
provider.checks = ["nonce"];
}
else {
const { value, cookie } = await checks.pkce.create(options);
authParams.set("code_challenge", value);
authParams.set("code_challenge_method", "S256");
cookies.push(cookie);
}
}
const nonce = await checks.nonce.create(options);
if (nonce) {
authParams.set("nonce", nonce.value);
cookies.push(nonce.cookie);
}
// TODO: This does not work in normalizeOAuth because authorization endpoint can come from discovery
// Need to make normalizeOAuth async
if (provider.type === "oidc" && !url.searchParams.has("scope")) {
url.searchParams.set("scope", "openid profile email");
}
logger.debug("authorization url is ready", { url, cookies, provider });
return { redirect: url.toString(), cookies };
}