You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

9.0 KiB

SSO cookie vendor

Lets the Bitwarden mobile and desktop apps log in when Vaultwarden sits behind a reverse proxy that authenticates every request, such as Cloudflare Access, Authentik, Authelia, or oauth2-proxy.

Background

Putting Vaultwarden behind an authenticating proxy means only users who pass your identity provider (IdP) reach the vault at all. Bots cannot crawl the endpoint, credential stuffing never reaches the login form, and the exposed surface shrinks to the proxy.

The cost is that the Bitwarden mobile and desktop apps can no longer finish logging in. The proxy expects a browser with a cookie jar and OAuth 2.0 redirect support, and the apps' HTTP clients have neither. After the browser step, the apps receive the proxy's HTML login page where they expect JSON from Vaultwarden, and login stalls.

Bitwarden solved this in their own server in February 2026 with a flow called SSO cookie vending:

  1. The server states in /api/config that it sits behind an authenticating proxy, and names the IdP login URL and the cookie to look for.
  2. The app opens a system browser at that IdP login URL.
  3. After the user authenticates, the proxy sets its cookie and the browser reaches /api/sso-cookie-vendor.
  4. The server reads the cookie and redirects the browser to a bitwarden:// deep link carrying it.
  5. The app attaches that cookie to every later API request, and the proxy lets those requests through.

Vaultwarden 2026.2.0 shipped the web-vault connector page from bitwarden/clients#18476, but not the two server-side pieces the flow needs: the /api/sso-cookie-vendor endpoint and the communication.bootstrap block in /api/config. Without them the apps detect the connector page, open a browser, complete the proxy's authentication, and then get a 404 when they try to collect the cookie. This change adds both pieces.

What this adds

A configuration section, sso_cookie_vendor, holding four settings:

Setting Purpose
SSO_COOKIE_VENDOR_ENABLED Turns the feature on. Defaults to false.
SSO_COOKIE_VENDOR_IDP_LOGIN_URL URL the app opens in a browser to authenticate.
SSO_COOKIE_VENDOR_COOKIE_NAME Name of the cookie the proxy sets on authenticated requests.
SSO_COOKIE_VENDOR_COOKIE_DOMAIN Domain scope of that cookie.

On top of those settings, this change:

  • Publishes the three string settings in /api/config as a communication.bootstrap object, in the shape Bitwarden's clients already read from bitwarden/server#6892.
  • Serves GET /api/sso-cookie-vendor, which reads the proxy's cookie from the request and returns a 302 to bitwarden://sso-cookie-vendor?COOKIE_NAME=COOKIE_VALUE&d=1. Replace COOKIE_NAME and COOKIE_VALUE with your configured cookie name and its percent-encoded value; d=1 is the sentinel the clients look for.
  • Refuses to start when SSO_COOKIE_VENDOR_ENABLED is true and any of the three string settings is empty, and reports which ones to set.

The route is registered only when the feature is on. With SSO_COOKIE_VENDOR_ENABLED=false, Vaultwarden serves exactly the routes it served before.

Sharded cookies

Cloudflare Access splits its auth JWT across numbered cookies, such as CF_Authorization-0 and CF_Authorization-1, when the token outgrows the per-cookie size limit. The endpoint looks for up to 20 shards, -0 through -19, and forwards every shard it finds in one deep link, in ascending order, so the app can reassemble the token. An unsuffixed cookie takes precedence over any shards, matching the Bitwarden server.

Why this belongs in the server

The existing workaround for Cloudflare Access users is a Cloudflare Worker that intercepts /api/config and /api/sso-cookie-vendor and supplies the same behavior. That works, with three drawbacks:

  • Every user behind Cloudflare Access has to deploy and maintain a Worker.
  • A Worker helps only Cloudflare Access users. Authentik, Authelia, oauth2-proxy, and any other authenticating proxy that sets a cookie can use the same flow, but each one needs its own shim.
  • communication.bootstrap is part of Bitwarden's /api/config contract, so it belongs in the server rather than in a proxy layer.

Implementing it in Vaultwarden makes any authenticating proxy work with the mobile and desktop apps once you set four environment variables.

