bitwarden/server · error · Exception
UserIdAndTokenMismatch
Error message
UserIdAndTokenMismatch
What it means
Thrown during SSO manual linking when a user is found by the provided userId but the accompanying LinkSso token fails verification via UserManager.VerifyUserTokenAsync. The token is validated against the PasswordResetTokenProvider with purpose TokenPurposes.LinkSso, so failure means the token is expired, tampered with, or was issued for a different purpose/user.
Source
Thrown at bitwarden_license/src/Sso/Controllers/AccountController.cs:808
}
var userId = split[0];
var token = split[1];
var tokenOptions = new TokenOptions();
var claimedUser = await _userService.GetUserByIdAsync(userId);
if (claimedUser != null)
{
var tokenIsValid = await _userManager.VerifyUserTokenAsync(
claimedUser, tokenOptions.PasswordResetTokenProvider, TokenPurposes.LinkSso, token);
if (tokenIsValid)
{
user = claimedUser;
}
else
{
throw new Exception(_i18nService.T("UserIdAndTokenMismatch"));
}
}
return user;
}
/// <summary>
/// Tries to get the organization by the provider which is org id for us as we use the scheme
/// to identify organizations - not identity providers.
/// </summary>
/// <param name="provider">Org id string from SSO scheme property</param>
/// <exception cref="Exception">Errors if the provider string is not a valid org id guid or if the org cannot be found by the id.</exception>
private async Task<Organization> GetOrganizationByProviderAsync(string provider)
{
if (!Guid.TryParse(provider, out var organizationId))
{
// TODO: support non-org (server-wide) SSO in the future?
throw new Exception(_i18nService.T("SSOProviderIsNotAnOrgId", provider));View on GitHub (pinned to e93b962371)
Solutions
- Regenerate the SSO linking invitation from the admin console to obtain a fresh LinkSso token.
- Verify the token was generated with TokenPurposes.LinkSso and PasswordResetTokenProvider — a mismatched purpose will always fail verification.
- If tokens expire too quickly, review the ASP.NET Core token lifespan configuration (DataProtection / TokenLifespan) for the SSO project.
- Confirm the user's SecurityStamp has not changed since the token was issued.
Example fix
// before: stale or wrong-purpose token
var token = oldTokenFromEmail;
// after: regenerate with correct provider and purpose
var token = await userManager.GenerateUserTokenAsync(
user, TokenOptions.DefaultProvider, TokenPurposes.LinkSso); Defensive patterns
Strategy: try-catch
Validate before calling
// Before submitting SSO linking, verify the token is still valid by checking its age
var tokenAge = DateTime.UtcNow - tokenIssuedAt;
if (tokenAge > TimeSpan.FromHours(24)) // typical token lifespan
{
return Redirect("/regenerate-linking-invite");
} Try / catch
try { var user = await GetUserFromManualLinkingDataAsync(userIdentifier); }
catch (Exception ex) when (ex.Message.Contains("UserIdAndTokenMismatch"))
{ /* Token is invalid/expired — prompt user to request a new SSO linking invitation */ } Prevention
- Generate fresh LinkSso tokens immediately before the user initiates linking.
- Monitor SecurityStamp changes (password resets, account recovery) that invalidate outstanding tokens.
- Set a generous but finite token lifespan in TokenOptions to avoid stale-token failures.
- Include the token generation timestamp in logs (not the token itself) for debugging expiry.
When it happens
Trigger: The SSO callback contains a valid userId with a token that has expired, was already consumed, belongs to a different user, or was generated before a security stamp change (e.g. password reset).
Common situations: The linking invitation token has a short lifespan and the user delayed completing SSO linking. A security stamp rotation (password change, account recovery) invalidated outstanding tokens. The token was copied from a different user's invitation.
Related errors
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/2ad0f47815808b1b.
Report an issue: GitHub.