{"record":{"id":"6589df0ec34f0e7b","repo":"passbolt/passbolt_api","slug":"the-authentication-token-is-not-valid-6589df","errorCode":null,"errorMessage":"The authentication token is not valid.","messagePattern":"The authentication token is not valid\\.","errorType":"validation","errorClass":"CustomValidationException","httpStatus":400,"severity":"error","filePath":"src/Service/AuthenticationTokens/AuthenticationTokenConsumeService.php","lineNumber":57,"sourceCode":"     * @throws \\Cake\\Http\\Exception\\NotFoundException if token is not found\n     * @throws \\App\\Error\\Exception\\CustomValidationException if the token is expired, inactive, or lost a concurrent-consume race\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if token id is not a valid uuid\n     */\n    public function consumeActiveNotExpiredOrFail(\n        string $token,\n        string $userId,\n        string $type,\n        ?string $expiry = null\n    ): AuthenticationToken {\n        $authenticationToken = (new AuthenticationTokenGetService())\n            ->getActiveNotExpiredOrFail($token, $userId, $type, $expiry);\n\n        /** @var \\App\\Model\\Table\\AuthenticationTokensTable $authTokensTable */\n        $authTokensTable = $this->fetchTable('AuthenticationTokens');\n        if (!$authTokensTable->setInactive($authenticationToken->token)) {\n            // Lost the concurrent-consume race — mirror `getActiveOrFail`'s \"already consumed\" contract.\n            $error = ['token' => ['isActive' => __('The token is already consumed.')]];\n            throw new CustomValidationException(__('The authentication token is not valid.'), $error);\n        }\n\n        $authenticationToken->set('active', false);\n        $authenticationToken->setDirty('active', false);\n\n        return $authenticationToken;\n    }\n}\n","sourceCodeStart":39,"sourceCodeEnd":66,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/AuthenticationTokens/AuthenticationTokenConsumeService.php#L39-L66","documentation":"consumeActiveNotExpiredOrFail atomically deactivates the token via setInactive(). If the update affects no rows, another request already consumed the token in a race, so this method throws CustomValidationException('The authentication token is not valid.') with a 'token.isActive' error mirroring getActiveOrFail's contract.","triggerScenarios":"Two requests consume the same valid, unexpired register/complete-setup token concurrently and both pass getActiveNotExpiredOrFail; the loser's setInactive() returns false.","commonSituations":"User double-clicks a setup link or the setup page auto-submits while the user clicks manually; duplicated email-link deliveries; retries after slow responses.","solutions":["Treat the exception as a consumed-token case and show the user an 'already used link' message.","Reload the setup/complete page to get a fresh token state and an accurate error.","Idempotent-guard clients: disable the submit action after the first request.","If the user is actually stuck, issue a new authentication token (e.g. resend setup email)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    $token = $service->consumeActiveNotExpiredOrFail($token, $userId, $type);\n} catch (CustomValidationException $e) {\n    $errors = $e->getErrors();\n    if (isset($errors['token']['isActive'])) {\n        // token already consumed — show 'link already used' page\n    }\n}","preventionTips":["Disable submit buttons after first request to avoid double consumption","Detect the token.isActive error key to give accurate UX","Educate users that links are single-use"],"tags":["php","auth-tokens","race-condition"],"backgroundTag":"invalid-state-transition","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"}