in packages/hub/src/lib/oauth-handle-redirect.ts [114:270]
export async function oauthHandleRedirect(opts?: {
/**
* The URL of the hub. Defaults to {@link HUB_URL}.
*/
hubUrl?: string;
/**
* The URL to analyze.
*
* @default window.location.href
*/
redirectedUrl?: string;
/**
* nonce generated by oauthLoginUrl
*
* @default localStorage.getItem("huggingface.co:oauth:nonce")
*/
nonce?: string;
/**
* codeVerifier generated by oauthLoginUrl
*
* @default localStorage.getItem("huggingface.co:oauth:code_verifier")
*/
codeVerifier?: string;
}): Promise<OAuthResult> {
if (typeof window === "undefined" && !opts?.redirectedUrl) {
throw new Error("oauthHandleRedirect is only available in the browser, unless you provide redirectedUrl");
}
if (typeof localStorage === "undefined" && (!opts?.nonce || !opts?.codeVerifier)) {
throw new Error(
"oauthHandleRedirect requires localStorage to be available, unless you provide nonce and codeVerifier"
);
}
const redirectedUrl = opts?.redirectedUrl ?? window.location.href;
const searchParams = (() => {
try {
return new URL(redirectedUrl).searchParams;
} catch (err) {
throw new Error("Failed to parse redirected URL: " + redirectedUrl);
}
})();
const [error, errorDescription] = [searchParams.get("error"), searchParams.get("error_description")];
if (error) {
throw new Error(`${error}: ${errorDescription}`);
}
const code = searchParams.get("code");
const nonce = opts?.nonce ?? localStorage.getItem("huggingface.co:oauth:nonce");
if (!code) {
throw new Error("Missing oauth code from query parameters in redirected URL: " + redirectedUrl);
}
if (!nonce) {
throw new Error("Missing oauth nonce from localStorage");
}
const codeVerifier = opts?.codeVerifier ?? localStorage.getItem("huggingface.co:oauth:code_verifier");
if (!codeVerifier) {
throw new Error("Missing oauth code_verifier from localStorage");
}
const state = searchParams.get("state");
if (!state) {
throw new Error("Missing oauth state from query parameters in redirected URL");
}
let parsedState: { nonce: string; redirectUri: string; state?: string };
try {
parsedState = JSON.parse(state);
} catch {
throw new Error("Invalid oauth state in redirected URL, unable to parse JSON: " + state);
}
if (parsedState.nonce !== nonce) {
throw new Error("Invalid oauth state in redirected URL");
}
const hubUrl = opts?.hubUrl || HUB_URL;
const openidConfigUrl = `${new URL(hubUrl).origin}/.well-known/openid-configuration`;
const openidConfigRes = await fetch(openidConfigUrl, {
headers: {
Accept: "application/json",
},
});
if (!openidConfigRes.ok) {
throw await createApiError(openidConfigRes);
}
const openidConfig: {
authorization_endpoint: string;
token_endpoint: string;
userinfo_endpoint: string;
} = await openidConfigRes.json();
const tokenRes = await fetch(openidConfig.token_endpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: parsedState.redirectUri,
code_verifier: codeVerifier,
}).toString(),
});
if (!opts?.codeVerifier) {
localStorage.removeItem("huggingface.co:oauth:code_verifier");
}
if (!opts?.nonce) {
localStorage.removeItem("huggingface.co:oauth:nonce");
}
if (!tokenRes.ok) {
throw await createApiError(tokenRes);
}
const token: {
access_token: string;
expires_in: number;
id_token: string;
// refresh_token: string;
scope: string;
token_type: string;
} = await tokenRes.json();
const accessTokenExpiresAt = new Date(Date.now() + token.expires_in * 1000);
const userInfoRes = await fetch(openidConfig.userinfo_endpoint, {
headers: {
Authorization: `Bearer ${token.access_token}`,
},
});
if (!userInfoRes.ok) {
throw await createApiError(userInfoRes);
}
const userInfo: UserInfo = await userInfoRes.json();
return {
accessToken: token.access_token,
accessTokenExpiresAt,
userInfo: userInfo,
state: parsedState.state,
scope: token.scope,
};
}