JWT Authentication for Yii2

Installs: 4

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

Type:yii2-extension

1.0.0 2025-06-12 21:01 UTC

This package is auto-updated.

Last update: 2025-06-13 06:30:39 UTC


README

Packagist Version CI Status License PHP Version

🔐 Modern, type-safe JWT for Yii2

Seamless lcobucci/jwt 5.x integration for Yii 2.0 with PHP 8.1+ support

InstallationQuick StartExamplesAPI ReferenceContributing

📋 Table of Contents

✨ Features

  • 🔒 Secure JWT Operations - Create, parse, and validate JWT tokens with industry-standard security
  • 🧩 Modern PHP - Native PHP 8.1+ types, enums, and attributes
  • 🛡️ Multiple Algorithms - HMAC, RSA, ECDSA support with easy configuration
  • Yii2 Integration - Seamless component integration with dependency injection
  • 🧑‍💻 Developer Friendly - Clean, IDE-friendly API with full type hints
  • 🔄 Flexible Configuration - Support for various key formats and validation constraints
  • 🌐 REST Ready - Built-in HTTP Bearer authentication filter

📋 Requirements

  • PHP PHP 8.1 or higher
  • Yii2 Yii2 ^2.0.52
  • JWT lcobucci/jwt ^5.0

Optional Dependencies

For time-based validation constraints, you may want to install a PSR-20 clock implementation:

composer require lcobucci/clock

📦 Installation

Install via Composer:

composer require sherinbloemendaal/yii2-jwt

🚀 Quick Start

1. Configure the Component

Add to your Yii2 application configuration:

'components' => [
    'jwt' => [
        'class' => \sherinbloemendaal\jwt\Jwt::class,
        'signer' => \sherinbloemendaal\jwt\JwtSigner::HS256,
        'signerKey' => \sherinbloemendaal\jwt\JwtKey::PLAIN_TEXT,
        'signerKeyContents' => getenv('JWT_SECRET') ?: 'your-secret-key',
        'constraints' => [
            static fn() => new \Lcobucci\JWT\Validation\Constraint\IssuedBy('https://your-app.com'),
        ],
    ],
],

2. Create Your First Token

/** @var \sherinbloemendaal\jwt\Jwt $jwt */
$jwt = Yii::$app->jwt;
$now = new DateTimeImmutable();

$token = $jwt->getBuilder()
    ->issuedBy('https://your-app.com')           // iss claim
    ->permittedFor('https://api.your-app.com')   // aud claim
    ->identifiedBy('unique-token-id')            // jti claim
    ->issuedAt($now)                            // iat claim
    ->expiresAt($now->modify('+1 hour'))        // exp claim
    ->withClaim('uid', 42)                      // custom claim
    ->withClaim('role', 'admin')                // another custom claim
    ->getToken($jwt->getSigner(), $jwt->getSignerKey());

echo $token->toString();

3. Parse and Validate Token

$tokenString = '...'; // JWT token from request

try {
    $token = $jwt->loadToken($tokenString, validate: true);
    
    // Access claims
    $userId = $token->claims()->get('uid');
    $userRole = $token->claims()->get('role');
    
    echo "Welcome user #{$userId} with role: {$userRole}";
} catch (\Exception $e) {
    echo "Invalid token: " . $e->getMessage();
}

🔧 Configuration

Basic Configuration Options

'jwt' => [
    'class' => \sherinbloemendaal\jwt\Jwt::class,
    
    // Signing algorithm
    'signer' => \sherinbloemendaal\jwt\JwtSigner::HS256,
    
    // Key configuration
    'signerKey' => \sherinbloemendaal\jwt\JwtKey::PLAIN_TEXT,
    'signerKeyContents' => 'your-secret-key',
    'signerKeyPassphrase' => '', // for encrypted keys
    
    // Validation constraints
    'constraints' => [
        static fn() => new \Lcobucci\JWT\Validation\Constraint\IssuedBy('https://your-app.com'),
    ],
],

Clock Implementations (Optional)

For time-based JWT validation (checking expiration, not-before, issued-at claims), you need a PSR-20 compatible clock:

Option 1: Using lcobucci/clock (Recommended for time validation)

