This adds the server side of Key Connector, so SSO users can log in without a master password (the user key lives in a separately hosted key connector service instead).
Concretely: the three /api/accounts endpoints the clients call (set-key-connector-key, convert-to-key-connector, key-connector/confirmation-details/<org>), a uses_key_connector flag on users (+ migrations), and KEY_CONNECTOR_ENABLED / KEY_CONNECTOR_URL / KEY_CONNECTOR_ORG_NAME config options that are only accepted together with SSO_ENABLED=true. SSO logins now put amr: ["external"] in the access token and the login response advertises KeyConnectorUrl, which is what makes the clients enter the key connector flow. The login JWKS is also served via OIDC discovery now, so the connector can fetch the token signing key itself instead of needing a manually exported PEM. With the option unset nothing changes.
Some remarks:
KEY_CONNECTOR_ORG_NAME is just the name shown on the client's confirmation screen. Key Connector is normally tied to an organization, but there's no real org behind it here. Not sure if a fixed default would be preferable to a config option.
The protocol was worked out from the GPL client code only, I didn't look at Bitwarden's key-connector server repo.
Tested end-to-end against Keycloak plus my own key connector implementation, replaying the requests the web client makes: OIDC login with amr=external, the new endpoints, converting an existing user, and the login response advertising the connector URL afterwards.
Trying it out
Caution
the migration cannot be undone, once it has run the schema no longer matches upstream and downgrading will fail. Take a database backup before trying this.
My fork's main branch is a working copy of this and gets built as ghcr.io/acul021/vaultwarden:testing (amd64 only). The connector side and setup instructions live in the key-connector README. In short: point KC_IDENTITY_AUTHORITY at Vaultwarden's /identity, set an encryption key, and put the connector's URL into KEY_CONNECTOR_URL.
For the record, this is deliberately not #6949: Vaultwarden never sees or stores any key material here, the connector is a separate service meant to run on its own host, along the lines of what was suggested when that PR was closed.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/dani-garcia/vaultwarden/pull/7419
**Author:** [@acul021](https://github.com/acul021)
**Created:** 7/14/2026
**Status:** 🔄 Open
**Base:** `main` ← **Head:** `key-connector`
---
### 📝 Commits (6)
- [`f513a01`](https://github.com/dani-garcia/vaultwarden/commit/f513a01ef9c4a3359c3a45e4ce8af777626adcbb) Add Key Connector support for SSO users
- [`f48a4b7`](https://github.com/dani-garcia/vaultwarden/commit/f48a4b7eba1d14e1caf805da4b17f7c5d48e2ad7) Harden the Key Connector account endpoints
- [`317883c`](https://github.com/dani-garcia/vaultwarden/commit/317883c8c26878335c9940394f37c0eee166cbcd) Advertise the Key Connector to new SSO users
- [`c86ebd0`](https://github.com/dani-garcia/vaultwarden/commit/c86ebd07fd32b250ae080ab3d7195cc4c514fe22) Add Playwright tests for the Key Connector flow
- [`7adf1b5`](https://github.com/dani-garcia/vaultwarden/commit/7adf1b50cfb65f7a503446f45bc4d522abeb2d20) Tighten the KEY_CONNECTOR_URL validation
- [`826aea8`](https://github.com/dani-garcia/vaultwarden/commit/826aea858e4d49ddc37eeaeb64b1768ced360233) Serve the login JWKS via OIDC discovery
### 📊 Changes
**20 files changed** (+362 additions, -9 deletions)
<details>
<summary>View changed files</summary>
➕ `migrations/mysql/2026-06-26-000000_add_uses_key_connector/down.sql` (+1 -0)
➕ `migrations/mysql/2026-06-26-000000_add_uses_key_connector/up.sql` (+1 -0)
➕ `migrations/postgresql/2026-06-26-000000_add_uses_key_connector/down.sql` (+1 -0)
➕ `migrations/postgresql/2026-06-26-000000_add_uses_key_connector/up.sql` (+1 -0)
➕ `migrations/sqlite/2026-06-26-000000_add_uses_key_connector/down.sql` (+1 -0)
➕ `migrations/sqlite/2026-06-26-000000_add_uses_key_connector/up.sql` (+1 -0)
📝 `playwright/docker-compose.yml` (+2 -0)
📝 `playwright/test.env` (+4 -0)
➕ `playwright/tests/setups/keyconnector.ts` (+72 -0)
➕ `playwright/tests/sso_keyconnector.spec.ts` (+61 -0)
➕ `src/api/core/key_connector.rs` (+103 -0)
📝 `src/api/core/mod.rs` (+2 -0)
📝 `src/api/identity.rs` (+31 -2)
📝 `src/auth.rs` (+28 -0)
📝 `src/config.rs` (+24 -0)
📝 `src/db/models/organization.rs` (+4 -4)
📝 `src/db/models/user.rs` (+7 -2)
📝 `src/db/schema.rs` (+1 -0)
📝 `src/sso.rs` (+7 -1)
📝 `src/util.rs` (+10 -0)
</details>
### 📄 Description
This adds the server side of [Key Connector](https://bitwarden.com/help/about-key-connector/), so SSO users can log in without a master password (the user key lives in a separately hosted key connector service instead).
Concretely: the three `/api/accounts` endpoints the clients call (`set-key-connector-key`, `convert-to-key-connector`, `key-connector/confirmation-details/<org>`), a `uses_key_connector` flag on users (+ migrations), and `KEY_CONNECTOR_ENABLED` / `KEY_CONNECTOR_URL` / `KEY_CONNECTOR_ORG_NAME` config options that are only accepted together with `SSO_ENABLED=true`. SSO logins now put `amr: ["external"]` in the access token and the login response advertises `KeyConnectorUrl`, which is what makes the clients enter the key connector flow. The login JWKS is also served via OIDC discovery now, so the connector can fetch the token signing key itself instead of needing a manually exported PEM. With the option unset nothing changes.
Some remarks:
- `KEY_CONNECTOR_ORG_NAME` is just the name shown on the client's confirmation screen. Key Connector is normally tied to an organization, but there's no real org behind it here. Not sure if a fixed default would be preferable to a config option.
- The protocol was worked out from the GPL client code only, I didn't look at Bitwarden's key-connector server repo.
- Tested end-to-end against Keycloak plus [my own key connector implementation](https://github.com/acul021/key-connector), replaying the requests the web client makes: OIDC login with `amr=external`, the new endpoints, converting an existing user, and the login response advertising the connector URL afterwards.
## Trying it out
> [!CAUTION]
> the migration cannot be undone, once it has run the schema no longer matches upstream and downgrading will fail. Take a database backup before trying this.
My fork's `main` branch is a working copy of this and gets built as `ghcr.io/acul021/vaultwarden:testing` (amd64 only). The connector side and setup instructions live in the [key-connector](https://github.com/acul021/key-connector) README. In short: point `KC_IDENTITY_AUTHORITY` at Vaultwarden's `/identity`, set an encryption key, and put the connector's URL into `KEY_CONNECTOR_URL`.
For the record, this is deliberately not #6949: Vaultwarden never sees or stores any key material here, the connector is a separate service meant to run on its own host, along the lines of what was suggested when that PR was closed.
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/dani-garcia/vaultwarden/pull/7419
Author: @acul021
Created: 7/14/2026
Status: 🔄 Open
Base:
main← Head:key-connector📝 Commits (6)
f513a01Add Key Connector support for SSO usersf48a4b7Harden the Key Connector account endpoints317883cAdvertise the Key Connector to new SSO usersc86ebd0Add Playwright tests for the Key Connector flow7adf1b5Tighten the KEY_CONNECTOR_URL validation826aea8Serve the login JWKS via OIDC discovery📊 Changes
20 files changed (+362 additions, -9 deletions)
View changed files
➕
migrations/mysql/2026-06-26-000000_add_uses_key_connector/down.sql(+1 -0)➕
migrations/mysql/2026-06-26-000000_add_uses_key_connector/up.sql(+1 -0)➕
migrations/postgresql/2026-06-26-000000_add_uses_key_connector/down.sql(+1 -0)➕
migrations/postgresql/2026-06-26-000000_add_uses_key_connector/up.sql(+1 -0)➕
migrations/sqlite/2026-06-26-000000_add_uses_key_connector/down.sql(+1 -0)➕
migrations/sqlite/2026-06-26-000000_add_uses_key_connector/up.sql(+1 -0)📝
playwright/docker-compose.yml(+2 -0)📝
playwright/test.env(+4 -0)➕
playwright/tests/setups/keyconnector.ts(+72 -0)➕
playwright/tests/sso_keyconnector.spec.ts(+61 -0)➕
src/api/core/key_connector.rs(+103 -0)📝
src/api/core/mod.rs(+2 -0)📝
src/api/identity.rs(+31 -2)📝
src/auth.rs(+28 -0)📝
src/config.rs(+24 -0)📝
src/db/models/organization.rs(+4 -4)📝
src/db/models/user.rs(+7 -2)📝
src/db/schema.rs(+1 -0)📝
src/sso.rs(+7 -1)📝
src/util.rs(+10 -0)📄 Description
This adds the server side of Key Connector, so SSO users can log in without a master password (the user key lives in a separately hosted key connector service instead).
Concretely: the three
/api/accountsendpoints the clients call (set-key-connector-key,convert-to-key-connector,key-connector/confirmation-details/<org>), auses_key_connectorflag on users (+ migrations), andKEY_CONNECTOR_ENABLED/KEY_CONNECTOR_URL/KEY_CONNECTOR_ORG_NAMEconfig options that are only accepted together withSSO_ENABLED=true. SSO logins now putamr: ["external"]in the access token and the login response advertisesKeyConnectorUrl, which is what makes the clients enter the key connector flow. The login JWKS is also served via OIDC discovery now, so the connector can fetch the token signing key itself instead of needing a manually exported PEM. With the option unset nothing changes.Some remarks:
KEY_CONNECTOR_ORG_NAMEis just the name shown on the client's confirmation screen. Key Connector is normally tied to an organization, but there's no real org behind it here. Not sure if a fixed default would be preferable to a config option.amr=external, the new endpoints, converting an existing user, and the login response advertising the connector URL afterwards.Trying it out
My fork's
mainbranch is a working copy of this and gets built asghcr.io/acul021/vaultwarden:testing(amd64 only). The connector side and setup instructions live in the key-connector README. In short: pointKC_IDENTITY_AUTHORITYat Vaultwarden's/identity, set an encryption key, and put the connector's URL intoKEY_CONNECTOR_URL.For the record, this is deliberately not #6949: Vaultwarden never sees or stores any key material here, the connector is a separate service meant to run on its own host, along the lines of what was suggested when that PR was closed.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.