yii2-jwt

by sizeg

sizeg / yii2-jwt

A simple Yii2 component to work with JSON Web Token and JSON Web Signature

142 Stars 33 Forks Last release: 2 months ago (v2.0.1.1) 61 Commits 11 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Yii2 JWT

This extension provides the JWT integration for the Yii framework 2.0 (requires PHP 5.6+). It includes basic HTTP authentication support.

Table of contents

  1. Installation
  2. Dependencies
  3. Basic usage
    1. Creating
    2. Parsing from strings
    3. Validating
  4. Token signature
    1. Hmac
    2. RSA and ECDSA
  5. Yii2 basic template example

Installation

Package is available on Packagist, you can install it using Composer.

composer require sizeg/yii2-jwt

Dependencies

Basic usage

Add

jwt
component to your configuration file,
'components' => [
    'jwt' => [
      'class' => \sizeg\jwt\Jwt::class,
      'key'   => 'secret',
    ],
],

Configure the

authenticator
behavior as follows.
namespace app\controllers;

class ExampleController extends \yii\rest\Controller {

/**
 * @inheritdoc
 */
public function behaviors()
{
    $behaviors = parent::behaviors();
    $behaviors['authenticator'] = [
        'class' => \sizeg\jwt\JwtHttpBearerAuth::class,
    ];

    return $behaviors;
}

}

Also you can use it with

CompositeAuth
reffer to a doc.

Creating

Some methods marked as deprecated and will soon backport things from lcobucci/jwt 4.x to create an upgrade path.

Just use the builder to create a new JWT/JWS tokens:

