From e0bd30b0ca858251df4ff4d387e9ea5d741136f0 Mon Sep 17 00:00:00 2001 From: Simon Biewald Date: Wed, 28 Oct 2026 20:22:44 +0100 Subject: [PATCH] Add SSO_SKIP_USERINFO to fix ADFS interop. OpenID login flow gets claims from the idtoken, and calls the userinfo endpoint. Unfortunately, IDPs do not have to implement the userinfo endpoint. Notably, ADFS does not implement it. If SSO_SKIP_USERINFO is set to true, Vaultwarden will not attempt to retrieve claims from userinfo, and solely uses the identity token instead. --- src/config.rs | 2 ++ src/sso.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 54b3d056..c14d73f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -864,6 +864,8 @@ make_config! { sso_client_cache_expiration: u32, true, def, 0; /// Log all tokens |> `LOG_LEVEL=debug` or `LOG_LEVEL=info,vaultwarden::sso=debug` is required sso_debug_tokens: bool, true, def, false; + /// Do not call userinfo endpoint, solely use identity token claims (set to true if you use ADFS). + sso_skip_userinfo: bool, true, def, false; }, /// Yubikey settings diff --git a/src/sso.rs b/src/sso.rs index 6b575a3e..3b19b2f5 100644 --- a/src/sso.rs +++ b/src/sso.rs @@ -2,6 +2,8 @@ use std::{sync::LazyLock, time::Duration}; use chrono::Utc; use derive_more::{AsRef, Deref, Display, From, Into}; +use openidconnect::core::{CoreGenderClaim, CoreUserInfoClaims}; +use openidconnect::StandardClaims; use url::Url; use crate::{ @@ -281,7 +283,12 @@ pub async fn exchange_code( let client = Client::cached().await?; let (token_response, id_claims) = client.exchange_code(code, client_verifier, &sso_auth).await?; - let user_info = client.user_info(token_response.access_token().to_owned()).await?; + let user_info: CoreUserInfoClaims; + if CONFIG.sso_skip_userinfo() { + user_info = client.user_info(token_response.access_token().to_owned()).await?; + } else { + user_info = CoreUserInfoClaims::new(StandardClaims::new(id_claims.subject().clone()), Default::default()); + } let email = match id_claims.email().or(user_info.email()) { None => err!("Neither id token nor userinfo contained an email"),