{"record":{"id":"11b9d1d6cf2e056d","repo":"bitwarden/server","slug":"the-token-associated-with-your-request-is-expired","errorCode":null,"errorMessage":"The token associated with your request is expired. A valid token is required to continue.","messagePattern":"The token associated with your request is expired\\. A valid token is required to continue\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Auth/Controllers/WebAuthnController.cs","lineNumber":111,"sourceCode":"\n        return new WebAuthnLoginAssertionOptionsResponseModel\n        {\n            Options = options,\n            Token = token\n        };\n    }\n\n    [Authorize(Policies.Application)]\n    [HttpPost(\"\")]\n    public async Task<WebAuthnCredentialResponseModel> Post([FromBody] WebAuthnLoginCredentialCreateRequestModel model)\n    {\n        var user = await GetUserAsync();\n        await ValidateIfUserCanUsePasskeyLogin(user.Id);\n        var tokenable = _createOptionsDataProtector.Unprotect(model.Token);\n\n        if (!tokenable.TokenIsValid(user))\n        {\n            throw new BadRequestException(\"The token associated with your request is expired. A valid token is required to continue.\");\n        }\n\n        var credential = await _createWebAuthnLoginCredentialCommand.CreateWebAuthnLoginCredentialAsync(user, model.Name, tokenable.Options, model.DeviceResponse, model.SupportsPrf, model.EncryptedUserKey, model.EncryptedPublicKey, model.EncryptedPrivateKey);\n        if (credential == null)\n        {\n            throw new BadRequestException(\"Unable to complete WebAuthn registration.\");\n        }\n\n        return new WebAuthnCredentialResponseModel(credential);\n    }\n\n    private async Task ValidateIfUserCanUsePasskeyLogin(Guid userId)\n    {\n        var requireSsoPolicyRequirement = await _policyRequirementQuery.GetAsyncVNext<RequireSsoPolicyRequirement>(userId);\n\n        if (!requireSsoPolicyRequirement.CanUsePasskeyLogin)\n        {\n            throw new BadRequestException(\"Passkeys cannot be created for your account. SSO login is required.\");","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Auth/Controllers/WebAuthnController.cs#L93-L129","documentation":"BadRequestException 'The token associated with your request is expired...' is thrown in POST /webauthn (WebAuthnController.Post) when _createOptionsDataProtector.Unprotect(model.Token) succeeds but tokenable.TokenIsValid(user) returns false. The token is a data-protection-protected creation-options payload bound to the user; invalidity means it expired or is bound to a different user.","triggerScenarios":"POST /webauthn (passkey creation, line 111) where model.Token is expired, reused after consumption, or minted for a different user than the current principal. The user must also pass ValidateIfUserCanUsePasskeyLogin first.","commonSituations":"Delay between requesting creation options and submitting the device response, user switched accounts, token cached client-side, or the data-protection keys rotated on a self-hosted reinstall.","solutions":["Request fresh creation options (POST /webauthn/options or equivalent) and immediately complete registration.","Use the same authenticated user for options request and credential creation.","On self-hosted, ensure data-protection keys are persisted/consistent across restarts.","Do not cache the token; treat it as short-lived."],"exampleFix":"// before\napi.post('/webauthn', { token: cachedOptionsToken, deviceResponse })\n// after\nconst { token } = await api.post('/webauthn/options');\nconst deviceResponse = await navigator.credentials.create({ publicKey: parseOptions(token) });\napi.post('/webauthn', { token, name, deviceResponse });","handlingStrategy":"validation","validationCode":"if (!model.token) { model.token = (await api.post('/webauthn/options')).token; }","typeGuard":null,"tryCatchPattern":"try { await api.post('/webauthn', model); }\ncatch (e) {\n  if (e.response?.status === 400 && /expired/.test(e.response.data?.message)) {\n    model.token = (await api.post('/webauthn/options')).token;\n    throw new RetryableError('Restart passkey creation with a fresh token', model);\n  }\n  throw e;\n}","preventionTips":["Request creation options and complete the ceremony without delay.","Persist data-protection keys on self-hosted deployments.","Use the same authenticated user throughout."],"tags":["webauthn","passkey","data-protection","token"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}