{"record":{"id":"1829f2fc5e7e1715","repo":"bitwarden/server","slug":"acrmissingorinvalid","errorCode":null,"errorMessage":"AcrMissingOrInvalid","messagePattern":"AcrMissingOrInvalid","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"bitwarden_license/src/Sso/Controllers/AccountController.cs","lineNumber":467,"sourceCode":"        var provider = result.Properties.Items[\"scheme\"];\n        //Todo: Validate provider is a valid GUID with TryParse instead. When this is invalid it throws an exception\n        var orgId = new Guid(provider);\n        var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgId);\n        if (ssoConfig == null || !ssoConfig.Enabled)\n        {\n            throw new Exception(_i18nService.T(\"OrganizationOrSsoConfigNotFound\"));\n        }\n\n        var ssoConfigData = ssoConfig.GetData();\n        var externalUser = result.Principal;\n\n        // Validate acr claim against expectation before going further\n        if (!string.IsNullOrWhiteSpace(ssoConfigData.ExpectedReturnAcrValue))\n        {\n            var acrClaim = externalUser.FindFirst(JwtClaimTypes.AuthenticationContextClassReference);\n            if (acrClaim?.Value != ssoConfigData.ExpectedReturnAcrValue)\n            {\n                throw new Exception(_i18nService.T(\"AcrMissingOrInvalid\"));\n            }\n        }\n\n        // Ensure the NameIdentifier used is not a transient name ID, if so, we need a different attribute\n        //  for the user identifier.\n        static bool nameIdIsNotTransient(Claim c) => c.Type == ClaimTypes.NameIdentifier\n                                                     && (c.Properties == null\n                                                         || !c.Properties.TryGetValue(SamlPropertyKeys.ClaimFormat,\n                                                             out var claimFormat)\n                                                         || claimFormat != SamlNameIdFormats.Transient);\n\n        // Try to determine the unique id of the external user (issued by the provider)\n        // the most common claim type for that are the sub claim and the NameIdentifier\n        // depending on the external provider, some other claim type might be used\n        var customUserIdClaimTypes = ssoConfigData.GetAdditionalUserIdClaimTypes();\n        var userIdClaim = externalUser.FindFirst(c => customUserIdClaimTypes.Contains(c.Type)) ??\n                          externalUser.FindFirst(JwtClaimTypes.Subject) ??\n                          externalUser.FindFirst(nameIdIsNotTransient) ??","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Sso/Controllers/AccountController.cs#L449-L485","documentation":"Thrown in AccountController.FindUserFromExternalProviderAsync (line 467) when SsoConfigurationData.ExpectedReturnAcrValue is configured (non-empty) but the ACR (Authentication Context Class Reference) claim returned by the IdP is missing or does not match the expected value.","triggerScenarios":"The org's SSO config sets ExpectedReturnAcrValue (e.g., requiring a specific authentication assurance level), and the external user's claims either lack the JwtClaimTypes.AuthenticationContextClassReference claim or its value differs.","commonSituations":"IdP was reconfigured and no longer emits the expected ACR value; ExpectedReturnAcrValue was set incorrectly (typo, wrong value for the IdP); the IdP's authentication policy changed so a different ACR is returned; OIDC vs SAML ACR representation mismatch.","solutions":["Verify the ExpectedReturnAcrValue in the org's SSO configuration matches what the IdP actually emits.","Check the IdP's response (decode the id_token or inspect SAML response) for the actual ACR value.","If the IdP does not support the configured ACR level, update or clear ExpectedReturnAcrValue in the SSO config.","Ensure the IdP authentication policy enforces the required assurance level so the correct ACR is emitted."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify ACR expectation is met before proceeding\nif (!string.IsNullOrWhiteSpace(ssoConfigData.ExpectedReturnAcrValue))\n{\n    var acr = externalUser.FindFirst(JwtClaimTypes.AuthenticationContextClassReference);\n    if (acr?.Value != ssoConfigData.ExpectedReturnAcrValue)\n        return BadRequest(\"ACR claim does not meet the required authentication level.\");\n}","typeGuard":null,"tryCatchPattern":"try { await FindUserFromExternalProviderAsync(result); }\ncatch (Exception ex) when (ex.Message.Contains(\"AcrMissingOrInvalid\"))\n{ /* alert admin: IdP ACR config mismatch */ }","preventionTips":["Verify ExpectedReturnAcrValue matches the IdP's actual ACR emission.","Test the full SSO flow after changing IdP authentication policies.","Document the IdP's ACR values and map them to the SSO config accurately."],"tags":["sso","authentication","security","claims","acr","idp","configuration"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}