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

Secret is not set.

Error message

Secret is not set.

What it means

Thrown by getSigningKey() when a symmetric algorithm (HS256/HS384/HS512) is configured and the 'secret' config value is falsy. Since buildConfig() runs in the provider constructor, the JWT service cannot be instantiated at all - the first attempt to issue or verify a token throws, usually surfacing as a 500 on authenticated routes.

Source

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

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

            return $this->getKey($privateKey, $this->getPassphrase() ?? '');
        }

        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.');
            }

View on GitHub (pinned to 6c70930a92)

Solutions

  1. Generate and persist a secret: php artisan jwt:secret (writes JWT_SECRET to .env automatically; use --force to overwrite)
  2. Confirm the entry exists in every environment: grep JWT_SECRET .env, and check the platform's env/secret store for production
  3. Run php artisan config:clear after adding the secret (a stale config cache keeps null even once .env is fixed), then re-run config:cache if you use it
  4. Use the same JWT_SECRET across all services that issue or verify these tokens

Example fix

# before
# .env has no JWT_SECRET line -> JWTException: Secret is not set.

# after
php artisan jwt:secret
# .env now contains: JWT_SECRET=... (and run: php artisan config:clear)
Defensive patterns

Strategy: validation

Validate before calling

// Fail fast at boot instead of at the first login attempt
public function boot(): void
{
    if (in_array(config('jwt.algo'), ['HS256', 'HS384', 'HS512'], true)
        && empty(config('jwt.secret'))) {
        throw new RuntimeException('JWT_SECRET is not set - run: php artisan jwt:secret');
    }
}

Type guard

function hasJwtSecret(): bool
{
    return !empty(config('jwt.secret'));
}

Try / catch

use Tymon\JWTAuth\Exceptions\JWTException;

try {
    $token = auth('api')->login($user);
} catch (JWTException $e) {
    // 'Secret is not set.' is an environment problem - alert ops, do not blame the client
    Log::critical('JWT secret missing', ['env' => app()->environment()]);
    abort(500, 'token service unavailable');
}

Prevention

When it happens

Trigger: JWT_SECRET env var missing or empty in the running environment while JWT_ALGO is HS* (the default). Triggered by resolving auth('api'), calling auth()->login($user) / JWTAuth::fromUser(), or hitting any route behind auth:api middleware for the first time.

Common situations: Fresh install where `php artisan jwt:secret` was never run; .env missing the entry locally or on a new server; a deployed environment (Docker, Kubernetes, Forge, Vapor) whose secret variables were not provisioned; php artisan config:cache run before JWT_SECRET was set, so the cached config permanently holds null.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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