passbolt/passbolt_api · error · Cake\Http\Exception\ForbiddenException
You need to login to access this location.
Error message
You need to login to access this location.
What it means
GpgJwtAuthenticator.unauthenticated is invoked when JWT authentication fails or no token was provided. For JSON requests it throws ForbiddenException 'You need to login to access this location.'; non-JSON requests fall through to controller-level redirection. It is the standard passbolt signal that a valid login (JWT token or GPG auth) is required.
Solutions
- Perform a login (GPG-based /auth/login or JWT verification flow) to obtain fresh access and refresh tokens.
- Use the refresh token against /auth/jwt/refresh to get a new access token when the access token expired.
- Send the header exactly as 'Authorization: Bearer <access_token>' and ensure Accept: application/json.
- Check client/server clock synchronization if tokens seem to expire immediately.
- Confirm the user account is active and not disabled.
Example fix
// before: raw request without token
fetch('/resources.json');
// after: attach token and refresh on 403
const res = await fetch('/resources.json', {
headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }
});
if (res.status === 403) await refreshAccessToken(); Defensive patterns
Strategy: try-catch
Validate before calling
const isExpired = (tok) => Date.now() >= JSON.parse(atob(tok.split('.')[1])).exp * 1000;
if (!accessToken || isExpired(accessToken)) await refreshOrLogin(); Type guard
function hasBearer(auth) { return typeof auth === 'string' && auth.startsWith('Bearer ') && auth.length > 7; } Try / catch
try { return await api(path); } catch (e) { if (e.message.includes('You need to login')) { await refreshAccessToken(); return api(path); } throw e; } Prevention
- Implement automatic token refresh before access-token expiry.
- Always send Authorization: Bearer and Accept: application/json headers.
- Detect this message client-side and redirect to the login flow.
- Keep server and client clocks synchronized (NTP).
When it happens
Trigger: Calling a JSON endpoint that requires authentication with: no Authorization header; an expired or invalid JWT; a missing/malformed 'Bearer <token>' prefix; a token signed by a revoked key. The request must have the JSON Accept/header for this exception path.
Common situations: Access token expired (default short-lived JWT) and the client didn't refresh it; scripts/CI hitting the API without logging in; clock skew invalidating tokens; users logged out or account disabled.
Related errors
- Could not import the user OpenPGP key.
- The domain is invalid. Expected
- The user signature could not be verified.
- Attempt to access an expired verify token.
- Expired refresh token provided.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/ad17922be4cb7e2e.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/JwtAuthentication/src/Authenticator/GpgJwtAuthenticator.php:89
/**
* @var \App\Model\Entity\User $user user
* @access protected
*/
protected User $user;
/**
* When an unauthenticated user tries to access a protected page this method is called
*
* @param \Cake\Http\ServerRequest $request interface for accessing request parameters
* @param \Cake\Http\Response $response features and functionality for generating HTTP responses
* @throws \Cake\Http\Exception\ForbiddenException
* @return void
*/
public function unauthenticated(ServerRequest $request, Response $response): void
{
// If it's JSON we show an error message
if ($request->is('json')) {
throw new ForbiddenException(__('You need to login to access this location.'));
}
// Otherwise we let the controller handle the redirections
}
/**
* Authenticate
*
* @param \Psr\Http\Message\ServerRequestInterface $request interface for accessing request parameters
* @return \Authentication\Authenticator\ResultInterface User|false the user or false if authentication failed
*/
public function authenticate(ServerRequestInterface $request): ResultInterface
{
/** @var \Cake\Http\ServerRequest $request */
try {
$this->setRequest($request);
$this->init();
$verifyToken = $this->verifyChallenge();View on GitHub (pinned to 31c1bbc10f)