BookStackApp/BookStack · error · ApiAuthException
Unauthorized
Error message
Unauthorized
What it means
ApiTokenGuard::authenticate() throws ApiAuthException('Unauthorized') when no earlier exception was recorded and the request's API token could not be resolved to an authorised user. It is the generic fallback for failed API token authentication (missing/unknown token, failed secret check) when a more specific exception was not already stored in lastAuthException.
Source
Thrown at app/Api/ApiTokenGuard.php:71
/**
* Determine if the current user is authenticated. If not, throw an exception.
*
* @throws ApiAuthException
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*/
public function authenticate()
{
if (!is_null($user = $this->user())) {
return $user;
}
if ($this->lastAuthException) {
throw $this->lastAuthException;
}
throw new ApiAuthException('Unauthorized');
}
/**
* Check the API token in the request and fetch a valid authorised user.
*
* @throws ApiAuthException
*/
protected function getAuthorisedUserFromRequest(): Authenticatable
{
$authToken = trim($this->request->headers->get('Authorization', ''));
$this->validateTokenHeaderValue($authToken);
[$id, $secret] = explode(':', str_replace('Token ', '', $authToken));
$token = ApiToken::query()
->where('token_id', '=', $id)
->with(['user'])->first();
$this->validateToken($token, $secret);View on GitHub (pinned to 18f8469a1c)
Solutions
- Verify the Authorization header uses the format 'Token id:secret' with values from the user's API token settings.
- Regenerate the token in the user profile API settings and update the client credentials.
- Confirm the request targets the same BookStack instance the token was created on.
- Catch ApiAuthException client-side and check the 401 response before retrying.
Example fix
// before
$response = $http->get($url, ['headers' => ['Authorization' => $apiKey]]);
// after
$authHeader = 'Token ' . $tokenId . ':' . $tokenSecret;
$response = $http->get($url, ['headers' => ['Authorization' => $authHeader]]);
if ($response->status() === 401) {
// regenerate token and refresh credentials
} Defensive patterns
Strategy: try-catch
Validate before calling
if (empty($tokenId) || empty($tokenSecret)) {
throw new InvalidArgumentException('API token id and secret are required.');
} Try / catch
try {
$response = $client->get($apiUrl);
} catch (ApiAuthException | ClientException $e) {
if ($e->getCode() === 401) {
// regenerate token / refresh credentials before retrying
}
throw $e;
} Prevention
- Store token id and secret as separate config values and build 'Token id:secret' explicitly.
- Rotate and verify tokens after environment changes.
- Check token existence and owner account status before deploying integrations.
When it happens
Trigger: Any API request whose Authorization header token does not match a valid token row (unknown token id, wrong secret, nonexistent/deleted token/user), reaching the final throw in authenticate().
Common situations: Sending an API request without creating a token in the user's API settings; copying a token from the wrong environment/instance; token revoked or its owner deleted; typos in the token string; forgetting the 'Token ' prefix or secret part (though those usually raise the more specific errors 78/79).
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- errors.email_confirmation_awaiting
- errors.api_no_authorization_found
- errors.api_bad_authorization_format
- errors.api_user_token_not_found
- errors.api_incorrect_token_secret
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/dfd9722598eec63d.
Report an issue: GitHub.