mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 23:53:17 -05:00
fix(oauth-provider): avoid fetch redirect CORS after login (#8519)
This commit is contained in:
committed by
Alex Yang
parent
3f16e9f867
commit
c03666a5da
@@ -1,5 +1,6 @@
|
||||
import type { GenericEndpointContext } from "@better-auth/core";
|
||||
import { APIError } from "@better-auth/core/error";
|
||||
import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
|
||||
import { getSessionFromCtx } from "../../api";
|
||||
import { generateRandomString } from "../../crypto";
|
||||
import { getClient } from "./index";
|
||||
@@ -28,7 +29,7 @@ export async function authorize(
|
||||
options: OIDCOptions,
|
||||
) {
|
||||
const handleRedirect = (url: string) => {
|
||||
const fromFetch = ctx.request?.headers.get("sec-fetch-mode") === "cors";
|
||||
const fromFetch = isBrowserFetchRequest(ctx.request?.headers);
|
||||
if (fromFetch) {
|
||||
return ctx.json({
|
||||
redirect: true,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isBrowserFetchRequest } from "./fetch-metadata";
|
||||
|
||||
describe("isBrowserFetchRequest", () => {
|
||||
it("returns true for browser fetch requests", () => {
|
||||
expect(
|
||||
isBrowserFetchRequest(
|
||||
new Headers({
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for navigation requests", () => {
|
||||
expect(
|
||||
isBrowserFetchRequest(
|
||||
new Headers({
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
}),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false without fetch metadata", () => {
|
||||
expect(isBrowserFetchRequest()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isBrowserFetchRequest(headers?: Headers | null): boolean {
|
||||
return headers?.get("sec-fetch-mode") === "cors";
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { GenericEndpointContext } from "@better-auth/core";
|
||||
import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
|
||||
import { getSessionFromCtx } from "better-auth/api";
|
||||
import { generateRandomString, makeSignature } from "better-auth/crypto";
|
||||
import type { Verification } from "better-auth/db";
|
||||
@@ -40,8 +41,9 @@ export function formatErrorURL(
|
||||
}
|
||||
|
||||
export const handleRedirect = (ctx: GenericEndpointContext, uri: string) => {
|
||||
const fromFetch = isBrowserFetchRequest(ctx.request?.headers);
|
||||
const acceptJson = ctx.headers?.get("accept")?.includes("application/json");
|
||||
if (acceptJson) {
|
||||
if (fromFetch || acceptJson) {
|
||||
return {
|
||||
redirect: true,
|
||||
url: uri.toString(),
|
||||
|
||||
@@ -285,6 +285,78 @@ describe("oauth", async () => {
|
||||
expect(callbackUrl).toContain("/success");
|
||||
});
|
||||
|
||||
it("should return a JSON redirect after email sign-in in a fetch-based oauth flow", async ({
|
||||
onTestFinished,
|
||||
}) => {
|
||||
if (!oauthClient?.client_id || !oauthClient?.client_secret) {
|
||||
throw Error("beforeAll not run properly");
|
||||
}
|
||||
|
||||
const { customFetchImpl: customFetchImplRP } = await createTestInstance();
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: rpBaseUrl,
|
||||
fetchOptions: {
|
||||
customFetchImpl: customFetchImplRP,
|
||||
},
|
||||
});
|
||||
const headers = new Headers();
|
||||
const data = await client.signIn.oauth2(
|
||||
{
|
||||
providerId,
|
||||
callbackURL: "/success",
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
);
|
||||
|
||||
let loginRedirectUri = "";
|
||||
await authClient.$fetch(data.url, {
|
||||
method: "GET",
|
||||
onError(ctx) {
|
||||
loginRedirectUri = ctx.response.headers.get("Location") || "";
|
||||
},
|
||||
});
|
||||
expect(loginRedirectUri).toContain("/login");
|
||||
|
||||
vi.stubGlobal("window", {
|
||||
location: {
|
||||
href: "",
|
||||
search: new URL(loginRedirectUri, authServerBaseUrl).search,
|
||||
},
|
||||
});
|
||||
onTestFinished(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
let signInLocationHeader = "";
|
||||
const signInResponse = await authClient.signIn.email(
|
||||
{
|
||||
email: testUser.email,
|
||||
password: testUser.password,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
},
|
||||
throw: true,
|
||||
onResponse(ctx) {
|
||||
signInLocationHeader = ctx.response.headers.get("Location") || "";
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(signInLocationHeader).toBe("");
|
||||
expect(signInResponse.redirect).toBe(true);
|
||||
expect(signInResponse.url).toContain(
|
||||
`${rpBaseUrl}/api/auth/oauth2/callback/${providerId}`,
|
||||
);
|
||||
expect(signInResponse.url).not.toContain(`${authServerBaseUrl}/login`);
|
||||
});
|
||||
|
||||
it("should sign in using generic oauth discovery", async ({
|
||||
onTestFinished,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user