{"record":{"id":"e339f16b31c0cdd5","repo":"passbolt/passbolt_api","slug":"the-authentication-token-is-not-valid-e339f1","errorCode":null,"errorMessage":"The authentication token is not valid.","messagePattern":"The authentication token is not valid\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Service/Setup/RecoverStartUserInfoService.php","lineNumber":62,"sourceCode":"        return $data;\n    }\n\n    /**\n     * Find the recover token\n     *\n     * @param string $token uuid of the token\n     * @param \\App\\Model\\Entity\\User $user user attempting to recover\n     * @return void\n     * @throw Custom if the token is not valid\n     * @throw BadRequestException if the token is not valid\n     */\n    private function assertAuthToken(string $token, User $user): void\n    {\n        try {\n            (new AuthenticationTokenGetService())\n                ->getActiveNotExpiredOrFail($token, $user->id, AuthenticationToken::TYPE_RECOVER);\n        } catch (NotFoundException $exception) {\n            throw new BadRequestException(__('The authentication token is not valid.'));\n        }\n    }\n}\n","sourceCodeStart":44,"sourceCodeEnd":66,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/Setup/RecoverStartUserInfoService.php#L44-L66","documentation":"Thrown by assertAuthToken in RecoverStartUserInfoService when AuthenticationTokenGetService::getActiveNotExpiredOrFail finds no active, unexpired RECOVER token for the given token id and user id. The recover start step requires proof of a valid token before disclosing user info.","triggerScenarios":"Calling recover start with an expired token, an already-consumed token, a token of the wrong type (e.g. REGISTER instead of RECOVER), a token belonging to a different user, or a bogus token id.","commonSituations":"Clicking an old recover email after requesting a newer one; waiting past the token expiry window; mixing up setup (registration) and recover token links; hand-copying the token id with truncation/extra characters.","solutions":["Restart the recovery process to generate and receive a fresh RECOVER token, then use the new link","Verify the token row: type=RECOVER, active=1, and not expired, and that user_id matches the URL user id","Make sure you are using the recover link, not the initial setup/invite link","Check clock/timezone skew is not expiring tokens prematurely on the server"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const t = await getTokenRow(tokenId);\nconst valid = t && t.type === 'recover' && t.active && new Date(t.expired) > new Date() && t.user_id === userId;","typeGuard":null,"tryCatchPattern":"try { const info = await recoverStartInfo(userId, token, data); }\ncatch (e) { if (isInvalidToken(e)) startNewRecoveryFlow(userId); else throw e; }","preventionTips":["Always use the newest recovery email's token","Check token type (RECOVER, not REGISTER/SETUP)","Mind the expiry window; restart recovery if it lapsed","Ensure user id matches the token owner and watch for server clock skew"],"tags":["recover","authentication-token","token-expired"],"backgroundTag":"invalid-identifier","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}