├── tests ├── bootstrap.php ├── phpunit.xml.dist ├── JWTToolsTest.php └── JWTPayloadTest.php ├── .gitignore ├── .editorconfig ├── LICENSE ├── src ├── JWTSignatureBehavior.php ├── JWTPayload.php └── JWTTools.php ├── composer.json └── README.md /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | ./ 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kilderson Sena 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/JWTSignatureBehavior.php: -------------------------------------------------------------------------------- 1 | request->getHeaders()->get($this->headerName); 28 | 29 | if (!$authorizationHeader) { 30 | throw new UnauthorizedHttpException('Your request was made without an authorization token.'); 31 | } 32 | 33 | $token = explode(' ', $authorizationHeader)[1]; 34 | 35 | $jwtTools = JWTTools::build($this->secretKey); 36 | 37 | if ($jwtTools->tokenIsExpired($token)) { 38 | throw new UnauthorizedHttpException('Authentication token is expired.'); 39 | } 40 | 41 | if (!$jwtTools->signatureIsValid($token)) { 42 | throw new UnauthorizedHttpException('The token signature is invalid.'); 43 | } 44 | 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astrotechlabs/yii2-jwt-tools", 3 | "type": "yii2-extension", 4 | "description": "An easy way to configure JWT authentication and validation on Yii Framework 2 Projects", 5 | "license": "MIT", 6 | "minimum-stability": "dev", 7 | "keywords": [ 8 | "yii2", 9 | "yii2-framework", 10 | "yii2-extension", 11 | "yii2-authentication", 12 | "yii2-jwt", 13 | "yii2-jwt-auth", 14 | "yii2-security", 15 | "yii2-jwt-tools" 16 | ], 17 | "scripts": { 18 | "test": "phpunit --testdox --do-not-cache-result --configuration tests/phpunit.xml", 19 | "php-cs": "phpcs ./src" 20 | }, 21 | "authors": [ 22 | { 23 | "name": "Kilderson Sena", 24 | "email": "kilderson@astrotech.solutions", 25 | "role": "Developer" 26 | } 27 | ], 28 | "autoload": { 29 | "psr-4": { 30 | "DersonSena\\JWTTools\\": "src", 31 | "AstrotechLabs\\JWTTools\\": "src" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "classmap": [ 36 | "tests/" 37 | ] 38 | }, 39 | "require": { 40 | "php": ">=8", 41 | "firebase/php-jwt": "^6.1", 42 | "yiisoft/yii2": "^2.0" 43 | }, 44 | "require-dev": { 45 | "squizlabs/php_codesniffer": "*", 46 | "friendsofphp/php-cs-fixer": "^2.15", 47 | "phpunit/phpunit": "^8.4" 48 | }, 49 | "config": { 50 | "process-timeout": 1800, 51 | "fxp-asset": { 52 | "enabled": false 53 | } 54 | }, 55 | "repositories": [ 56 | { 57 | "type": "composer", 58 | "url": "https://asset-packagist.org" 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /src/JWTPayload.php: -------------------------------------------------------------------------------- 1 | iat = $payloadAttrs['iat'] ?? $now->getTimestamp(); 26 | $this->exp = $payloadAttrs['exp'] ?? $now->add(new DateInterval("PT3600S"))->getTimestamp(); 27 | $this->iss = $payloadAttrs['iss'] ?? ''; 28 | $this->aud = $payloadAttrs['aud'] ?? ''; 29 | $this->sub = $payloadAttrs['sub'] ?? $this->generateHash(); 30 | $this->jti = $payloadAttrs['jti'] ?? $this->generateHash(); 31 | 32 | if (!isset($payloadAttrs['extraParams'])) { 33 | return; 34 | } 35 | 36 | foreach ($payloadAttrs['extraParams'] as $name => $value) { 37 | $this->addExtraAttribute($name, $value); 38 | } 39 | } 40 | 41 | /** 42 | * @param array $payloadAttrs 43 | * @return static 44 | */ 45 | public static function build(array $payloadAttrs = []): self 46 | { 47 | return new self($payloadAttrs); 48 | } 49 | 50 | /** 51 | * @param string $attribute 52 | * @throws InvalidArgumentException 53 | * @return string | int 54 | */ 55 | public function get(string $attribute) 56 | { 57 | if (!property_exists($this, $attribute)) { 58 | throw new InvalidArgumentException("Payload attribute '{$attribute}' doesn't exists."); 59 | } 60 | 61 | return $this->{$attribute}; 62 | } 63 | 64 | /** 65 | * @param string $name 66 | * @param $value 67 | * @return $this 68 | */ 69 | public function addExtraAttribute(string $name, $value): self 70 | { 71 | $this->extraAttributes[$name] = $value; 72 | return $this; 73 | } 74 | 75 | /** 76 | * @param string | int $sub 77 | * @return JWTPayload 78 | */ 79 | public function setSub($sub): JWTPayload 80 | { 81 | $this->sub = $sub; 82 | return $this; 83 | } 84 | 85 | /** 86 | * @return array 87 | */ 88 | public function getData(): array 89 | { 90 | return array_merge([ 91 | 'sub' => $this->sub, 92 | 'iss' => $this->iss, 93 | 'aud' => $this->aud, 94 | 'iat' => $this->iat, 95 | 'exp' => $this->exp, 96 | 'jti' => $this->jti 97 | ], $this->extraAttributes); 98 | } 99 | 100 | /** 101 | * @return string 102 | */ 103 | private function generateHash(): string 104 | { 105 | $hash = md5(uniqid(rand() . "", true)); 106 | return substr($hash, 0, 15); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/JWTToolsTest.php: -------------------------------------------------------------------------------- 1 | jwtTools = JWTTools::build(static::SECRET); 23 | } 24 | 25 | public function testBuildASingleInstance() 26 | { 27 | $this->assertInstanceOf(JWTTools::class, $this->jwtTools); 28 | } 29 | 30 | public function testConfiguringOptionalOptions() 31 | { 32 | $jwtTools = JWTTools::build(static::SECRET, [ 33 | 'algorithm' => 'ES256', 34 | 'expiration' => 1589069866, 35 | 'iss' => 'localhost-iss.com.br', 36 | 'aud' => 'localhost-aud.com.br', 37 | ]); 38 | 39 | $this->assertSame(static::SECRET, $jwtTools->getSecretKey()); 40 | $this->assertSame('ES256', $jwtTools->getAlgorithm()); 41 | $this->assertSame(1589069866, $jwtTools->getExpiration()); 42 | $this->assertSame('localhost-iss.com.br', $jwtTools->getPayload()->get('iss')); 43 | $this->assertSame('localhost-aud.com.br', $jwtTools->getPayload()->get('aud')); 44 | } 45 | 46 | public function testGenerationTokenWithoutActiveRecord() 47 | { 48 | $token = $this->jwtTools->getJWT(); 49 | $payload = $this->jwtTools->getPayload()->getData(); 50 | 51 | $this->assertEquals(6, count($payload)); 52 | 53 | $this->assertTrue(array_key_exists('sub', $payload)); 54 | $this->assertTrue(array_key_exists('iss', $payload)); 55 | $this->assertTrue(array_key_exists('aud', $payload)); 56 | $this->assertTrue(array_key_exists('iat', $payload)); 57 | $this->assertTrue(array_key_exists('exp', $payload)); 58 | $this->assertTrue(array_key_exists('jti', $payload)); 59 | } 60 | 61 | public function testGenerationOfTheTokenWithActiveRecord() 62 | { 63 | $model = $this->createPersonMock(); 64 | 65 | $payload = $this->jwtTools 66 | ->withModel($model, ['name', 'github']) 67 | ->getPayload() 68 | ->getData(); 69 | 70 | $this->assertEquals(8, count($payload)); 71 | 72 | $this->assertSame('100', $payload['sub']); 73 | $this->assertSame('Kilderson Sena', $payload['name']); 74 | $this->assertSame('dersonsena', $payload['github']); 75 | } 76 | 77 | public function testDecodeJWTTokenWithoutModel() 78 | { 79 | $token = $this->jwtTools->getJWT(); 80 | $decodedToken = $this->jwtTools->decodeToken($token); 81 | 82 | $this->assertEquals(stdClass::class, get_class($decodedToken)); 83 | 84 | $this->assertTrue(property_exists($decodedToken, 'sub')); 85 | $this->assertTrue(property_exists($decodedToken, 'iss')); 86 | $this->assertTrue(property_exists($decodedToken, 'aud')); 87 | $this->assertTrue(property_exists($decodedToken, 'iat')); 88 | $this->assertTrue(property_exists($decodedToken, 'exp')); 89 | $this->assertTrue(property_exists($decodedToken, 'jti')); 90 | } 91 | 92 | public function testIfSignatureIsValid() 93 | { 94 | $token = $this->jwtTools->getJWT(); 95 | $signatureIsValid = $this->jwtTools->signatureIsValid($token); 96 | 97 | $this->assertTrue($signatureIsValid); 98 | } 99 | 100 | public function testIfDateTimeTokenIsNotExpired() 101 | { 102 | $token = JWTTools::build(static::SECRET, [ 103 | 'expiration' => 10, // expires in 10 seconds 104 | ])->getJWT(); 105 | 106 | $expired = $this->jwtTools->tokenIsExpired($token); 107 | 108 | $this->assertFalse($expired); 109 | } 110 | 111 | private function createPersonMock() 112 | { 113 | $model = $this->createMock(ActiveRecord::class); 114 | 115 | $model->method('attributes') 116 | ->willReturn(['name', 'github']); 117 | 118 | $model->method('getPrimaryKey') 119 | ->willReturn('100'); 120 | 121 | $model->expects($this->exactly(2)) 122 | ->method('hasAttribute') 123 | ->with($this->logicalOr( 124 | $this->equalTo('name'), 125 | $this->equalTo('github') 126 | )) 127 | ->willReturn(true); 128 | 129 | $model->expects($this->exactly(2)) 130 | ->method('getAttribute') 131 | ->withConsecutive( 132 | [$this->equalTo('name')], 133 | [$this->equalTo('github')] 134 | ) 135 | ->willReturnOnConsecutiveCalls('Kilderson Sena', 'dersonsena'); 136 | 137 | return $model; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/JWTPayloadTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(6, count($payload->getData())); 13 | 14 | $this->assertEmpty($payload->get('iss')); 15 | $this->assertEmpty($payload->get('aud')); 16 | $this->assertEquals(15, strlen($payload->get('sub'))); 17 | $this->assertEquals(15, strlen($payload->get('jti'))); 18 | } 19 | 20 | public function testCreatePayloadWithCustomAttributes() 21 | { 22 | $now = new DateTime(); 23 | $nowTimestamp = $now->getTimestamp(); 24 | $expirationTimestamp = $now->add(new DateInterval("PT3600S"))->getTimestamp(); 25 | 26 | $payload = JWTPayload::build([ 27 | 'iat' => $nowTimestamp, 28 | 'exp' => $expirationTimestamp, 29 | 'iss' => 'localhost-iss.com', 30 | 'aud' => 'localhost-aud.com', 31 | 'sub' => 20, 32 | 'jti' => 'd4b678686' 33 | ]); 34 | 35 | $this->assertEquals(6, count($payload->getData())); 36 | 37 | $this->assertSame($nowTimestamp, $payload->get('iat')); 38 | $this->assertSame($expirationTimestamp, $payload->get('exp')); 39 | $this->assertSame('localhost-iss.com', $payload->get('iss')); 40 | $this->assertSame('localhost-aud.com', $payload->get('aud')); 41 | $this->assertSame(20, $payload->get('sub')); 42 | $this->assertSame('d4b678686', $payload->get('jti')); 43 | } 44 | 45 | public function testGetAInvalidAttributeFromPayload() 46 | { 47 | $this->expectException(InvalidArgumentException::class); 48 | JWTPayload::build()->get('xpto'); 49 | } 50 | 51 | public function testChangeSub() 52 | { 53 | $payload = JWTPayload::build(); 54 | $payload->setSub(500); 55 | 56 | $this->assertEquals(500, $payload->get('sub')); 57 | } 58 | 59 | public function testAddExtraAttributesToPayload() 60 | { 61 | $payload = JWTPayload::build(); 62 | $payload->addExtraAttribute('name', 'Kilderson Sena'); 63 | $payload->addExtraAttribute('github', 'dersonsena'); 64 | $payload->addExtraAttribute('twitter', 'derson_sena'); 65 | 66 | $data = $payload->getData(); 67 | 68 | $this->assertSame(9, count($data)); 69 | 70 | $this->assertTrue(isset($data['name'])); 71 | $this->assertTrue(isset($data['github'])); 72 | $this->assertTrue(isset($data['twitter'])); 73 | 74 | $this->assertSame('Kilderson Sena', $data['name']); 75 | $this->assertSame('dersonsena', $data['github']); 76 | $this->assertSame('derson_sena', $data['twitter']); 77 | } 78 | 79 | public function testPayloadDataWithoutExtraAttributes() 80 | { 81 | $payload = JWTPayload::build(); 82 | $data = $payload->getData(); 83 | 84 | $this->assertSame(6, count($data)); 85 | 86 | $this->assertTrue(isset($data['sub'])); 87 | $this->assertTrue(isset($data['iss'])); 88 | $this->assertTrue(isset($data['aud'])); 89 | $this->assertTrue(isset($data['iat'])); 90 | $this->assertTrue(isset($data['exp'])); 91 | $this->assertTrue(isset($data['jti'])); 92 | } 93 | 94 | public function testPayloadDataWithExtraAttributes() 95 | { 96 | $payload = JWTPayload::build(); 97 | $payload->addExtraAttribute('name', 'Kilderson Sena'); 98 | $payload->addExtraAttribute('github', 'dersonsena'); 99 | $payload->addExtraAttribute('twitter', 'derson_sena'); 100 | 101 | $data = $payload->getData(); 102 | 103 | $this->assertSame(9, count($data)); 104 | 105 | $this->assertTrue(isset($data['sub'])); 106 | $this->assertTrue(isset($data['iss'])); 107 | $this->assertTrue(isset($data['aud'])); 108 | $this->assertTrue(isset($data['iat'])); 109 | $this->assertTrue(isset($data['exp'])); 110 | $this->assertTrue(isset($data['jti'])); 111 | $this->assertTrue(isset($data['name'])); 112 | $this->assertTrue(isset($data['github'])); 113 | $this->assertTrue(isset($data['twitter'])); 114 | } 115 | 116 | public function testIfCreatesPayloadWithExtraParamsInConstructor() 117 | { 118 | $payload = JWTPayload::build(['extraParams' => [ 119 | 'any_key_1' => 'any_value1', 120 | 'any_key_2' => 'any_value2', 121 | ]])->getData(); 122 | 123 | $this->assertSame(8, count($payload)); 124 | $this->assertTrue(isset($payload['any_key_1'])); 125 | $this->assertTrue(isset($payload['any_key_2'])); 126 | $this->assertSame('any_value1', $payload['any_key_1']); 127 | $this->assertSame('any_value2', $payload['any_key_2']); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/JWTTools.php: -------------------------------------------------------------------------------- 1 | secretKey = $secretKey; 30 | 31 | if (isset($options['algorithm'])) { 32 | $this->algorithm = $options['algorithm']; 33 | } 34 | 35 | if (isset($options['expiration'])) { 36 | $this->expiration = (int)$options['expiration']; 37 | 38 | $options['exp'] = (new DateTime()) 39 | ->add(new DateInterval("PT{$this->expiration}S")) 40 | ->getTimestamp(); 41 | } 42 | 43 | $this->payload = JWTPayload::build($options); 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getAlgorithm(): string 50 | { 51 | return $this->algorithm; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function getSecretKey(): string 58 | { 59 | return $this->secretKey; 60 | } 61 | 62 | /** 63 | * @return int 64 | */ 65 | public function getExpiration(): int 66 | { 67 | return $this->expiration; 68 | } 69 | 70 | /** 71 | * @return JWTPayload 72 | */ 73 | public function getPayload(): JWTPayload 74 | { 75 | return $this->payload; 76 | } 77 | 78 | /** 79 | * @param string $secretKey 80 | * @param array $options 81 | * @return JWTTools 82 | */ 83 | public static function build(string $secretKey, array $options = []): self 84 | { 85 | return new self($secretKey, $options); 86 | } 87 | 88 | /** 89 | * @param ActiveRecord $model 90 | * @param array $attributes 91 | * @return $this 92 | * @throws InvalidArgumentException 93 | */ 94 | public function withModel(ActiveRecord $model, array $attributes = []): self 95 | { 96 | $this->model = $model; 97 | $this->payload->setSub($this->model->getPrimaryKey()); 98 | 99 | if (empty($attributes)) { 100 | return $this; 101 | } 102 | 103 | foreach ($attributes as $attr) { 104 | if (!$this->model->hasAttribute($attr)) { 105 | throw new InvalidArgumentException( 106 | "Attribute '{$attr}' doesn't exists in model class '" . get_class($this->model) . "' ." 107 | ); 108 | } 109 | 110 | $this->payload->addExtraAttribute($attr, $this->model->getAttribute($attr)); 111 | } 112 | 113 | return $this; 114 | } 115 | 116 | public function withData($data = []): self 117 | { 118 | foreach($data as $k => $v){ 119 | $this->payload->addExtraAttribute($k, $v); 120 | } 121 | return $this; 122 | } 123 | 124 | public function getJWT(): string 125 | { 126 | try { 127 | return JWT::encode( 128 | $this->payload->getData(), 129 | $this->secretKey, 130 | $this->algorithm, 131 | $this->payload->get('sub') 132 | ); 133 | } catch (ExpiredException $e) { 134 | throw new UnauthorizedHttpException('Authentication token is expired.'); 135 | } 136 | } 137 | 138 | public function decodeToken(string $token): stdClass 139 | { 140 | try { 141 | return JWT::decode($token, new Key($this->secretKey, $this->algorithm)); 142 | } catch (\Throwable $e) { 143 | throw new UnauthorizedHttpException($e->getMessage()); 144 | } 145 | } 146 | 147 | /** 148 | * @param string $token 149 | * @return bool 150 | */ 151 | public function signatureIsValid(string $token): bool 152 | { 153 | list($header, $payload, $signatureProvided) = explode(".", $token); 154 | 155 | $signature = hash_hmac('sha256', "{$header}.{$payload}", $this->secretKey, true); 156 | $signature = str_replace("=", "", BaseStringHelper::base64UrlEncode($signature)); 157 | 158 | if ($signatureProvided !== $signature) { 159 | return false; 160 | } 161 | 162 | return true; 163 | } 164 | 165 | /** 166 | * @param string $token 167 | * @return bool 168 | * @throws Exception 169 | */ 170 | public function tokenIsExpired(string $token): bool 171 | { 172 | $decodedToken = $this->decodeToken($token); 173 | $now = new DateTime(); 174 | $expiration = new DateTime("@{$decodedToken->exp}"); 175 | 176 | if ($now > $expiration) { 177 | return true; 178 | } 179 | 180 | return false; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JWT Tools to Yii Framework 2 2 | 3 | ![GitHub](https://img.shields.io/github/license/dersonsena/yii2-jwt-tools) ![GitHub repo size](https://img.shields.io/github/repo-size/dersonsena/yii2-jwt-tools) ![Packagist Stars](https://img.shields.io/packagist/stars/dersonsena/yii2-jwt-tools) ![Packagist PHP Version Support (specify version)](https://img.shields.io/packagist/php-v/dersonsena/yii2-jwt-tools/1.0.0) ![Packagist Downloads](https://img.shields.io/packagist/dm/dersonsena/yii2-jwt-tools) 4 | 5 | JWT Tools is a toolbox that will help you to configure authentication with [JWT](http://jwt.io/) token. Not only authentication but also signature validation, the famous secret key. 6 | 7 | My biggest motivation to do this was because I didn't see a easy way to setup a simple JWT Validation with some helper functions. I always needed copy and past whole the code to a new project. 8 | 9 | Follow the steps below to install and setup in your project. 10 | 11 | ## Installation 12 | 13 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 14 | 15 | To install, either run: 16 | 17 | ```bash 18 | $ php composer.phar require dersonsena/yii2-jwt-tools 19 | ``` 20 | 21 | or add 22 | 23 | ``` 24 | "dersonsena/yii2-jwt-tools": "^1.0" 25 | ``` 26 | 27 | to the `require` section of your `composer.json` file. 28 | 29 | ## Usage 30 | 31 | ### Configuration File 32 | 33 | Let's guarantee somes application settings are correct. Open your `config/web.php` and setup such as: 34 | 35 | ```php 36 | 'components' => [ 37 | // ... 38 | 'request' => [ 39 | 'enableCookieValidation' => false, 40 | ], 41 | 'user' => [ 42 | 'identityClass' => 'app\models\User', 43 | 'enableAutoLogin' => false, 44 | 'enableSession' => false, 45 | 'loginUrl' => null 46 | ], 47 | // ... 48 | ``` 49 | 50 | ### Controller 51 | 52 | In your controller class, register the [JWTSignatureBehavior](./src/JWTSignatureBehavior.php) and [HttpBearerAuth](https://www.yiiframework.com/doc/api/2.0/yii-filters-auth-httpbearerauth) behaviors in `behaviors()` method, such as below: 53 | 54 | ```php 55 | use yii\rest\Controller; 56 | 57 | class YourCuteController extends Controller 58 | { 59 | public function behaviors() 60 | { 61 | $behaviors = parent::behaviors(); 62 | 63 | $behaviors['jwtValidator'] = [ 64 | 'class' => JWTSignatureBehavior::class, 65 | 'secretKey' => Yii::$app->params['jwt']['secret'], 66 | 'except' => ['login'] // it's doesn't run in login action 67 | ]; 68 | 69 | $behaviors['authenticator'] = [ 70 | 'class' => HttpBearerAuth::class, 71 | 'except' => ['login'] // it's doesn't run in login action 72 | ]; 73 | 74 | return $behaviors; 75 | } 76 | } 77 | ``` 78 | 79 | > **NOTE:** in this examples I used `Yii::$app->params['jwt']['secret']` to store my JWT Secret Key, but, I like a lot of the .env files and this information could be stored there 80 | 81 | The `JWTSignatureBehavior` will validate the JWT token sent by `Authorization` HTTP Header. If there are some problem with your token this one it will throw one of Exceptions below: 82 | 83 | - [UnauthorizedHttpException](https://www.yiiframework.com/doc/api/2.0/yii-web-unauthorizedhttpexception) with message `Your request was made without an authorization token.` if HTTP Header doesn't exist or token is empty or null. 84 | 85 | - [UnauthorizedHttpException](https://www.yiiframework.com/doc/api/2.0/yii-web-unauthorizedhttpexception) with message `Authentication token is expired.` if token is out of due. 86 | 87 | - [UnauthorizedHttpException](https://www.yiiframework.com/doc/api/2.0/yii-web-unauthorizedhttpexception) with message `The token signature is invalid.` if the token signature is invalid. 88 | 89 | If for some reason you need to change the HTTP Header name (to be honest I can't see this scenario) you can change this one setting up the `headerName` property, such as below: 90 | 91 | ```php 92 | class YourCuteController extends Controller 93 | { 94 | // ... 95 | public function behaviors() 96 | { 97 | $behaviors['jwtValidator'] = [ 98 | 'class' => JWTSignatureBehavior::class, 99 | 'secretKey' => Yii::$app->params['jwt']['secret'], 100 | 'headerName' => 'Auth' 101 | ]; 102 | } 103 | // ... 104 | } 105 | ``` 106 | 107 | In your login action you need to create a JWT Token to send your response. It's very easy create a token, see below: 108 | 109 | ```php 110 | class YourCuteController extends Controller 111 | { 112 | // ... 113 | public function behaviors() 114 | { 115 | $behaviors['jwtValidator'] = [ 116 | 'class' => JWTSignatureBehavior::class, 117 | 'secretKey' => Yii::$app->params['jwt']['secret'], 118 | 'headerName' => 'Auth' 119 | ]; 120 | } 121 | 122 | public function actionLogin() 123 | { 124 | // validation stuff 125 | // find user 126 | 127 | $token = JWTTools::build(Yii::$app->params['jwt']['secret']) 128 | ->withModel($user, ['name', 'email', 'group']) 129 | ->getJWT(); 130 | 131 | return ['success' => true, 'token' => $token]; 132 | } 133 | // ... 134 | } 135 | ``` 136 | 137 | ### Model Identity Class 138 | 139 | At this point we know that the token is valid and we can decode this one to authenticate user. 140 | 141 | I'm using here `app/models/User` as my User Identity, so, let's implement the `findIdentityByAccessToken()` method of the [IdentityInterface](https://www.yiiframework.com/doc/api/2.0/yii-web-identityinterface) interface: 142 | 143 | ```php 144 | namespace app\models; 145 | 146 | use yii\db\ActiveRecord; 147 | use yii\web\IdentityInterface; 148 | 149 | class User extends ActiveRecord implements IdentityInterface 150 | { 151 | // ... 152 | public static function findIdentity($id) 153 | { 154 | return static::findOne($id); 155 | } 156 | 157 | public function getId() 158 | { 159 | return $this->id; 160 | } 161 | 162 | public function getAuthKey() 163 | { 164 | // we don't need to implement this method 165 | } 166 | 167 | public function validateAuthKey($authKey) 168 | { 169 | // we don't need to implement this method 170 | } 171 | 172 | public static function findIdentityByAccessToken($token, $type = null) 173 | { 174 | $decodedToken = JWTTools::build(Yii::$app->params['jwt']['secret']) 175 | ->decodeToken($token); 176 | 177 | return static::findOne(['id' => $decodedToken->sub]); 178 | } 179 | } 180 | ``` 181 | 182 | If all ok, at this point you're able to authenticate with a valid JWT Token. 183 | 184 | ## Demos 185 | 186 | ### Generating a token 187 | 188 | You can use the [JWTTools](./src/JWTTools.php) methods to make specific things in your project. See some examples below: 189 | 190 | ```php 191 | use AstrotechLabs\JWTTools\JWTTools; 192 | 193 | $jwtTools = JWTTools::build('my-secret-key'); 194 | 195 | $token = $jwtTools->getJWT(); 196 | $payload = $jwtTools->getPayload()->getData(); 197 | 198 | var_dump($token); 199 | print_r($payload); 200 | ``` 201 | 202 | This code will be return something like: 203 | 204 | ``` 205 | string(248) "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImJlMTgzOTQ4YjJmNjkzZSJ9.eyJzdWIiOiJiZTE4Mzk0OGIyZjY5M2UiLCJpc3MiOiIiLCJhdWQiOiIiLCJpYXQiOjE1ODkxMzEzNjIsImV4cCI6MTU4OTEzNDk2MiwianRpIjoiNTM4NTRiMGQ5MzFkMGVkIn0.-JDBkID1oJ7anC_JLg68AJxbKGK-5ubA83zZlDZYYso" 206 | 207 | Array 208 | ( 209 | [sub] => 9c65241853de774 210 | [iss] => 211 | [aud] => 212 | [iat] => 1589129672 213 | [exp] => 1589133272 214 | [jti] => a0a98e2364d2721 215 | ) 216 | ``` 217 | 218 | > **NOTE:** the `->getPayload()` returns an instance of the [JWTPayload](./src/JWTPayload.php). 219 | 220 | ### Generating Token with an Active Record 221 | 222 | You can insert the active record attributes in your payload using `withModel()` method, like this: 223 | 224 | ```php 225 | use AstrotechLabs\JWTTools\JWTTools; 226 | 227 | $user = app\models\User::findOne(2); 228 | 229 | $payload = JWTTools::build('my-secret-key'); 230 | ->withModel($user, ['id', 'name', 'email']) 231 | ->getPayload() 232 | ->getData(); 233 | 234 | print_r($payload); 235 | ``` 236 | 237 | This code will be return something like: 238 | 239 | ``` 240 | Array 241 | ( 242 | [sub] => 10 <~~~~ 243 | [iss] => 244 | [aud] => 245 | [iat] => 1589130028 246 | [exp] => 1589133628 247 | [jti] => 7aba5b7666d7868 248 | [id] => 10 <~~~~ 249 | [name] => Kilderson Sena <~~~~ 250 | [email] => email@email.com.br <~~~~ 251 | ) 252 | ``` 253 | 254 | The `sub` property is automatically override to `$model->getPrimaryKey()` value, following the [RFC7519](https://tools.ietf.org/html/rfc7519#section-4.1) instructions. 255 | 256 | ### Changing JWT Properties 257 | 258 | You can change the JWT Properties (such as `iss`, `aud` etc) adding an array in second method parameter, as below: 259 | 260 | ```php 261 | use AstrotechLabs\JWTTools\JWTTools; 262 | 263 | $payload = JWTTools::build('my-secret-key', [ 264 | 'algorithm' => 'ES256', 265 | 'expiration' => 1589069866, //<~~ It will generate the exp property automatically 266 | 'iss' => 'yourdomain.com', 267 | 'aud' => 'yourdomain.com', 268 | ]); 269 | ``` 270 | 271 | ## Authors 272 | 273 | - [Kilderson Sena](https://github.com/dersonsena) - Initial work - [Yii Academy](https://www.yiiacademy.com.br) 274 | 275 | See also the list of [contributors](https://github.com/dersonsena/yii2-jwt-tools/contributors) who participated in this project. 276 | 277 | ## Contributing 278 | 279 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 280 | 281 | Please make sure to update tests as appropriate. 282 | 283 | ## Licence 284 | 285 | This package is released under the [MIT](https://choosealicense.com/licenses/mit/) License. See the bundled [LICENSE](./LICENSE) for details. 286 | --------------------------------------------------------------------------------