$time = time();
$token = Yii::$app->jwt->getBuilder()
            ->issuedBy('http://example.com') // Configures the issuer (iss claim)
            ->permittedFor('http://example.org') // Configures the audience (aud claim)
            ->identifiedBy('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
            ->issuedAt($time) // Configures the time that the token was issue (iat claim)
            ->canOnlyBeUsedAfter($time + 60) // Configures the time that the token can be used (nbf claim)
            ->expiresAt($time + 3600) // Configures the expiration time of the token (exp claim)
            ->withClaim('uid', 1) // Configures a new claim, called "uid"
            ->getToken(); // Retrieves the generated token


$token->getHeaders(); // Retrieves the token headers $token->getClaims(); // Retrieves the token claims

echo $token->getHeader('jti'); // will print "4f1g23a12aa" echo $token->getClaim('iss'); // will print "http://example.com" echo $token->getClaim('uid'); // will print "1" echo $token; // The string representation of the object is a JWT string (pretty easy, right?)

Parsing from strings

Use the parser to create a new token from a JWT string (using the previous token as example):

$token = Yii::$app->jwt->getParser()->parse((string) $token); // Parses from a string
$token->getHeaders(); // Retrieves the token header
$token->getClaims(); // Retrieves the token claims

echo $token->getHeader('jti'); // will print "4f1g23a12aa" echo $token->getClaim('iss'); // will print "http://example.com" echo $token->getClaim('uid'); // will print "1"

Validating

We can easily validate if the token is valid (using the previous token as example):

$data = Yii::$app->jwt->getValidationData(); // It will use the current time to validate (iat, nbf and exp)
$data->setIssuer('http://example.com');
$data->setAudience('http://example.org');
$data->setId('4f1g23a12aa');

var_dump($token->validate($data)); // false, because we created a token that cannot be used before of time() + 60

$data->setCurrentTime(time() + 61); // changing the validation time to future

var_dump($token->validate($data)); // true, because validation information is equals to data contained on the token

$data->setCurrentTime(time() + 4000); // changing the validation time to future

var_dump($token->validate($data)); // false, because token is expired since current time is greater than exp

We can also use the $leeway parameter to deal with clock skew (see notes below). If token's claimed time is invalid but the difference between that and the validation time is less than $leeway, then token is still considered valid

php
'components' => [
    'jwt' => [
        'class' => \sizeg\jwt\Jwt:class,
        'key'   => 'secret',
        'jwtValidationData' => [
            'class' => \sizeg\jwt\JwtValidationData::class,
             // configure leeway 
            'leeway' => 20,
        ],
    ],
],
$dataWithLeeway = Yii::$app->jwt->getValidationData();
$dataWithLeeway->setIssuer('http://example.com');
$dataWithLeeway->setAudience('http://example.org');
$dataWithLeeway->setId('4f1g23a12aa');

var_dump($token->validate($dataWithLeeway)); // false, because token can't be used before now() + 60, not within leeway

$dataWithLeeway->setCurrentTime($time + 61); // changing the validation time to future

var_dump($token->validate($dataWithLeeway)); // true, because current time plus leeway is between "nbf" and "exp" claims

$dataWithLeeway->setCurrentTime($time + 3610); // changing the validation time to future but within leeway

var_dump($token->validate($dataWithLeeway)); // true, because current time - 20 seconds leeway is less than exp

$dataWithLeeway->setCurrentTime($time + 4000); // changing the validation time to future outside of leeway

var_dump($token->validate($dataWithLeeway)); // false, because token is expired since current time is greater than exp

Important

  • You have to configure
    ValidationData
    informing all claims you want to validate the token.
  • If
    ValidationData
    contains claims that are not being used in token or token has claims that are not configured in
    ValidationData
    they will be ignored by
    Token::validate()
    .
  • exp
    ,
    nbf
    and
    iat
    claims are configured by default in
    ValidationData::__construct()
    with the current UNIX time (
    time()
    ).
  • The optional
    $leeway
    parameter of
    ValidationData
    will cause us to use that number of seconds of leeway when validating the time-based claims, pretending we are further in the future for the "Issued At" (
    iat
    ) and "Not Before" (
    nbf
    ) claims and pretending we are further in the past for the "Expiration Time" (
    exp
    ) claim. This allows for situations where the clock of the issuing server has a different time than the clock of the verifying server, as mentioned in section 4.1 of RFC 7519.

Token signature

We can use signatures to be able to verify if the token was not modified after its generation. This extension implements Hmac, RSA and ECDSA signatures (using 256, 384 and 512).

Important

Do not allow the string sent to the Parser to dictate which signature algorithm to use, or else your application will be vulnerable to a critical JWT security vulnerability.

The examples below are safe because the choice in

Signer
is hard-coded and cannot be influenced by malicious users.

Hmac

Hmac signatures are really simple to be used:

$jwt = Yii::$app->jwt;
$signer = $jwt->getSigner('HS256');
$key = $jwt->getKey();
$time = time();

$token = $jwt->getBuilder() ->issuedBy('http://example.com') // Configures the issuer (iss claim) ->permittedFor('http://example.org') // Configures the audience (aud claim) ->identifiedBy('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item ->issuedAt($time) // Configures the time that the token was issue (iat claim) ->canOnlyBeUsedAfter($time + 60) // Configures the time that the token can be used (nbf claim) ->expiresAt($time + 3600) // Configures the expiration time of the token (exp claim) ->withClaim('uid', 1) // Configures a new claim, called "uid" ->getToken($signer, $key); // Retrieves the generated token

var_dump($token->verify($signer, 'testing 1')); // false, because the key is different var_dump($token->verify($signer, 'testing')); // true, because the key is the same

RSA and ECDSA

RSA and ECDSA signatures are based on public and private keys so you have to generate using the private key and verify using the public key:

$jwt = Yii::$app->jwt;
$signer = $jwt->getSigner('RS256'); // you can use 'ES256' if you're using ECDSA keys
$privateKey = $jwt->getKey('file://{path to your private key}');
$time = time();

$token = $jwt->getBuilder() ->issuedBy('http://example.com') // Configures the issuer (iss claim) ->permittedFor('http://example.org') // Configures the audience (aud claim) ->identifiedBy('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item ->issuedAt($time) // Configures the time that the token was issue (iat claim) ->canOnlyBeUsedAfter($time + 60) // Configures the time that the token can be used (nbf claim) ->expiresAt($time + 3600) // Configures the expiration time of the token (exp claim) ->withClaim('uid', 1) // Configures a new claim, called "uid" ->getToken($signer, $privateKey); // Retrieves the generated token

$publicKey = $jwt->getKey('file://{path to your public key}');

var_dump($token->verify($signer, $publicKey)); // true when the public key was generated by the private one =)

It's important to say that if you're using RSA keys you shouldn't invoke ECDSA signers (and vice-versa), otherwise

sign()
and
verify()
will raise an exception!

Yii2 basic template example

Basic scheme

  1. Client send credentials. For example, login + password
  2. Backend validate them
  3. If credentials is valid client receive token
  4. Client store token for the future requests

Step-by-step usage example

  1. Create Yii2 application

    In this example we will use basic template, but you can use advanced template in the same way.

    composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic yii2-jwt-test
    
  2. Install component

    composer require sizeg/yii2-jwt
    
  3. Add to config/web.php into

    components
    section
    $config = [
        'components' => [
            // other default components here..
            'jwt' => [
                'class' => \sizeg\jwt\Jwt::class,
                'key' => 'secret',
                // You have to configure ValidationData informing all claims you want to validate the token.
                'jwtValidationData' => \app\components\JwtValidationData::class,
            ],
        ],
    ];
    
  4. Create JwtValidationData class. Where you have to configure ValidationData informing all claims you want to validate the token.

    class JwtValidationData extends \sizeg\jwt\JwtValidationData
    {

    /**
     * @inheritdoc
     */
    public function init()
    {
        $this->validationData->setIssuer('http://example.com');
        $this->validationData->setAudience('http://example.org');
        $this->validationData->setId('4f1g23a12aa');
    
        parent::init();
    }

    }

  5. Change method

    app\models\User::findIdentityByAccessToken()
        /**
         * {@inheritdoc}
         * @param \Lcobucci\JWT\Token $token
         */
        public static function findIdentityByAccessToken($token, $type = null)
        {
            foreach (self::$users as $user) {
                if ($user['id'] === (string) $token->getClaim('uid')) {
                    return new static($user);
                }
            }
    
    
        return null;
    }

  6. Create controller

    use sizeg\jwt\Jwt;
    use sizeg\jwt\JwtHttpBearerAuth;
    use Yii;
    use yii\rest\Controller;

    class RestController extends Controller { /** * @inheritdoc */ public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => JwtHttpBearerAuth::class, 'optional' => [ 'login', ], ];

        return $behaviors;
    }
    
    /**
     * @return \yii\web\Response
     */
    public function actionLogin()
    {
        /** @var Jwt $jwt */
        $jwt = Yii::$app->jwt;
        $signer = $jwt->getSigner('HS256');
        $key = $jwt->getKey();
        $time = time();
    
        // Previous implementation
        /*
        $token = $jwt->getBuilder()
            ->setIssuer('http://example.com')// Configures the issuer (iss claim)
            ->setAudience('http://example.org')// Configures the audience (aud claim)
            ->setId('4f1g23a12aa', true)// Configures the id (jti claim), replicating as a header item
            ->setIssuedAt(time())// Configures the time that the token was issue (iat claim)
            ->setExpiration(time() + 3600)// Configures the expiration time of the token (exp claim)
            ->set('uid', 100)// Configures a new claim, called "uid"
            ->sign($signer, $jwt->key)// creates a signature using [[Jwt::$key]]
            ->getToken(); // Retrieves the generated token
        */
    
        // Adoption for lcobucci/jwt ^4.0 version
        $token = $jwt->getBuilder()
            ->issuedBy('http://example.com')// Configures the issuer (iss claim)
            ->permittedFor('http://example.org')// Configures the audience (aud claim)
            ->identifiedBy('4f1g23a12aa', true)// Configures the id (jti claim), replicating as a header item
            ->issuedAt($time)// Configures the time that the token was issue (iat claim)
            ->expiresAt($time + 3600)// Configures the expiration time of the token (exp claim)
            ->withClaim('uid', 100)// Configures a new claim, called "uid"
            ->getToken($signer, $key); // Retrieves the generated token
    
        return $this->asJson([
            'token' => (string)$token,
        ]);
    }
    
    /**
     * @return \yii\web\Response
     */
    public function actionData()
    {
        return $this->asJson([
            'success' => true,
        ]);
    }

    }

  7. Send simple login request to get token. Here we does not send any credentials to simplify example. As we specify in

    authenticator
    behavior action
    login
    as optional the
    authenticator
    skip auth check for that action. image
  8. First of all we try to send request to rest/data without token and getting error

    Unauthorized
    image
  9. Then we retry request but already adding

    Authorization
    header with our token image

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.