Fixing Dropped Cookies in Android Custom Tabs and WebViews During Auth Redirects
When implementing authentication flows that rely on redirects and session cookies, developers often encounter a baffling issue: everything works seamlessly on desktop and standard mobile browsers, but clicking a link from inside an app like Discord, Slack, or Twitter breaks the session. The cookie is saved in storage, yet the browser refuses to send it along with the redirect request.
The Underlying Mystery: WebView or Custom Tab?
Before diving into the cookie mechanics, it is important to clarify what that "mini-browser" actually is. If you see a menu option like Open in Firefox or Powered by Chrome, the application is using Custom Tabs (such as Chrome Custom Tabs or Firefox Custom Tabs), not a raw Android WebView.
Custom Tabs share the user's primary browser engine, storage, and cookie store. However, they are launched as an external Intent from a host app (like Discord). This origin context plays a critical role in how modern browsers enforce security policies like SameSite.
Why Is the Cookie Dropped? The Role of SameSite=Strict
The root cause of this behavior lies in how SameSite=Strict handles external navigation chains:
SameSite=Strict: The browser will never send the cookie on cross-site requests. Crucially, this includes top-level document navigations initiated outside your site (e.g., clicking a link in Discord or an external email).SameSite=Lax: The cookie is withheld on cross-site subresource requests (images, iframes, AJAXPOST), but it is sent when a user navigates to the origin site via a standard top-level navigation (like clicking an external link or following aGETredirect).
When you click a link in Discord, the top-level navigation originates from an external app/context. Even though your server attempts to set the cookie on the 307 Temporary Redirect to the same origin, many browser engines (including Firefox and Chromium-based engines) treat the immediate redirect target as part of the initial cross-site navigation chain. Because the initial navigation context was cross-site, SameSite=Strict triggers, and the browser silently withholds the newly set cookie.
The Solution: Switch to SameSite=Lax
For authentication links, magic links, OAuth callbacks, and session handling, SameSite=Lax is the recommended web standard. It provides robust defense against Cross-Site Request Forgery (CSRF) while ensuring seamless user transitions from external platforms.
Here is how to adjust the cookie definition in Rust using Actix-Web:
use actix_web::cookie::{Cookie, SameSite};let cookie = Cookie::build("session", session_token) .secure(true) .http_only(true) .same_site(SameSite::Lax) // Change Strict to Lax .path("/") .finish();Alternative Solution: Client-Side Redirect (If Strict is Required)
If your compliance requirements strictly mandate SameSite=Strict for session cookies, you cannot rely on an HTTP 302 or 307 redirect directly from an external link click. Instead, you must break the cross-site navigation chain using an intermediate landing page:
- Respond to
/auth/guest-linkwith anHTTP 200 OKand an HTML page (setting theSameSite=Strictcookie in the response headers). - Within that HTML page, execute a client-side navigation via JavaScript or a manual button:
<!DOCTYPE html><html><head> <title>Authenticating...</title> <meta http-equiv="refresh" content="0; url=/dashboard"></head><body> <p>Logging you in... If you are not redirected, <a href="/dashboard">click here</a>.</p> <script> window.location.replace("/dashboard"); </script></body></html>Because the subsequent navigation to /dashboard is initiated from a page already rendered under your domain's origin, the browser recognizes it as a pure first-party same-site navigation and sends the SameSite=Strict cookie without issue.
Summary
When redirecting users into an authenticated state from external links or in-app custom tabs, avoid using SameSite=Strict with server-side HTTP redirects. Using SameSite=Lax retains strong CSRF security while preventing mobile in-app browsers from dropping your session tokens.