composer require lcobucci/clock
// Time-based validation with SystemClock
static fn() => new \Lcobucci\JWT\Validation\Constraint\LooseValidAt(
    \Lcobucci\Clock\SystemClock::fromUTC()
),

// With leeway for clock skew
static fn() => new \Lcobucci\JWT\Validation\Constraint\LooseValidAt(
    \Lcobucci\Clock\SystemClock::fromUTC(),
    new \DateInterval('PT30S') // 30 second leeway
),

Option 2: Built-in PSR-20 Implementation (no dependencies)

static function() {
    return new \Lcobucci\JWT\Validation\Constraint\LooseValidAt(
        new class implements \Psr\Clock\ClockInterface {
            public function now(): \DateTimeImmutable
            {
                return new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
            }
        }
    );
},

Advanced Configuration

'jwt' => [
    'class' => \sherinbloemendaal\jwt\Jwt::class,
    
    // Custom encoder/decoder
    'encoder' => ['class' => \Lcobucci\JWT\Encoding\JoseEncoder::class],
    'decoder' => ['class' => \Lcobucci\JWT\Encoding\JoseEncoder::class],
    
    // Custom claims formatter
    'claimsFormatter' => [
        'class' => \sherinbloemendaal\jwt\Encoding\ChainedFormatter::class,
        'formatters' => [
            \Lcobucci\JWT\Encoding\UnifyAudience::class,
            \Lcobucci\JWT\Encoding\MicrosecondBasedDateConversion::class,
        ],
    ],
],

💡 Examples

REST API Authentication

Create a secure REST controller with JWT authentication:

<?php

use yii\rest\Controller;
use sherinbloemendaal\jwt\JwtHttpBearerAuth;

class ApiController extends Controller
{
    public function behaviors(): array
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => JwtHttpBearerAuth::class,
            'optional' => ['login', 'public-endpoint'],
        ];
        return $behaviors;
    }

    public function actionLogin(): array
    {
        // Authenticate user credentials here...
        $user = $this->authenticateUser();
        
        if (!$user) {
            throw new \yii\web\UnauthorizedHttpException('Invalid credentials');
        }

        $jwt = Yii::$app->jwt;
        $now = new DateTimeImmutable();
        
        $token = $jwt->getBuilder()
            ->issuedBy(Yii::$app->request->hostInfo)
            ->permittedFor(Yii::$app->request->hostInfo . '/api')
            ->identifiedBy(uniqid())
            ->issuedAt($now)
            ->expiresAt($now->modify('+8 hours'))
            ->withClaim('uid', $user->id)
            ->withClaim('email', $user->email)
            ->withClaim('role', $user->role)
            ->getToken($jwt->getSigner(), $jwt->getSignerKey());

        return [
            'access_token' => $token->toString(),
            'token_type' => 'Bearer',
            'expires_in' => 28800, // 8 hours in seconds
        ];
    }

    public function actionProfile(): array
    {
        $token = Yii::$app->request->getHeaders()->get('authorization');
        $parsedToken = Yii::$app->jwt->loadToken(str_replace('Bearer ', '', $token));
        
        return [
            'uid' => $parsedToken->claims()->get('uid'),
            'email' => $parsedToken->claims()->get('email'),
            'role' => $parsedToken->claims()->get('role'),
        ];
    }
}

Working with RSA Keys

'jwt' => [
    'class' => \sherinbloemendaal\jwt\Jwt::class,
    'signer' => \sherinbloemendaal\jwt\JwtSigner::RS256,
    'signerKey' => \sherinbloemendaal\jwt\JwtKey::FILE,
    'signerKeyContents' => '/path/to/private-key.pem',
    'signerKeyPassphrase' => 'optional-passphrase',
],

Custom Validation Constraints

'jwt' => [
    'class' => \sherinbloemendaal\jwt\Jwt::class,
    'constraints' => [
        // Validate issuer
        static fn() => new \Lcobucci\JWT\Validation\Constraint\IssuedBy('https://your-app.com'),
        
        // Validate audience  
        static fn() => new \Lcobucci\JWT\Validation\Constraint\PermittedFor('https://api.your-app.com'),
        
        // Custom constraint
        static function() {
            return new class implements \Lcobucci\JWT\Validation\Constraint {
                public function assert(\Lcobucci\JWT\Token $token): void
                {
                    if (!$token->claims()->has('uid')) {
                        throw new \Lcobucci\JWT\Validation\ConstraintViolation('Token must contain uid claim');
                    }
                }
            };
        },
    ],
],

