🌏 中文版
TL;DR
When mobile Chrome redirects back to the login page after signing in, the issue isn’t necessarily OAuth or broken frontend state. In this case, the root cause was that the HTTP entry point for app-dev.daodao.so wasn’t issuing a 301 redirect to HTTPS, so /auth/me requests sent with an http origin didn’t include the auth_token cookie.
Context
The problem occurred in a dev environment. Users reported that after signing in on mobile Chrome, they were immediately redirected back to the login page. Safari appeared to work fine.
The system setup:
- Frontend: Next.js product app deployed at
app-dev.daodao.so - Backend: Express API deployed at
server-dev.daodao.so - Auth: Google OAuth callback handled by the backend, which sets an
auth_tokencookie and redirects back to the frontend - Auth state check: frontend calls
/api/v1/auth/me
This kind of issue often leads you to suspect the frontend route guard first — that the auth state hasn’t refreshed after the OAuth callback, causing the client-side guard to treat the user as unauthenticated. But the real clue this time was in the request headers.
The Problem
After signing in on mobile Chrome, the user was redirected back to the login page. The backend was returning 401 for /api/v1/auth/me:
{
"method": "GET",
"url": "/api/v1/auth/me",
"message": "Missing authentication token"
}
This meant the backend wasn’t receiving a usable token. There were at least three possible causes:
- The OAuth callback didn’t set
Set-Cookie - The browser received the cookie but didn’t send it on subsequent requests
- The request was coming from a different origin or protocol, causing the cookie rules not to apply
Since Safari worked but Chrome didn’t, I focused on cookies, secure context, Origin/Referer, and CORS.
Investigation
I added two diagnostic log statements to the backend.
The first goes right after the OAuth callback sets the cookie, logging only environment context — no token values:
const setCookieHeader = res.getHeader("Set-Cookie");
loggerService.info("OAuth callback set auth cookie", {
userAgent: req.get("User-Agent"),
origin: req.get("Origin"),
referer: req.get("Referer"),
host: req.get("Host"),
forwardedProto: req.get("X-Forwarded-Proto"),
frontendUrl,
cookieDomain: process.env.COOKIE_DOMAIN,
redirectTarget,
setCookieHeaderCount: Array.isArray(setCookieHeader) ? setCookieHeader.length : setCookieHeader ? 1 : 0,
});
The second goes in the /auth/me handler when the token is missing — again, only cookie names and boolean flags, no token values:
loggerService.warn("Auth me missing token", {
userAgent: req.get("User-Agent"),
origin: req.get("Origin"),
referer: req.get("Referer"),
host: req.get("Host"),
forwardedProto: req.get("X-Forwarded-Proto"),
cookieDomain: process.env.COOKIE_DOMAIN,
cookieNames: Object.keys(req.cookies ?? {}),
hasAuthTokenCookie: Boolean(req.cookies?.auth_token),
hasAuthorizationHeader: Boolean(req.get("Authorization")),
secFetchSite: req.get("Sec-Fetch-Site"),
secFetchMode: req.get("Sec-Fetch-Mode"),
secFetchDest: req.get("Sec-Fetch-Dest"),
});
The new logs quickly pointed to the anomaly:
{
"message": "Auth me missing token",
"origin": "http://app-dev.daodao.so",
"referer": "http://app-dev.daodao.so/",
"host": "server-dev.daodao.so",
"cookieDomain": ".daodao.so",
"cookieNames": ["_ga", "_ga_52MHR7GXYH", "ph_phc_...", "_clck"],
"hasAuthTokenCookie": false,
"secFetchSite": "cross-site"
}
Two key observations:
- The backend received
Origin/Refererashttp://app-dev.daodao.so. - No
auth_tokenin the cookie jar.
But later in the same login flow, the OAuth callback had successfully set the cookie:
{
"message": "OAuth callback set auth cookie",
"frontendUrl": "https://app-dev.daodao.so",
"cookieDomain": ".daodao.so",
"redirectTarget": "https://app-dev.daodao.so/auth/callback?...",
"setCookieHeaderCount": 1
}
And the /api/v1/auth/me call following the callback returned 200. That meant OAuth itself wasn’t broken — the cookie worked fine after the HTTPS callback.
Next, I tested the HTTP entry point directly:
curl -I -L http://app-dev.daodao.so
Before the fix, Cloudflare returned 200 directly:
HTTP/1.1 200 OK
Server: cloudflare
link: <http://app-dev.daodao.so/>; rel="alternate"; hreflang="zh-TW"
If HTTP were being forced to HTTPS, this should have been a 301 or 302 with a Location pointing to https://app-dev.daodao.so/. It wasn’t.
The Fix
Enable Always Use HTTPS for daodao.so in Cloudflare.
Steps in the Cloudflare dashboard:
- Open Cloudflare Dashboard
- Select
daodao.so - Go to
SSL/TLS - Go to
Edge Certificates - Enable
Always Use HTTPS
After the fix, testing again:
curl -I http://app-dev.daodao.so
Now returns:
HTTP/1.1 301 Moved Permanently
Location: https://app-dev.daodao.so/
Server: cloudflare
Signing in again on mobile Chrome worked normally.
Why This Happens
Cloudflare proxying your traffic does not mean HTTP entry points are automatically redirected to HTTPS.
In this case, http://app-dev.daodao.so was returning 200, so browsers could — and did, in certain situations — send requests with an HTTP origin. For auth cookies, this is a recipe for trouble:
auth_tokenis a secure, cross-subdomain cookie.SameSite=Nonecookies requireSecure, which means they only apply over HTTPS.- When the frontend makes a request with
http://app-dev.daodao.soas its origin, Chrome applies different security context and site classification rules. - The backend saw
secFetchSite: "cross-site"and received noauth_token.
MDN’s documentation on Set-Cookie notes that SameSite=None must be paired with Secure. The MDN Request.credentials docs also warn that whether the browser sends credentials and whether it honors Set-Cookie depends on the request credentials configuration. Even when all those conditions are met correctly, you still need to make sure the page itself isn’t issuing requests from an HTTP origin.
So this wasn’t “mobile Chrome doesn’t support login” or “the OAuth callback didn’t set the cookie.” The real issue was that the HTTP entry point was still alive, causing some requests to originate from the wrong protocol.
Takeaways
Don’t just check “is the cookie there?” when debugging login issues. Also look at:
- Whether the OAuth callback returned
Set-Cookie - Whether the
/auth/merequest includedauth_token - Whether
Origin/Refererishttporhttps - Whether
Sec-Fetch-Siteissame-siteorcross-site - Whether the Cloudflare HTTP entry point actually issues a 301 to HTTPS
In short: running a service behind Cloudflare does not mean the HTTP entry point is closed. Redirecting HTTP to HTTPS should be a basic acceptance criterion for any authentication system.
References
Loading...