{"record":{"id":"bf949ffa888beac5","repo":"BookStackApp/BookStack","slug":"errors-api-incorrect-token-secret","errorCode":null,"errorMessage":"errors.api_incorrect_token_secret","messagePattern":"errors\\.api_incorrect_token_secret","errorType":"exception","errorClass":"ApiAuthException","httpStatus":null,"severity":"error","filePath":"app/Api/ApiTokenGuard.php","lineNumber":127,"sourceCode":"        if (!str_contains($authToken, ':') || !str_starts_with($authToken, 'Token ')) {\n            throw new ApiAuthException(trans('errors.api_bad_authorization_format'));\n        }\n    }\n\n    /**\n     * Validate the given secret against the given token and ensure the token\n     * currently has access to the instance API.\n     *\n     * @throws ApiAuthException\n     */\n    protected function validateToken(?ApiToken $token, string $secret): void\n    {\n        if ($token === null) {\n            throw new ApiAuthException(trans('errors.api_user_token_not_found'));\n        }\n\n        if (!Hash::check($secret, $token->secret)) {\n            throw new ApiAuthException(trans('errors.api_incorrect_token_secret'));\n        }\n\n        $now = Carbon::now();\n        if ($token->expires_at <= $now) {\n            throw new ApiAuthException(trans('errors.api_user_token_expired'), 403);\n        }\n\n        if (!$token->user->can(Permission::AccessApi)) {\n            throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function validate(array $credentials = []): bool\n    {\n        if (empty($credentials['id']) || empty($credentials['secret'])) {","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Api/ApiTokenGuard.php#L109-L145","documentation":"ApiTokenGuard::validateToken throws ApiAuthException('errors.api_incorrect_token_secret') when Hash::check fails: the supplied secret does not match the hashed secret stored for the found ApiToken. The token id was valid, but the credential half of the pair is wrong.","triggerScenarios":"Sending a stale or regenerated secret with a still-valid token id; truncating the secret (copy/paste cut); swapping the id and secret halves in the header; whitespace or newline appended to the secret in an env var.","commonSituations":"The token was regenerated in the UI (invalidating the old secret) but the client kept the old secret; secrets stored in .env files with trailing spaces or unescaped characters; multiple tokens and the wrong secret paired with an id.","solutions":["Regenerate or view the token in BookStack (secrets are only shown at creation, so regenerate if lost) and update the client","Trim whitespace/newlines from the secret in environment variables or config","Confirm the header order is 'Token <id>:<secret>', not '<secret>:<id>'","Update all deployed clients/cron jobs after any token rotation"],"exampleFix":"// before\n$secret = trim(env('BOOKSTACK_TOKEN_SECRET')); // actually mismatched old secret\n// after\n// Regenerate token in UI, then:\n$secret = 'NewSecretFromTokenCreationScreen';\n$headers = ['Authorization' => 'Token ' . $id . ':' . $secret];","handlingStrategy":"validation","validationCode":"// Guard against common secret corruption before sending\n$secret = trim($secret);\nif (strlen($secret) < 16 || preg_match('/\\s/', $secret)) {\n    throw new RuntimeException('BookStack token secret looks truncated or contains whitespace');\n}","typeGuard":"function looksLikeTokenSecret(?string $s): bool {\n    return is_string($s) && strlen($s) >= 16 && !preg_match('/\\s/', $s);\n}","tryCatchPattern":"try {\n    $res = $client->get($url, ['headers' => ['Authorization' => \"Token {$id}:{$secret}\"]]);\n} catch (ClientException $e) {\n    if ($e->getResponse()->getStatusCode() === 401) {\n        // secret mismatch: rotate the token via UI/admin and reload credentials\n    }\n    throw $e;\n}","preventionTips":["Regenerate the token in the UI after any suspected leak and update all clients","Avoid editing secrets by hand; load them from a secrets manager","Watch for token rotations by other admins and rotate your client at the same time","Never swap the id and secret halves in the header"],"tags":["api","authentication","credentials"],"backgroundTag":"api-token-secret-mismatch","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}