passbolt/passbolt_api · error · InvalidArgumentException

The resource identifier should be a valid UUID.

Error message

The resource identifier should be a valid UUID.

What it means

JwtTokenCreateService::createToken() validates that the $userId argument is a valid UUID with Validation::uuid() before signing a JWT. A non-UUID identifier throws this InvalidArgumentException, as documented in the method's @throws annotation.

Solutions

  1. Pass the user's id from the users table ( Cake\Routing uses UuidFactory::uuid() format), e.g. $user->id from a UsersTable lookup
  2. Validate the identifier before calling: \Cake\Validation\Validation::uuid($userId)
  3. If you only have a username/email, resolve it via UsersTable->find()->where(['username' => $email])->first() and use ->id
  4. Fix test fixtures/callers that pass literal placeholder strings

Example fix

// before
$jwt = $this->jwtService->createToken($user['username']);
// after
if (!\Cake\Validation\Validation::uuid($user['id'])) {
    throw new \InvalidArgumentException('User id must be a UUID');
}
$jwt = $this->jwtService->createToken($user['id']);
Defensive patterns

Strategy: validation

Validate before calling

use Cake\Validation\Validation;
if (!Validation::uuid($userId)) { throw new \InvalidArgumentException('$userId must be a UUID'); }

Type guard

function isUuid(mixed $id): bool {
    return is_string($id) && \Cake\Validation\Validation::uuid($id);
}

Try / catch

try { $token = $service->createToken($userId); } catch (\InvalidArgumentException $e) { // resolve the real user id from UsersTable before retrying }

Prevention

When it happens

Trigger: Calling createToken($userId, $expiration) with an empty string, an integer cast to string, an email address, or any string that is not a RFC 4122 UUID (e.g. '1', 'me', 'abc').

Common situations: Passing a user's username/email instead of id; using a null/empty value after a failed user lookup; calling createToken from custom code or tests with placeholder identifiers.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/a6638ca78348dc2d. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/AccessToken/JwtTokenCreateService.php:47

{
    public const JWT_SECRET_KEY_PATH = self::JWT_CONFIG_DIR . 'jwt.key';
    public const JWT_ALG = 'RS256';
    public const JWT_KEY_LENGTH = 4096;
    public const JWT_EXPIRY_CONFIG_KEY = 'passbolt.auth.token.access_token.expiry';

    protected string $keyPath = self::JWT_SECRET_KEY_PATH;

    /**
     * @param string $userId The id of the user successfully logging in.
     * @param string|null $expiration The validity duration of the token in words (optional).
     * @return string
     * @throws \InvalidArgumentException if the userId is not a valid Uuid
     * @throws \Passbolt\JwtAuthentication\Error\Exception\AccessToken\InvalidJwtKeyPairException if the JWT secret key is not readable.
     */
    public function createToken(string $userId, ?string $expiration = null): string
    {
        if (!Validation::uuid($userId)) {
            throw new InvalidArgumentException(__('The resource identifier should be a valid UUID.'));
        }

        $privateKey = $this->readKeyFileContent();
        $payload = [
            'iss' => Router::url('/', true),
            'sub' => $userId,
            'exp' => $this->createExpiryDate($expiration),
        ];

        return JWT::encode($payload, $privateKey, self::JWT_ALG);
    }

    /**
     * Create a UNIX time from a time expressed in words.
     * This should return an integer.
     *
     * @param string|null $expirationPeriod Expiration period in words.
     * @return int Unix time

View on GitHub (pinned to 31c1bbc10f)