Configure the endpoint

Set the four settings in .env, in config.json, or through the admin panel:

SSO_COOKIE_VENDOR_ENABLED=true
SSO_COOKIE_VENDOR_IDP_LOGIN_URL=https://example.cloudflareaccess.com/cdn-cgi/access/login/vault.example.com
SSO_COOKIE_VENDOR_COOKIE_NAME=CF_Authorization
SSO_COOKIE_VENDOR_COOKIE_DOMAIN=vault.example.com

Cloudflare Access

  • SSO_COOKIE_VENDOR_IDP_LOGIN_URL is the Access Login URL on the application's details page. It takes the form https://TEAM.cloudflareaccess.com/cdn-cgi/access/login/YOUR_DOMAIN, where TEAM is your Cloudflare Zero Trust team name and YOUR_DOMAIN is the hostname the Access application protects.
  • SSO_COOKIE_VENDOR_COOKIE_NAME is always CF_Authorization.
  • SSO_COOKIE_VENDOR_COOKIE_DOMAIN is the domain the Access application protects.

Other authenticating proxies

Any proxy works that redirects unauthenticated requests to a browser-based IdP flow and sets a cookie on the authenticated response. Set SSO_COOKIE_VENDOR_IDP_LOGIN_URL to the proxy's login URL, and set SSO_COOKIE_VENDOR_COOKIE_NAME and SSO_COOKIE_VENDOR_COOKIE_DOMAIN to the cookie the proxy sets on authenticated sessions.

Note: Cloudflare Access is the only proxy this has run against in production. The others meet the requirements above, but no one has reported a tested configuration for them yet.

How the login flow runs

From the user's side, with the feature configured:

  1. The user opens the Bitwarden app and points it at their Vaultwarden server.
  2. The app reads /api/config, finds communication.bootstrap.type set to ssoCookieVendor, and switches to the cookie vending flow.
  3. The app prompts the user to sign in through their browser and opens the system browser at SSO_COOKIE_VENDOR_IDP_LOGIN_URL.
  4. The browser follows the proxy to the IdP. The user authenticates.
  5. The proxy sets its auth cookie and sends the browser to /api/sso-cookie-vendor.
  6. Vaultwarden reads the cookie off the request and redirects the browser to bitwarden://sso-cookie-vendor?CF_Authorization=COOKIE_VALUE&d=1.
  7. The operating system hands the deep link to the Bitwarden app.
  8. The app stores the cookie and sends it with every later API request. The proxy recognizes the cookie, lets the request through, and the app continues to the usual master password unlock.

The apps need no changes. This uses the cookie vending support Bitwarden's clients already ship.

Security notes

  • The endpoint exists only when SSO_COOKIE_VENDOR_ENABLED is true. An install that leaves the feature off serves the same routes it served before.
  • The endpoint reads a cookie from a request the proxy has already authenticated. The proxy validates the IdP session before the request reaches Vaultwarden, so this adds no authentication boundary of its own.
  • The redirect moves the cookie between two parties that both already hold it: the browser that received it and the app on the same device. The proxy validates the same cookie in either case.
  • Vaultwarden's own authentication still applies. The user unlocks the vault with their master password after the proxy gate, so this does not weaken the vault.
  • The deep link is capped at 8192 bytes, matching the Bitwarden server. A request that would exceed the cap gets a 400 and an HTML error page.
  • A request carrying neither the cookie nor any shard gets a 404 and the same error page, which tells the user to return to the app.

Test the change

Unit tests live in src/api/core/sso_cookie_vendor.rs under #[cfg(test)] mod tests. Run them with:

cargo test --features sqlite -- sso_cookie_vendor

They cover:

  • A single cookie, the common case.
  • Sharded cookies, forwarded in suffix order regardless of map iteration order.
  • An unsuffixed cookie taking precedence when shards are also present.
  • A missing cookie producing a 404.
  • Percent-encoding of values holding spaces and reserved characters.
  • An oversize cookie producing a link past the 8192-byte cap.
  • The error page matching the Bitwarden server's HTML.

References