bitwarden/server · error · BadRequestException

Invalid token.

Error message

Invalid token.

What it means

Thrown inside CompleteSetupAsync when the 'ProviderSetupInvite' data-protection token fails CoreHelpers.TokenIsValid (invalid or expired, governed by GlobalSettings.OrganizationInviteExpirationHours). The token binds the owner's email and the provider Id. BadRequestException (HTTP 400).

Solutions

  1. Regenerate the setup invite via SendProviderSetupInviteEmailAsync to mint a fresh token.
  2. Confirm the token's embedded email still matches the owner's current email.
  3. Check OrganizationInviteExpirationHours configuration if invites expire too aggressively.
Defensive patterns

Strategy: validation

Validate before calling

if (!CoreHelpers.TokenIsValid("ProviderSetupInvite", _dataProtector, token, owner.Email, provider.Id,
        _globalSettings.OrganizationInviteExpirationHours))
    throw new InvalidOperationException("Setup token is invalid or expired. Request a new invite.");

Try / catch

try { await _providerService.CompleteSetupAsync(provider, ownerUserId, token, key, payment, billing); }
catch (BadRequestException ex) when (ex.Message == "Invalid token.")
{ /* regenerate invite via SendProviderSetupInviteEmailAsync */ }

Prevention

When it happens

Trigger: Setup link used after the invite expiration window; tampered token; token generated for a different email or provider; data-protection key ring changed on the server.

Common situations: User delays setup beyond the invite window; server key rotation invalidating older tokens; owner email changed after the invite was generated.

Understand the failure class

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/4f9d6bc1b04e0cbf. Report an issue: GitHub.

Appendix: source

Thrown at bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs:120

    }

    public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TokenizedPaymentMethod paymentMethod, BillingAddress billingAddress)
    {
        var owner = await _userService.GetUserByIdAsync(ownerUserId);
        if (owner == null)
        {
            throw new BadRequestException("Invalid owner.");
        }

        if (provider.Status != ProviderStatusType.Pending)
        {
            throw new BadRequestException("Provider is already setup.");
        }

        if (!CoreHelpers.TokenIsValid("ProviderSetupInvite", _dataProtector, token, owner.Email, provider.Id,
            _globalSettings.OrganizationInviteExpirationHours))
        {
            throw new BadRequestException("Invalid token.");
        }

        var providerUser = await _providerUserRepository.GetByProviderUserAsync(provider.Id, ownerUserId);
        if (!(providerUser is { Type: ProviderUserType.ProviderAdmin }))
        {
            throw new BadRequestException("Invalid owner.");
        }

        var organizationAutoConfirmPolicyRequirement = await _policyRequirementQuery
            .GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerUserId);

        if (organizationAutoConfirmPolicyRequirement
            .CannotCreateProvider())
        {
            throw new BadRequestException(new UserCannotJoinProvider().Message);
        }

        var customer = await _providerBillingService.SetupCustomer(provider, paymentMethod, billingAddress);

View on GitHub (pinned to e93b962371)