Refresh Token Pattern

class AuthService
{
    public function generateTokenPair(int $userId): array
    {
        $jwt = Yii::$app->jwt;
        $now = new DateTimeImmutable();
        
        // Short-lived access token
        $accessToken = $jwt->getBuilder()
            ->issuedAt($now)
            ->expiresAt($now->modify('+15 minutes'))
            ->withClaim('uid', $userId)
            ->withClaim('type', 'access')
            ->getToken($jwt->getSigner(), $jwt->getSignerKey());
        
        // Long-lived refresh token
        $refreshToken = $jwt->getBuilder()
            ->issuedAt($now)
            ->expiresAt($now->modify('+7 days'))
            ->withClaim('uid', $userId)
            ->withClaim('type', 'refresh')
            ->getToken($jwt->getSigner(), $jwt->getSignerKey());
        
        return [
            'access_token' => $accessToken->toString(),
            'refresh_token' => $refreshToken->toString(),
            'expires_in' => 900, // 15 minutes
        ];
    }
}

🔑 Supported Algorithms

Enum Value Algorithm Key Type Required
JwtSigner::HS256 HMAC-SHA-256 Symmetric
JwtSigner::HS384 HMAC-SHA-384 Symmetric
JwtSigner::HS512 HMAC-SHA-512 Symmetric
JwtSigner::RS256 RSA-SHA-256 RSA Private
JwtSigner::RS384 RSA-SHA-384 RSA Private
JwtSigner::RS512 RSA-SHA-512 RSA Private
JwtSigner::ES256 ECDSA-SHA-256 ECDSA Private
JwtSigner::ES384 ECDSA-SHA-384 ECDSA Private
JwtSigner::ES512 ECDSA-SHA-512 ECDSA Private

Key Format Options

Enum Value Description
JwtKey::EMPTY Empty key (for testing only)
JwtKey::PLAIN_TEXT Plain text key
JwtKey::BASE64_ENCODED Base64 encoded key
JwtKey::FILE Key loaded from file

📚 API Reference

Main JWT Component

getBuilder(): BuilderInterface

Returns a token builder instance for creating new tokens.

getSigner(?JwtSigner $signer = null): SignerInterface

Returns the configured signer instance.

getSignerKey(?JwtKey $signerKey = null, ?string $contents = null, ?string $passphrase = null): KeyInterface

Returns the configured signing key.

parse(string $jwt): TokenInterface

Parses a JWT string into a token object.

validate(TokenInterface $token, ConstraintInterface ...$constraints): bool

Validates a token against the given constraints.

loadToken(string $jwt, bool $validate = true, bool $throwException = true): ?TokenInterface

Convenience method to parse and optionally validate a token.

🛠 Troubleshooting

Common Issues

"Invalid token structure"

  • Ensure the JWT string is properly formatted (header.payload.signature)
  • Check that the token hasn't been corrupted during transmission

"Token signature mismatch"

  • Verify that the same signing key is used for both creation and validation
  • Ensure the signing algorithm matches between creation and validation

"Token has expired"

  • Check the exp claim in your token
  • Consider adding leeway to your validation constraints

"Invalid key format"

  • For RSA/ECDSA keys, ensure they are in PEM format
  • Check file permissions if loading keys from files

Debug Mode

Enable debug logging by configuring Yii2's logger:

'log' => [
    'targets' => [
        [
            'class' => 'yii\log\FileTarget',
            'categories' => ['jwt'],
            'logFile' => '@runtime/logs/jwt.log',
        ],
    ],
],

Performance Tips

  • Use static caching for parser and validator instances (already implemented)
  • Consider using Redis for token blacklisting in production
  • Use appropriate token expiration times to balance security and performance

🤝 Contributing

We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

  1. Clone the repository
  2. Install dependencies: composer install
  3. Run tests: composer test
  4. Check code style: composer cs-check

Guidelines

  • Follow PSR-12 coding standards
  • Add tests for new features
  • Update documentation as needed
  • Keep backward compatibility in mind

📄 License

This project is licensed under the MIT License.

Made with ❤️ for the Yii2 community

Report BugRequest FeatureDocumentation