fix(GRO-545): switch OAuth state to cookie storage and add login error display
The OAuth callback was failing with "please_restart_the_process" because Better-Auth's default DB-backed state (verification table) was unreliable — the UAT hourly reset wipes all tables including verification records. Switch to cookie-based state storage so the encrypted state survives in the browser cookie across the redirect flow. Also removes explicit redirectURI from socialProviders (Better-Auth derives it from baseURL) and adds visible error feedback on the login page when OAuth callbacks fail. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -170,8 +170,6 @@ export async function initAuth(): Promise<void> {
|
||||
const hasGoogle = !!(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET);
|
||||
const hasGitHub = !!(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET);
|
||||
|
||||
const callbackBase = `${BETTER_AUTH_URL}/api/auth/callback`;
|
||||
|
||||
// Build Better-Auth instance using resolved config
|
||||
authInstance = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
@@ -179,6 +177,9 @@ export async function initAuth(): Promise<void> {
|
||||
}),
|
||||
secret: BETTER_AUTH_SECRET,
|
||||
baseURL: BETTER_AUTH_URL,
|
||||
account: {
|
||||
storeStateStrategy: "cookie" as const,
|
||||
},
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -205,14 +206,12 @@ export async function initAuth(): Promise<void> {
|
||||
google: {
|
||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||
redirectURI: `${callbackBase}/google`,
|
||||
},
|
||||
} : {}),
|
||||
...(hasGitHub ? {
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID!,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
||||
redirectURI: `${callbackBase}/github`,
|
||||
},
|
||||
} : {}),
|
||||
},
|
||||
|
||||
+15
-1
@@ -23,17 +23,26 @@ import { useSession, signIn } from "./lib/auth-client.js";
|
||||
function LoginPage() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [providers, setProviders] = useState<string[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/auth/providers")
|
||||
.then((r) => r.json())
|
||||
.then((data) => setProviders(data.providers ?? []))
|
||||
.catch(() => setProviders([]));
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const authError = params.get("error");
|
||||
if (authError) setError(authError.replace(/_/g, " "));
|
||||
}, []);
|
||||
|
||||
const handleSocialLogin = async (provider: string) => {
|
||||
setIsLoading(true);
|
||||
await signIn.social({ provider, callbackURL: window.location.origin });
|
||||
setError(null);
|
||||
const result = await signIn.social({ provider, callbackURL: window.location.origin });
|
||||
if (result?.error) {
|
||||
setError(result.error.message ?? "Sign-in failed");
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isGoogle = providers.includes("google");
|
||||
@@ -65,6 +74,11 @@ function LoginPage() {
|
||||
<p style={{ color: "#6b7280", marginBottom: "1.5rem", fontSize: 14 }}>
|
||||
Sign in to continue
|
||||
</p>
|
||||
{error && (
|
||||
<div style={{ background: "#fef2f2", border: "1px solid #fecaca", borderRadius: 6, padding: "0.5rem 0.75rem", marginBottom: "1rem", color: "#991b1b", fontSize: 13 }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{isGoogle && (
|
||||
<button
|
||||
onClick={() => handleSocialLogin("google")}
|
||||
|
||||
Reference in New Issue
Block a user