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

  1. Regenerate or view the token in BookStack (secrets are only shown at creation, so regenerate if lost) and update the client
  2. Trim whitespace/newlines from the secret in environment variables or config
  3. Confirm the header order is 'Token <id>:<secret>', not '<secret>:<id>'
  4. 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

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


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/bf949ffa888beac5. Report an issue: GitHub.