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

  1. Perform a login (GPG-based /auth/login or JWT verification flow) to obtain fresh access and refresh tokens.
  2. Use the refresh token against /auth/jwt/refresh to get a new access token when the access token expired.
  3. Send the header exactly as 'Authorization: Bearer <access_token>' and ensure Accept: application/json.
  4. Check client/server clock synchronization if tokens seem to expire immediately.
  5. 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

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


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)