{"record":{"id":"3653a33735db31f0","repo":"bitwarden/server","slug":"invalidssotoken","errorCode":null,"errorMessage":"InvalidSsoToken","messagePattern":"InvalidSsoToken","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"bitwarden_license/src/Sso/Controllers/AccountController.cs","lineNumber":255,"sourceCode":"    }\n\n    /// <summary>\n    /// Validates the scheme (organization ID) against the organization ID found in the ssoToken.\n    /// </summary>\n    /// <param name=\"scheme\">The authentication scheme (organization ID) to validate.</param>\n    /// <param name=\"ssoToken\">The SSO token to validate against.</param>\n    /// <exception cref=\"Exception\">Thrown if the scheme (organization ID) does not match the organization ID found in the ssoToken.</exception>\n    private void ValidateSchemeAgainstSsoToken(string scheme, string ssoToken)\n    {\n        SsoTokenable tokenable;\n\n        try\n        {\n            tokenable = _dataProtector.Unprotect(ssoToken);\n        }\n        catch\n        {\n            throw new Exception(_i18nService.T(\"InvalidSsoToken\"));\n        }\n\n        if (!Guid.TryParse(scheme, out var schemeOrgId) || tokenable.OrganizationId != schemeOrgId)\n        {\n            throw new Exception(_i18nService.T(\"SsoOrganizationIdMismatch\"));\n        }\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> ExternalCallback()\n    {\n        // Read external identity from the temporary cookie\n        var result = await HttpContext.AuthenticateAsync(\n            AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);\n\n        if (!result.Succeeded)\n        {\n            throw new Exception(_i18nService.T(\"ExternalAuthenticationError\"));","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Sso/Controllers/AccountController.cs#L237-L273","documentation":"Thrown in AccountController.ValidateSchemeAgainstSsoToken (line 255) when _dataProtector.Unprotect(ssoToken) throws an exception. The SSO token is a data-protected payload (ASP.NET Core Data Protection) that encodes the organization ID and an expiry. Unprotect fails if the token is corrupt, expired, tampered with, or was protected under a different key ring.","triggerScenarios":"ExternalChallenge is called with an ssoToken that cannot be decrypted/verified by IDataProtectorTokenFactory<SsoTokenable>.Unprotect.","commonSituations":"Token expired (default lifetime from SsoTokenLifetimeInSeconds); data-protection keys rotated or not persisted across server restarts (common in self-hosted multi-instance without shared key ring); token from a different environment (staging vs prod); token truncated or URL-decoded incorrectly in transit.","solutions":["Ensure data-protection keys are persisted and shared across all server instances (e.g., Redis, file share, or Azure Key Vault).","Verify the token hasn't expired — re-run PreValidate to mint a fresh token if needed.","Confirm the token is passed intact (no double-encoding, no truncation) from client to ExternalChallenge."],"exampleFix":"// before — stale token reused after restart with new key ring\nvar token = oldPreValidateToken;\n// after — mint a fresh token via PreValidate and persist DP keys\nservices.AddDataProtection()\n    .PersistKeysToFileSystem(new DirectoryInfo(\"/var/keys\"))\n    .SetApplicationName(\"Bitwarden\");","handlingStrategy":"validation","validationCode":"// Validate token before passing to ExternalChallenge\ntry { _dataProtector.Unprotect(ssoToken); }\ncatch { /* re-mint via PreValidate or return error */\n    return BadRequest(\"SSO token is expired or invalid. Please restart the login flow.\");\n}","typeGuard":null,"tryCatchPattern":"try { ValidateSchemeAgainstSsoToken(scheme, ssoToken); }\ncatch (Exception ex) when (ex.Message.Contains(\"InvalidSsoToken\"))\n{ /* redirect to PreValidate to mint a fresh token */ }","preventionTips":["Persist ASP.NET Core Data Protection keys to a shared store (Redis, file share, Key Vault) across all instances.","Set an appropriate SsoTokenLifetimeInSeconds and educate users to complete login promptly.","Pass tokens via secure, non-truncating channels; avoid URL parameters where possible."],"tags":["sso","authentication","token","data-protection","configuration","key-management"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}