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).

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)

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.