BookStackApp/BookStack · error · ApiAuthException
errors.api_incorrect_token_secret
Error message
errors.api_incorrect_token_secret
What it means
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.
Source
Thrown at app/Api/ApiTokenGuard.php:127
if (!str_contains($authToken, ':') || !str_starts_with($authToken, 'Token ')) {
throw new ApiAuthException(trans('errors.api_bad_authorization_format'));
}
}
/**
* Validate the given secret against the given token and ensure the token
* currently has access to the instance API.
*
* @throws ApiAuthException
*/
protected function validateToken(?ApiToken $token, string $secret): void
{
if ($token === null) {
throw new ApiAuthException(trans('errors.api_user_token_not_found'));
}
if (!Hash::check($secret, $token->secret)) {
throw new ApiAuthException(trans('errors.api_incorrect_token_secret'));
}
$now = Carbon::now();
if ($token->expires_at <= $now) {
throw new ApiAuthException(trans('errors.api_user_token_expired'), 403);
}
if (!$token->user->can(Permission::AccessApi)) {
throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
}
}
/**
* {@inheritdoc}
*/
public function validate(array $credentials = []): bool
{
if (empty($credentials['id']) || empty($credentials['secret'])) {View on GitHub (pinned to 18f8469a1c)
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
Example fix
// before
$secret = trim(env('BOOKSTACK_TOKEN_SECRET')); // actually mismatched old secret
// after
// Regenerate token in UI, then:
$secret = 'NewSecretFromTokenCreationScreen';
$headers = ['Authorization' => 'Token ' . $id . ':' . $secret]; Defensive patterns
Strategy: validation
Validate before calling
// Guard against common secret corruption before sending
$secret = trim($secret);
if (strlen($secret) < 16 || preg_match('/\s/', $secret)) {
throw new RuntimeException('BookStack token secret looks truncated or contains whitespace');
} Type guard
function looksLikeTokenSecret(?string $s): bool {
return is_string($s) && strlen($s) >= 16 && !preg_match('/\s/', $s);
} Try / catch
try {
$res = $client->get($url, ['headers' => ['Authorization' => "Token {$id}:{$secret}"]]);
} catch (ClientException $e) {
if ($e->getResponse()->getStatusCode() === 401) {
// secret mismatch: rotate the token via UI/admin and reload credentials
}
throw $e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unauthorized
- errors.email_confirmation_awaiting
- errors.api_no_authorization_found
- errors.api_bad_authorization_format
- errors.api_user_token_not_found
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/bf949ffa888beac5.
Report an issue: GitHub.