diff --git a/internal/app/server/auth_handler_v2.go b/internal/app/server/auth_handler_v2.go
index 5ab70f3..d3c9b17 100644
--- a/internal/app/server/auth_handler_v2.go
+++ b/internal/app/server/auth_handler_v2.go
@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
"net/url"
- "time"
"github.com/gin-gonic/gin"
)
@@ -32,12 +31,13 @@ func (s *Server) GoogleCallbackHandlerV2(ctx *gin.Context) {
domain := s.deps.EnvironmentConfig.FrontendDomain
if jwt, err := s.deps.AuthService.GoogleAuthSuccess(state, code); err != nil {
- url := fmt.Sprintf("%s/v2/web/login?error=%s", domain, url.QueryEscape(err.Error()))
- ctx.Redirect(http.StatusSeeOther, url)
+ redirectUrl := fmt.Sprintf("%s/v2/web/login?error=%s", domain, url.QueryEscape(err.Error()))
+ ctx.Redirect(http.StatusSeeOther, redirectUrl)
} else {
- url := fmt.Sprintf("%s/v2/web/home", domain)
- s.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7)
- ctx.Redirect(http.StatusSeeOther, url)
+ // Pass JWT via query param - frontend will set the cookie
+ // This bypasses cross-origin cookie issues with Cloudflare/proxies
+ redirectUrl := fmt.Sprintf("%s/v2/web/auth/callback?token=%s", domain, url.QueryEscape(jwt))
+ ctx.Redirect(http.StatusSeeOther, redirectUrl)
}
}
diff --git a/web/src/App.tsx b/web/src/App.tsx
index a4ef3b0..8e46a3d 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -13,6 +13,7 @@ import { use, type ReactNode } from 'react';
import { AuthContext } from './context/AuthContext';
import RecipePage from './pages/Recipe';
import SearchPage from './pages/Search';
+import AuthCallback from './pages/AuthCallback';
function ProtectedRoute({ children }: { children: ReactNode }) {
const { isLoggedIn } = use(AuthContext)
@@ -37,6 +38,9 @@ function App() {
{/* Login page does not inherit WebLayout */}
} />
+ {/* Auth callback - handles token from OAuth redirect */}
+ } />
+
}>
} />
} />
diff --git a/web/src/pages/AuthCallback.tsx b/web/src/pages/AuthCallback.tsx
new file mode 100644
index 0000000..545b9ff
--- /dev/null
+++ b/web/src/pages/AuthCallback.tsx
@@ -0,0 +1,29 @@
+import { useEffect } from "react";
+import { useSearchParams, useNavigate } from "react-router-dom";
+import { useCookies } from "react-cookie";
+
+export default function AuthCallback() {
+ const [searchParams] = useSearchParams();
+ const navigate = useNavigate();
+ const [, setCookie] = useCookies(["jwt_token"]);
+
+ useEffect(() => {
+ const token = searchParams.get("token");
+
+ if (token) {
+ // Set cookie with 7 day expiration, accessible across all subdomains
+ setCookie("jwt_token", token, {
+ path: "/",
+ maxAge: 60 * 60 * 24 * 7, // 7 days in seconds
+ secure: true,
+ sameSite: "lax",
+ });
+ void navigate("/v2/web/home", { replace: true });
+ } else {
+ // No token provided, redirect to login
+ void navigate("/v2/web/login", { replace: true });
+ }
+ }, [searchParams, setCookie, navigate]);
+
+ return null;
+}