Repository: better-auth/better-auth
File: packages/better-auth/src/api/middlewares/origin-check.ts (TypeScript)

Code before:
import type { GenericEndpointContext } from "@better-auth/core";
import { createAuthMiddleware } from "@better-auth/core/api";
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
import { matchesOriginPattern } from "../../auth/trusted-origins";

/**
 * Checks if CSRF should be skipped for backward compatibility.
 * Previously, disableOriginCheck also disabled CSRF checks.
 * This maintains that behavior when disableCSRFCheck isn't explicitly set.
 */
function shouldSkipCSRFForBackwardCompat(ctx: GenericEndpointContext): boolean {
	return (
		ctx.context.skipOriginCheck &&
		ctx.context.options.advanced?.disableCSRFCheck === undefined
	);
}

/**
 * Logs deprecation warning for users relying on coupled behavior.
 * Only logs if user explicitly set disableOriginCheck (not test environment default).
 */
function logBackwardCompatWarning(ctx: GenericEndpointContext): void {
	if (ctx.context.options.advanced?.disableOriginCheck === true) {
		ctx.context.logger?.warn(
			"[Deprecation] disableOriginCheck: true currently also disables CSRF checks. " +
				"In a future version, disableOriginCheck will ONLY disable URL validation. " +
				"To keep CSRF disabled, add disableCSRFCheck: true to your config.",
		);
	}
}

/**
 * A middleware to validate callbackURL and origin against trustedOrigins.
 * Also handles CSRF protection using Fetch Metadata for first-login scenarios.
 */
export const originCheckMiddleware = createAuthMiddleware(async (ctx) => {
	// Skip origin check for GET, OPTIONS, HEAD requests - we don't mutate state here.
	if (
		ctx.request?.method === "GET" ||
		ctx.request?.method === "OPTIONS" ||
		ctx.request?.method === "HEAD" ||
		!ctx.request
	) {
		return;
	}
	await validateOrigin(ctx);

	if (ctx.context.skipOriginCheck) {
		return;

Review comment (suggestion):
We should only emit this warning once. I already added `deprecated` utils in the core package. We can use that I think

Code after:
import type { GenericEndpointContext } from "@better-auth/core";
import { createAuthMiddleware } from "@better-auth/core/api";
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
import { deprecate } from "@better-auth/core/utils";
import { matchesOriginPattern } from "../../auth/trusted-origins";

/**
 * Checks if CSRF should be skipped for backward compatibility.
 * Previously, disableOriginCheck also disabled CSRF checks.
 * This maintains that behavior when disableCSRFCheck isn't explicitly set.
 */
function shouldSkipCSRFForBackwardCompat(ctx: GenericEndpointContext): boolean {
	return (
		ctx.context.skipOriginCheck &&
		ctx.context.options.advanced?.disableCSRFCheck === undefined
	);
}

/**
 * Logs deprecation warning for users relying on coupled behavior.
 * Only logs if user explicitly set disableOriginCheck (not test environment default).
 */
const logBackwardCompatWarning = deprecate(
	function logBackwardCompatWarning() {},
	"disableOriginCheck: true currently also disables CSRF checks. " +
		"In a future version, disableOriginCheck will ONLY disable URL validation. " +
		"To keep CSRF disabled, add disableCSRFCheck: true to your config.",
);

/**
 * A middleware to validate callbackURL and origin against trustedOrigins.
 * Also handles CSRF protection using Fetch Metadata for first-login scenarios.
 */
export const originCheckMiddleware = createAuthMiddleware(async (ctx) => {
	// Skip origin check for GET, OPTIONS, HEAD requests - we don't mutate state here.
	if (
		ctx.request?.method === "GET" ||
		ctx.request?.method === "OPTIONS" ||
		ctx.request?.method === "HEAD" ||
		!ctx.request
	) {
		return;
	}
	await validateOrigin(ctx);

	if (ctx.context.skipOriginCheck) {
		return;
	}