tymondesigns/jwt-auth · critical · Tymon\JWTAuth\Exceptions\JWTException

Public key is not set.

Error message

Public key is not set.

What it means

Thrown by getVerificationKey() when an asymmetric algorithm (RS*/ES*) is configured but the keys.public config value is null or empty. The provider needs the public key at construction time both to build the SignedWith validation constraint and for verification, so the service fails to instantiate on first use.

Source

Thrown at src/Providers/JWT/Lcobucci.php:255

        if (! $secret = $this->getSecret()) {
            throw new JWTException('Secret is not set.');
        }

        return $this->getKey($secret);
    }

    /**
     * {@inheritdoc}
     *
     * @return \Lcobucci\JWT\Signer\Key
     *
     * @throws \Tymon\JWTAuth\Exceptions\JWTException
     */
    protected function getVerificationKey()
    {
        if ($this->isAsymmetric()) {
            if (! $public = $this->getPublicKey()) {
                throw new JWTException('Public key is not set.');
            }

            return $this->getKey($public);
        }

        if (! $secret = $this->getSecret()) {
            throw new JWTException('Secret is not set.');
        }

        return $this->getKey($secret);
    }

    /**
     * Get the signing key instance.
     */
    protected function getKey(string $contents, string $passphrase = ''): Key
    {
        return InMemory::plainText($contents, $passphrase);

View on GitHub (pinned to 6c70930a92)

Solutions

  1. Export the public half of your existing private key: openssl rsa -in private.pem -pubout -out public.pem (use -passin if encrypted)
  2. Set JWT_PUBLIC_KEY in every environment, value being the full PEM contents including BEGIN/END lines
  3. Run php artisan config:clear to drop the stale cached config, then re-cache if used
  4. Make sure the public key belongs to the same pair as JWT_PRIVATE_KEY, otherwise signing works but every verification fails with 'Token Signature could not be verified.'

Example fix

# before
JWT_ALGO=RS256
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# JWT_PUBLIC_KEY unset -> JWTException: Public key is not set.

# after
openssl rsa -in private.pem -pubout -out public.pem
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBg...\n-----END PUBLIC KEY-----\n"
Defensive patterns

Strategy: validation

Validate before calling

// Boot guard: asymmetric algorithms need BOTH keys at construction time
public function boot(): void
{
    $asymmetric = in_array(config('jwt.algo'), ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'], true);

    if ($asymmetric && empty(config('jwt.keys.public'))) {
        throw new RuntimeException('jwt.keys.public must be set for algorithm '.config('jwt.algo'));
    }
}

Type guard

function hasCompleteAsymmetricKeys(array $keysConfig): bool
{
    return !empty($keysConfig['private'])
        && !empty($keysConfig['public'])
        && openssl_pkey_get_public($keysConfig['public']) !== false;
}

Try / catch

use Tymon\JWTAuth\Exceptions\JWTException;

try {
    auth('api')->parseToken()->authenticate();
} catch (JWTException $e) {
    // 'Public key is not set.' fires at service construction - treat as 500 config failure
    report($e);
    abort(500, 'token service unavailable');
}

Prevention

When it happens

Trigger: JWT_ALGO set to RS256/RS384/RS512/ES256/ES384/ES512 with JWT_PUBLIC_KEY unset or empty. Even token *creation* fails, because buildConfig() registers a SignedWith(verification) constraint at construction; triggered by auth('api') resolution, auth:api middleware, or resolving 'tymon.jwt' from the container.

Common situations: Developer sets only JWT_PRIVATE_KEY (assuming verification needs nothing), deploys with the private key but forgets the public key in production secrets, or regenerates the key pair and only updates one side; config cached before the env value existed.

Related errors


AI-assisted analysis of tymondesigns/jwt-auth@6c70930a92 (2026-08-21). Data as JSON: /api/errors/ee2f7588b7830913. Report an issue: GitHub.