├── src ├── Exceptions │ ├── PayloadException.php │ ├── TokenExpiredException.php │ ├── TokenInvalidException.php │ ├── UserNotDefinedException.php │ ├── TokenBlacklistedException.php │ ├── JWTException.php │ └── InvalidClaimException.php ├── Claims │ ├── Issuer.php │ ├── JwtId.php │ ├── Subject.php │ ├── Audience.php │ ├── Custom.php │ ├── Expiration.php │ ├── NotBefore.php │ ├── IssuedAt.php │ ├── DatetimeTrait.php │ ├── Collection.php │ ├── Claim.php │ └── Factory.php ├── Contracts │ ├── Http │ │ └── ParserInterface.php │ ├── Providers │ │ ├── JWTInterface.php │ │ └── StorageInterface.php │ ├── ValidatorInterface.php │ ├── JWTSubjectInterface.php │ └── ClaimInterface.php ├── Providers │ ├── JWTAuthServiceProvider.php │ ├── Storage │ │ └── CacheStorage.php │ └── JWT │ │ ├── Provider.php │ │ └── Lcobucci.php ├── Http │ ├── Parser │ │ ├── Cookies.php │ │ ├── InputSource.php │ │ ├── QueryString.php │ │ ├── KeyTrait.php │ │ ├── AuthHeaders.php │ │ └── HttpParser.php │ └── Middleware │ │ ├── AuthenticateFIlter.php │ │ ├── AuthenticateAndRenewFilter.php │ │ ├── CheckFilter.php │ │ ├── RefreshTokenFilter.php │ │ └── AbstractBaseFilter.php ├── Support │ ├── RefreshFlowTrait.php │ ├── CustomClaimsTrait.php │ └── UtilsTrait.php ├── Token.php ├── Validators │ ├── AbstractValidator.php │ ├── TokenValidator.php │ └── PayloadValidator.php ├── Config │ ├── Services.php │ └── JWT.php ├── Blacklist.php ├── Factory.php ├── Manager.php ├── Payload.php ├── JWT.php └── JWTGuard.php ├── LICENSE.md ├── composer.json └── README.md /src/Exceptions/PayloadException.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Exceptions; 14 | 15 | class PayloadException extends JWTException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Exceptions/TokenExpiredException.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Exceptions; 14 | 15 | class TokenExpiredException extends JWTException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Exceptions/TokenInvalidException.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Exceptions; 14 | 15 | class TokenInvalidException extends JWTException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Exceptions/UserNotDefinedException.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Exceptions; 14 | 15 | class UserNotDefinedException extends JWTException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Exceptions/TokenBlacklistedException.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Exceptions; 14 | 15 | class TokenBlacklistedException extends TokenInvalidException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Claims/Issuer.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | class Issuer extends Claim 16 | { 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | protected $name = 'iss'; 21 | } 22 | -------------------------------------------------------------------------------- /src/Claims/JwtId.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | class JwtId extends Claim 16 | { 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | protected $name = 'jti'; 21 | } 22 | -------------------------------------------------------------------------------- /src/Claims/Subject.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | class Subject extends Claim 16 | { 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | protected $name = 'sub'; 21 | } 22 | -------------------------------------------------------------------------------- /src/Claims/Audience.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | class Audience extends Claim 16 | { 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | protected $name = 'aud'; 21 | } 22 | -------------------------------------------------------------------------------- /src/Exceptions/JWTException.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Exceptions; 14 | 15 | use Exception; 16 | 17 | class JWTException extends Exception 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | protected $message = 'An error occurred'; 23 | } 24 | -------------------------------------------------------------------------------- /src/Contracts/Http/ParserInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Contracts\Http; 14 | 15 | use CodeIgniter\Http\Request; 16 | 17 | interface ParserInterface 18 | { 19 | /** 20 | * Parse the request. 21 | * 22 | * @return null|string 23 | */ 24 | public function parse(Request $request); 25 | } 26 | -------------------------------------------------------------------------------- /src/Claims/Custom.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | class Custom extends Claim 16 | { 17 | /** 18 | * @param string $name 19 | * @param mixed $value 20 | * @return void 21 | */ 22 | public function __construct($name, $value) 23 | { 24 | parent::__construct($value); 25 | $this->setName($name); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Contracts/Providers/JWTInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Contracts\Providers; 14 | 15 | interface JWTInterface 16 | { 17 | /** 18 | * @param array $payload 19 | * @return string 20 | */ 21 | public function encode(array $payload); 22 | 23 | /** 24 | * @param string $token 25 | * @return array 26 | */ 27 | public function decode($token); 28 | } 29 | -------------------------------------------------------------------------------- /src/Providers/JWTAuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | createUserProvider($config['provider']), 22 | ); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Contracts/ValidatorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Contracts; 14 | 15 | interface ValidatorInterface 16 | { 17 | /** 18 | * Perform some checks on the value. 19 | * 20 | * @param mixed $value 21 | * @return void 22 | */ 23 | public function check($value); 24 | 25 | /** 26 | * Helper function to return a boolean. 27 | * 28 | * @param array $value 29 | * @return bool 30 | */ 31 | public function isValid($value); 32 | } 33 | -------------------------------------------------------------------------------- /src/Http/Parser/Cookies.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Parser; 14 | 15 | use CodeIgniter\Http\Request; 16 | use Fluent\JWTAuth\Contracts\Http\ParserInterface; 17 | 18 | class Cookies implements ParserInterface 19 | { 20 | use KeyTrait; 21 | 22 | /** 23 | * Try to parse the token from the request cookies. 24 | * 25 | * @return null|string 26 | */ 27 | public function parse(Request $request) 28 | { 29 | return $request->fetchGlobal('cookie', $this->key); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Contracts/JWTSubjectInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Contracts; 14 | 15 | interface JWTSubjectInterface 16 | { 17 | /** 18 | * Get the identifier that will be stored in the subject claim of the JWT. 19 | * 20 | * @return mixed 21 | */ 22 | public function getJWTIdentifier(); 23 | 24 | /** 25 | * Return a key value array, containing any custom claims to be added to the JWT. 26 | * 27 | * @return array 28 | */ 29 | public function getJWTCustomClaims(); 30 | } 31 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidClaimException.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Exceptions; 14 | 15 | use Exception; 16 | use Fluent\JWTAuth\Claims\Claim; 17 | 18 | class InvalidClaimException extends JWTException 19 | { 20 | /** 21 | * @param int $code 22 | * @return void 23 | */ 24 | public function __construct(Claim $claim, $code = 0, ?Exception $previous = null) 25 | { 26 | parent::__construct('Invalid value provided for claim [' . $claim->getName() . ']', $code, $previous); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Http/Parser/InputSource.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Parser; 14 | 15 | use CodeIgniter\Http\Request; 16 | use Fluent\JWTAuth\Contracts\Http\ParserInterface; 17 | 18 | class InputSource implements ParserInterface 19 | { 20 | use KeyTrait; 21 | 22 | /** 23 | * Try to parse the token from the request input source. 24 | * 25 | * @return null|string 26 | */ 27 | public function parse(Request $request) 28 | { 29 | return $request->fetchGlobal('POST', $this->key); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Http/Parser/QueryString.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Parser; 14 | 15 | use CodeIgniter\Http\Request; 16 | use Fluent\JWTAuth\Contracts\Http\ParserInterface; 17 | 18 | class QueryString implements ParserInterface 19 | { 20 | use KeyTrait; 21 | 22 | /** 23 | * Try to parse the token from the request query string. 24 | * 25 | * @return null|string 26 | */ 27 | public function parse(Request $request) 28 | { 29 | return $request->fetchGlobal('GET', $this->key); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Claims/Expiration.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | use Fluent\JWTAuth\Exceptions\TokenExpiredException; 16 | 17 | class Expiration extends Claim 18 | { 19 | use DatetimeTrait; 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | protected $name = 'exp'; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function validatePayload() 30 | { 31 | if ($this->isPast($this->getValue())) { 32 | throw new TokenExpiredException('Token has expired'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Support/RefreshFlowTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Support; 14 | 15 | trait RefreshFlowTrait 16 | { 17 | /** 18 | * The refresh flow flag. 19 | * 20 | * @var bool 21 | */ 22 | protected $refreshFlow = false; 23 | 24 | /** 25 | * Set the refresh flow flag. 26 | * 27 | * @param bool $RefreshFlowTrait 28 | * @return $this 29 | */ 30 | public function setRefreshFlow($refreshFlow = true) 31 | { 32 | $this->refreshFlowTrait = $refreshFlow; 33 | 34 | return $this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Claims/NotBefore.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | use Fluent\JWTAuth\Exceptions\TokenInvalidException; 16 | 17 | class NotBefore extends Claim 18 | { 19 | use DatetimeTrait; 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | protected $name = 'nbf'; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function validatePayload() 30 | { 31 | if ($this->isFuture($this->getValue())) { 32 | throw new TokenInvalidException('Not Before (nbf) timestamp cannot be in the future'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Http/Parser/KeyTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Parser; 14 | 15 | trait KeyTrait 16 | { 17 | /** 18 | * The key. 19 | * 20 | * @var string 21 | */ 22 | protected $key = 'token'; 23 | 24 | /** 25 | * Set the key. 26 | * 27 | * @param string $key 28 | * @return $this 29 | */ 30 | public function setKey($key) 31 | { 32 | $this->key = $key; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * Get the key. 39 | * 40 | * @return string 41 | */ 42 | public function getKey() 43 | { 44 | return $this->key; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Http/Middleware/AuthenticateFIlter.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Middleware; 14 | 15 | use CodeIgniter\Filters\FilterInterface; 16 | use CodeIgniter\HTTP\RequestInterface; 17 | use CodeIgniter\HTTP\ResponseInterface; 18 | 19 | class AuthenticateFIlter extends AbstractBaseFilter implements FilterInterface 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function before(RequestInterface $request, $arguments = null) 25 | { 26 | $this->authenticate($request); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) 33 | { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Http/Middleware/AuthenticateAndRenewFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Middleware; 14 | 15 | use CodeIgniter\Filters\FilterInterface; 16 | use CodeIgniter\HTTP\RequestInterface; 17 | use CodeIgniter\HTTP\ResponseInterface; 18 | 19 | class AuthenticateAndRenewFilter extends AbstractBaseFilter implements FilterInterface 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function before(RequestInterface $request, $arguments = null) 25 | { 26 | $this->authenticate($request); 27 | 28 | return $this->setAuthenticationHeader(service('response')); 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) 35 | { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Contracts/Providers/StorageInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Contracts\Providers; 14 | 15 | interface StorageInterface 16 | { 17 | /** 18 | * @param string $key 19 | * @param mixed $value 20 | * @param int $minutes 21 | * @return void 22 | */ 23 | public function add($key, $value, $minutes); 24 | 25 | /** 26 | * @param string $key 27 | * @param mixed $value 28 | * @return void 29 | */ 30 | public function forever($key, $value); 31 | 32 | /** 33 | * @param string $key 34 | * @return mixed 35 | */ 36 | public function get($key); 37 | 38 | /** 39 | * @param string $key 40 | * @return bool 41 | */ 42 | public function destroy($key); 43 | 44 | /** 45 | * @return void 46 | */ 47 | public function flush(); 48 | } 49 | -------------------------------------------------------------------------------- /src/Token.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth; 14 | 15 | use Fluent\JWTAuth\Validators\TokenValidator; 16 | 17 | class Token 18 | { 19 | /** @var string */ 20 | private $value; 21 | 22 | /** 23 | * Create a new JSON Web Token. 24 | * 25 | * @param string $value 26 | * @return void 27 | */ 28 | public function __construct($value) 29 | { 30 | $this->value = (string) (new TokenValidator())->check($value); 31 | } 32 | 33 | /** 34 | * Get the token. 35 | * 36 | * @return string 37 | */ 38 | public function get() 39 | { 40 | return $this->value; 41 | } 42 | 43 | /** 44 | * Get the token when casting to string. 45 | * 46 | * @return string 47 | */ 48 | public function __toString() 49 | { 50 | return $this->get(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2021 Sean Tymon 4 | 5 | Copyright (c) 2021-2022 Agung Sugiarto 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/Http/Middleware/CheckFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Middleware; 14 | 15 | use CodeIgniter\Filters\FilterInterface; 16 | use CodeIgniter\HTTP\RequestInterface; 17 | use CodeIgniter\HTTP\ResponseInterface; 18 | use Exception; 19 | 20 | class CheckFilter extends AbstractBaseFilter implements FilterInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function before(RequestInterface $request, $arguments = null) 26 | { 27 | if ($this->auth->parser()->setRequest($request)->hasToken()) { 28 | try { 29 | $this->auth->check(); 30 | } catch (Exception $e) { 31 | } 32 | } 33 | 34 | return $request; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) 41 | { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Validators/AbstractValidator.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Validators; 14 | 15 | use Fluent\JWTAuth\Contracts\ValidatorInterface; 16 | use Fluent\JWTAuth\Exceptions\JWTException; 17 | use Fluent\JWTAuth\Support\RefreshFlowTrait; 18 | 19 | abstract class AbstractValidator implements ValidatorInterface 20 | { 21 | use RefreshFlowTrait; 22 | 23 | /** 24 | * Helper function to return a boolean. 25 | * 26 | * @param array $value 27 | * @return bool 28 | */ 29 | public function isValid($value) 30 | { 31 | try { 32 | $this->check($value); 33 | } catch (JWTException $e) { 34 | return false; 35 | } 36 | 37 | return true; 38 | } 39 | 40 | /** 41 | * Run the validation. 42 | * 43 | * @param array $value 44 | * @return void 45 | */ 46 | abstract public function check($value); 47 | } 48 | -------------------------------------------------------------------------------- /src/Support/CustomClaimsTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Support; 14 | 15 | trait CustomClaimsTrait 16 | { 17 | /** 18 | * Custom claims. 19 | * 20 | * @var array 21 | */ 22 | protected $customClaims = []; 23 | 24 | /** 25 | * Set the custom claims. 26 | * 27 | * @param array $CustomClaims 28 | * @return $this 29 | */ 30 | public function customClaims(array $customClaims) 31 | { 32 | $this->customClaims = $customClaims; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * Alias to set the custom claims. 39 | * 40 | * @param array $CustomClaims 41 | * @return $this 42 | */ 43 | public function claims(array $CustomClaims) 44 | { 45 | return $this->customClaims($CustomClaims); 46 | } 47 | 48 | /** 49 | * Get the custom claims. 50 | * 51 | * @return array 52 | */ 53 | public function getCustomClaims() 54 | { 55 | return $this->customClaims; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Contracts/ClaimInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Contracts; 14 | 15 | use Fluent\JWTAuth\Exceptions\InvalidClaimException; 16 | 17 | interface ClaimInterface 18 | { 19 | /** 20 | * Set the claim value, and call a validate method. 21 | * 22 | * @param mixed $value 23 | * @throws InvalidClaimException 24 | * @return $this 25 | */ 26 | public function setValue($value); 27 | 28 | /** 29 | * Get the claim value. 30 | * 31 | * @return mixed 32 | */ 33 | public function getValue(); 34 | 35 | /** 36 | * Set the claim name. 37 | * 38 | * @param string $name 39 | * @return $this 40 | */ 41 | public function setName($name); 42 | 43 | /** 44 | * Get the claim name. 45 | * 46 | * @return string 47 | */ 48 | public function getName(); 49 | 50 | /** 51 | * Validate the Claim value. 52 | * 53 | * @param mixed $value 54 | * @return bool 55 | */ 56 | public function validateCreate($value); 57 | } 58 | -------------------------------------------------------------------------------- /src/Http/Middleware/RefreshTokenFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Middleware; 14 | 15 | use CodeIgniter\Filters\FilterInterface; 16 | use CodeIgniter\HTTP\RequestInterface; 17 | use CodeIgniter\HTTP\ResponseInterface; 18 | use Fluent\Auth\Exceptions\AuthenticationException; 19 | use Fluent\JWTAuth\Exceptions\JWTException; 20 | 21 | class RefreshTokenFilter extends AbstractBaseFilter implements FilterInterface 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function before(RequestInterface $request, $arguments = null) 27 | { 28 | $this->checkForToken($request); 29 | 30 | try { 31 | $token = $this->auth->parseToken()->refresh(); 32 | } catch (JWTException $e) { 33 | throw new AuthenticationException($e->getMessage(), [], $e->getCode()); 34 | } 35 | 36 | return $this->setAuthenticationHeader(service('response'), $token); 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) 43 | { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Validators/TokenValidator.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Validators; 14 | 15 | use Fluent\JWTAuth\Exceptions\TokenInvalidException; 16 | 17 | use function array_filter; 18 | use function array_map; 19 | use function count; 20 | use function explode; 21 | use function implode; 22 | 23 | class TokenValidator extends AbstractValidator 24 | { 25 | /** 26 | * Check the structure of the token. 27 | * 28 | * @param string $value 29 | * @return string 30 | */ 31 | public function check($value) 32 | { 33 | return $this->validateStructure($value); 34 | } 35 | 36 | /** 37 | * @param string $token 38 | * @throws TokenInvalidException 39 | * @return string 40 | */ 41 | protected function validateStructure($token) 42 | { 43 | $parts = explode('.', $token); 44 | 45 | if (count($parts) !== 3) { 46 | throw new TokenInvalidException('Wrong number of segments'); 47 | } 48 | 49 | $parts = array_filter(array_map('trim', $parts)); 50 | 51 | if (count($parts) !== 3 || implode('.', $parts) !== $token) { 52 | throw new TokenInvalidException('Malformed token'); 53 | } 54 | 55 | return $token; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Claims/IssuedAt.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | use Fluent\JWTAuth\Exceptions\InvalidClaimException; 16 | use Fluent\JWTAuth\Exceptions\TokenExpiredException; 17 | use Fluent\JWTAuth\Exceptions\TokenInvalidException; 18 | 19 | class IssuedAt extends Claim 20 | { 21 | use DatetimeTrait { 22 | validateCreate as commonValidateCreate; 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected $name = 'iat'; 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function validateCreate($value) 34 | { 35 | $this->commonValidateCreate($value); 36 | 37 | if ($this->isFuture($value)) { 38 | throw new InvalidClaimException($this); 39 | } 40 | 41 | return $value; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function validatePayload() 48 | { 49 | if ($this->isFuture($this->getValue())) { 50 | throw new TokenInvalidException('Issued At (iat) timestamp cannot be in the future'); 51 | } 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function validateRefresh($refreshTTL) 58 | { 59 | if ($this->isPast($this->getValue() + $refreshTTL * 60)) { 60 | throw new TokenExpiredException('Token has expired and can no longer be refreshed'); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agungsugiarto/codeigniter4-authentication-jwt", 3 | "description": "JSON Web Token for codeigniter4 authentication.", 4 | "keywords": [ 5 | "codeigniter4", 6 | "authentication", 7 | "auth", 8 | "jwt", 9 | "json web token" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Sean Tymon", 15 | "email": "tymon148@gmail.com", 16 | "homepage": "https://tymon.xyz", 17 | "role": "Developer" 18 | }, 19 | { 20 | "name": "Agung Sugiarto", 21 | "email": "me.agungsugiarto@gmail.com", 22 | "homepage": "https://agungsugiarto.github.io", 23 | "role": "Developer" 24 | } 25 | ], 26 | "require": { 27 | "agungsugiarto/codeigniter4-authentication": "^1.0|^2.0", 28 | "codeigniter4/framework": "^4.1", 29 | "php": "^7.3|^8.0", 30 | "lcobucci/jwt": "<3.4", 31 | "nesbot/carbon": "^2.0", 32 | "tightenco/collect": "^8.19" 33 | }, 34 | "require-dev": { 35 | "mockery/mockery": ">=0.9.9", 36 | "phpunit/phpunit": "^8.5|^9.4", 37 | "yoast/phpunit-polyfills": "^0.2.0" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Fluent\\JWTAuth\\": "src/" 42 | } 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "Fluent\\JWTAuth\\Test\\": "tests/" 47 | } 48 | }, 49 | "extra": { 50 | "branch-alias": { 51 | "dev-master": "1.x-dev" 52 | } 53 | }, 54 | "provide": { 55 | "codeigniter4/authentication-implementation": "1.0" 56 | }, 57 | "minimum-stability": "dev", 58 | "prefer-stable": true, 59 | "scripts": { 60 | "test": "phpunit --color=always" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Support/UtilsTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Support; 14 | 15 | use Carbon\Carbon; 16 | 17 | class UtilsTrait 18 | { 19 | /** 20 | * Get the Carbon instance for the current time. 21 | * 22 | * @return Carbon 23 | */ 24 | public static function now() 25 | { 26 | return Carbon::now('UTC'); 27 | } 28 | 29 | /** 30 | * Get the Carbon instance for the timestamp. 31 | * 32 | * @param int $timestamp 33 | * @return Carbon 34 | */ 35 | public static function timestamp($timestamp) 36 | { 37 | return Carbon::createFromTimestampUTC($timestamp)->timezone('UTC'); 38 | } 39 | 40 | /** 41 | * Checks if a timestamp is in the past. 42 | * 43 | * @param int $timestamp 44 | * @param int $leeway 45 | * @return bool 46 | */ 47 | public static function isPast($timestamp, $leeway = 0) 48 | { 49 | $timestamp = static::timestamp($timestamp); 50 | 51 | return $leeway > 0 52 | ? $timestamp->addSeconds($leeway)->isPast() 53 | : $timestamp->isPast(); 54 | } 55 | 56 | /** 57 | * Checks if a timestamp is in the future. 58 | * 59 | * @param int $timestamp 60 | * @param int $leeway 61 | * @return bool 62 | */ 63 | public static function isFuture($timestamp, $leeway = 0) 64 | { 65 | $timestamp = static::timestamp($timestamp); 66 | 67 | return $leeway > 0 68 | ? $timestamp->subSeconds($leeway)->isFuture() 69 | : $timestamp->isFuture(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Providers/Storage/CacheStorage.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Providers\Storage; 14 | 15 | use CodeIgniter\Cache\CacheInterface; 16 | use Fluent\JWTAuth\Contracts\Providers\StorageInterface; 17 | 18 | class CacheStorage implements StorageInterface 19 | { 20 | /** 21 | * The cache repository contract. 22 | * 23 | * @var CacheInterface 24 | */ 25 | protected $cache; 26 | 27 | /** 28 | * @return void 29 | */ 30 | public function __construct(CacheInterface $cache) 31 | { 32 | $this->cache = $cache; 33 | } 34 | 35 | /** 36 | * Add a new item into storage. 37 | * 38 | * @param string $key 39 | * @param mixed $value 40 | * @param int $minutes 41 | * @return void 42 | */ 43 | public function add($key, $value, $minutes) 44 | { 45 | $this->cache->save($key, $value, $minutes); 46 | } 47 | 48 | /** 49 | * Add a new item into storage forever. 50 | * 51 | * @param string $key 52 | * @param mixed $value 53 | * @return void 54 | */ 55 | public function forever($key, $value) 56 | { 57 | $this->cache->save($key, $value, 0); 58 | } 59 | 60 | /** 61 | * Get an item from storage. 62 | * 63 | * @param string $key 64 | * @return mixed 65 | */ 66 | public function get($key) 67 | { 68 | return $this->cache->get($key); 69 | } 70 | 71 | /** 72 | * Remove an item from storage. 73 | * 74 | * @param string $key 75 | * @return bool 76 | */ 77 | public function destroy($key) 78 | { 79 | return $this->cache->delete($key); 80 | } 81 | 82 | /** 83 | * Remove all items associated with the tag. 84 | * 85 | * @return void 86 | */ 87 | public function flush() 88 | { 89 | $this->cache->clean(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Http/Parser/AuthHeaders.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Parser; 14 | 15 | use CodeIgniter\Http\Request; 16 | use Fluent\JWTAuth\Contracts\Http\ParserInterface; 17 | 18 | use function preg_match; 19 | 20 | class AuthHeaders implements ParserInterface 21 | { 22 | /** 23 | * The header name. 24 | * 25 | * @var string 26 | */ 27 | protected $header = 'authorization'; 28 | 29 | /** 30 | * The header prefix. 31 | * 32 | * @var string 33 | */ 34 | protected $prefix = 'bearer'; 35 | 36 | /** 37 | * Attempt to parse the token from some other possible headers. 38 | * 39 | * @return null|string 40 | */ 41 | protected function fromAltHeaders(Request $request) 42 | { 43 | return $request->getServer('HTTP_AUTHORIZATION') ?: $request->getServer('REDIRECT_HTTP_AUTHORIZATION'); 44 | } 45 | 46 | /** 47 | * Try to parse the token from the request header. 48 | * 49 | * @return null|string 50 | */ 51 | public function parse(Request $request) 52 | { 53 | $header = $request->header($this->header) ?: $this->fromAltHeaders($request); 54 | 55 | if ($header && preg_match('/' . $this->prefix . '\s*(\S+)\b/i', $header, $matches)) { 56 | return $matches[1]; 57 | } 58 | } 59 | 60 | /** 61 | * Set the header name. 62 | * 63 | * @param string $headerName 64 | * @return $this 65 | */ 66 | public function setHeaderName($headerName) 67 | { 68 | $this->header = $headerName; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Set the header prefix. 75 | * 76 | * @param string $headerPrefix 77 | * @return $this 78 | */ 79 | public function setHeaderPrefix($headerPrefix) 80 | { 81 | $this->prefix = $headerPrefix; 82 | 83 | return $this; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Claims/DatetimeTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | use DateInterval; 16 | use DateTimeInterface; 17 | use Fluent\JWTAuth\Exceptions\InvalidClaimException; 18 | use Fluent\JWTAuth\Support\UtilsTrait; 19 | 20 | use function is_numeric; 21 | 22 | trait DatetimeTrait 23 | { 24 | /** 25 | * Time leeway in seconds. 26 | * 27 | * @var int 28 | */ 29 | protected $leeway = 0; 30 | 31 | /** 32 | * Set the claim value, and call a validate method. 33 | * 34 | * @param mixed $value 35 | * @throws InvalidClaimException 36 | * @return $this 37 | */ 38 | public function setValue($value) 39 | { 40 | if ($value instanceof DateInterval) { 41 | $value = UtilsTrait::now()->add($value); 42 | } 43 | 44 | if ($value instanceof DateTimeInterface) { 45 | $value = $value->getTimestamp(); 46 | } 47 | 48 | return parent::setValue($value); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function validateCreate($value) 55 | { 56 | if (! is_numeric($value)) { 57 | throw new InvalidClaimException($this); 58 | } 59 | 60 | return $value; 61 | } 62 | 63 | /** 64 | * Determine whether the value is in the future. 65 | * 66 | * @param mixed $value 67 | * @return bool 68 | */ 69 | protected function isFuture($value) 70 | { 71 | return UtilsTrait::isFuture($value, $this->leeway); 72 | } 73 | 74 | /** 75 | * Determine whether the value is in the past. 76 | * 77 | * @param mixed $value 78 | * @return bool 79 | */ 80 | protected function isPast($value) 81 | { 82 | return UtilsTrait::isPast($value, $this->leeway); 83 | } 84 | 85 | /** 86 | * Set the leeway in seconds. 87 | * 88 | * @param int $leeway 89 | * @return $this 90 | */ 91 | public function setLeeway($leeway) 92 | { 93 | $this->leeway = $leeway; 94 | 95 | return $this; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Http/Middleware/AbstractBaseFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Middleware; 14 | 15 | use CodeIgniter\Http\Request; 16 | use CodeIgniter\HTTP\Response; 17 | use CodeIgniter\HTTP\ResponseInterface; 18 | use Fluent\Auth\Exceptions\AuthenticationException; 19 | use Fluent\Auth\Facades\Auth; 20 | use Fluent\JWTAuth\Exceptions\JWTException; 21 | use Fluent\JWTAuth\JWTGuard; 22 | 23 | abstract class AbstractBaseFilter 24 | { 25 | /** 26 | * The JWT Authenticator. 27 | * 28 | * @var JWTGuard 29 | */ 30 | protected $auth; 31 | 32 | /** 33 | * Create a new BaseMiddleware instance. 34 | * 35 | * @return void 36 | */ 37 | public function __construct() 38 | { 39 | $this->auth = Auth::guard('api'); 40 | } 41 | 42 | /** 43 | * Check the request for the presence of a token. 44 | * 45 | * @throws BadRequestHttpException 46 | * @return void 47 | */ 48 | public function checkForToken(Request $request) 49 | { 50 | if (! $this->auth->parser()->setRequest($request)->hasToken()) { 51 | throw new AuthenticationException('Token not provided'); 52 | } 53 | } 54 | 55 | /** 56 | * Attempt to authenticate a user via the token in the request. 57 | * 58 | * @throws UnauthorizedHttpException 59 | * @return void 60 | */ 61 | public function authenticate(Request $request) 62 | { 63 | $this->checkForToken($request); 64 | 65 | try { 66 | if (! $this->auth->check()) { 67 | throw new AuthenticationException('User not found'); 68 | } 69 | } catch (JWTException $e) { 70 | throw new AuthenticationException($e->getMessage(), [], $e->getCode()); 71 | } 72 | } 73 | 74 | /** 75 | * Set the authentication header. 76 | * 77 | * @param string|null $token 78 | * @return ResponseInterface 79 | */ 80 | protected function setAuthenticationHeader(Response $response, $token = null) 81 | { 82 | $token = $token ?: $this->auth->refresh(); 83 | 84 | return $response->setHeader('Authorization', 'Bearer ' . $token); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Http/Parser/HttpParser.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Http\Parser; 14 | 15 | use CodeIgniter\Http\Request; 16 | 17 | class HttpParser 18 | { 19 | /** 20 | * The chain. 21 | * 22 | * @var array 23 | */ 24 | private $chain; 25 | 26 | /** 27 | * The request. 28 | * 29 | * @var Request 30 | */ 31 | protected $request; 32 | 33 | /** 34 | * @param array $chain 35 | * @return void 36 | */ 37 | public function __construct(Request $request, array $chain = []) 38 | { 39 | $this->request = $request; 40 | $this->chain = $chain; 41 | } 42 | 43 | /** 44 | * Get the parser chain. 45 | * 46 | * @return array 47 | */ 48 | public function getChain() 49 | { 50 | return $this->chain; 51 | } 52 | 53 | /** 54 | * Add a new parser to the chain. 55 | * 56 | * @param array|\Fluent\JWTAuth\Contracts\Http\ParserInterface $parsers 57 | * 58 | * @return $this 59 | */ 60 | public function addParser($parsers) 61 | { 62 | $this->chain = array_merge($this->chain, is_array($parsers) ? $parsers : [$parsers]); 63 | 64 | return $this; 65 | } 66 | 67 | /** 68 | * Set the order of the parser chain. 69 | * 70 | * @param array $chain 71 | * @return $this 72 | */ 73 | public function setChain(array $chain) 74 | { 75 | $this->chain = $chain; 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Alias for setting the order of the chain. 82 | * 83 | * @param array $chain 84 | * @return $this 85 | */ 86 | public function setChainOrder(array $chain) 87 | { 88 | return $this->setChain($chain); 89 | } 90 | 91 | /** 92 | * Iterate through the parsers and attempt to retrieve 93 | * a value, otherwise return null. 94 | * 95 | * @return string|null 96 | */ 97 | public function parseToken() 98 | { 99 | foreach ($this->chain as $parser) { 100 | if ($response = $parser->parse($this->request)) { 101 | return $response; 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * Check whether a token exists in the chain. 108 | * 109 | * @return bool 110 | */ 111 | public function hasToken() 112 | { 113 | return $this->parseToken() !== null; 114 | } 115 | 116 | /** 117 | * Set the request instance. 118 | * 119 | * @return $this 120 | */ 121 | public function setRequest(Request $request) 122 | { 123 | $this->request = $request; 124 | 125 | return $this; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Validators/PayloadValidator.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Validators; 14 | 15 | use Fluent\JWTAuth\Claims\Collection; 16 | use Fluent\JWTAuth\Exceptions\TokenExpiredException; 17 | use Fluent\JWTAuth\Exceptions\TokenInvalidException; 18 | 19 | class PayloadValidator extends AbstractValidator 20 | { 21 | /** 22 | * The required claims. 23 | * 24 | * @var array 25 | */ 26 | protected $requiredClaims = [ 27 | 'iss', 28 | 'iat', 29 | 'exp', 30 | 'nbf', 31 | 'sub', 32 | 'jti', 33 | ]; 34 | 35 | /** 36 | * The refresh TTL. 37 | * 38 | * @var int 39 | */ 40 | protected $refreshTTL = 20160; 41 | 42 | /** 43 | * Run the validations on the payload array. 44 | * 45 | * @param Collection $value 46 | * @return Collection 47 | */ 48 | public function check($value) 49 | { 50 | $this->validateStructure($value); 51 | 52 | return $this->refreshFlow ? $this->validateRefresh($value) : $this->validatePayload($value); 53 | } 54 | 55 | /** 56 | * Ensure the payload contains the required claims and 57 | * the claims have the relevant type. 58 | * 59 | * @throws TokenInvalidException 60 | * @return void 61 | */ 62 | protected function validateStructure(Collection $claims) 63 | { 64 | if ($this->requiredClaims && ! $claims->hasAllClaims($this->requiredClaims)) { 65 | throw new TokenInvalidException('JWT payload does not contain the required claims'); 66 | } 67 | } 68 | 69 | /** 70 | * Validate the payload timestamps. 71 | * 72 | * @throws TokenExpiredException 73 | * @throws TokenInvalidException 74 | * @return Collection 75 | */ 76 | protected function validatePayload(Collection $claims) 77 | { 78 | return $claims->validate('payload'); 79 | } 80 | 81 | /** 82 | * Check the token in the refresh flow context. 83 | * 84 | * @throws TokenExpiredException 85 | * @return Collection 86 | */ 87 | protected function validateRefresh(Collection $claims) 88 | { 89 | return $this->refreshTTL === null ? $claims : $claims->validate('refresh', $this->refreshTTL); 90 | } 91 | 92 | /** 93 | * Set the required claims. 94 | * 95 | * @param array $claims 96 | * @return $this 97 | */ 98 | public function setRequiredClaims(array $claims) 99 | { 100 | $this->requiredClaims = $claims; 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Set the refresh ttl. 107 | * 108 | * @param int $ttl 109 | * @return $this 110 | */ 111 | public function setRefreshTTL($ttl) 112 | { 113 | $this->refreshTTL = $ttl; 114 | 115 | return $this; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Claims/Collection.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | use Fluent\JWTAuth\Claims\Claim; 16 | use Tightenco\Collect\Support\Collection as IlluminateCollection; 17 | 18 | use function array_shift; 19 | use function call_user_func_array; 20 | use function count; 21 | use function func_get_args; 22 | use function is_string; 23 | use function ucfirst; 24 | 25 | class Collection extends IlluminateCollection 26 | { 27 | /** 28 | * Create a new collection. 29 | * 30 | * @param mixed $items 31 | * @return void 32 | */ 33 | public function __construct($items = []) 34 | { 35 | parent::__construct($this->getArrayableItems($items)); 36 | } 37 | 38 | /** 39 | * Get a Claim instance by it's unique name. 40 | * 41 | * @param string $name 42 | * @param mixed $default 43 | * @return Claim 44 | */ 45 | public function getByClaimName($name, ?callable $callback = null, $default = null) 46 | { 47 | return $this->filter(function (Claim $claim) use ($name) { 48 | return $claim->getName() === $name; 49 | })->first($callback, $default); 50 | } 51 | 52 | /** 53 | * Validate each claim under a given context. 54 | * 55 | * @param string $context 56 | * @return $this 57 | */ 58 | public function validate($context = 'payload') 59 | { 60 | $args = func_get_args(); 61 | array_shift($args); 62 | 63 | $this->each(function ($claim) use ($context, $args) { 64 | call_user_func_array( 65 | [$claim, 'validate' . ucfirst($context)], 66 | $args 67 | ); 68 | }); 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Determine if the Collection contains all of the given keys. 75 | * 76 | * @param mixed $claims 77 | * @return bool 78 | */ 79 | public function hasAllClaims($claims) 80 | { 81 | return count($claims) && (new static($claims))->diff($this->keys())->isEmpty(); 82 | } 83 | 84 | /** 85 | * Get the claims as key/val array. 86 | * 87 | * @return array 88 | */ 89 | public function toPlainArray() 90 | { 91 | return $this->map(function (Claim $claim) { 92 | return $claim->getValue(); 93 | })->toArray(); 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | protected function getArrayableItems($items) 100 | { 101 | return $this->sanitizeClaims($items); 102 | } 103 | 104 | /** 105 | * Ensure that the given claims array is keyed by the claim name. 106 | * 107 | * @param mixed $items 108 | * @return array 109 | */ 110 | private function sanitizeClaims($items) 111 | { 112 | $claims = []; 113 | foreach ($items as $key => $value) { 114 | if (! is_string($key) && $value instanceof Claim) { 115 | $key = $value->getName(); 116 | } 117 | 118 | $claims[$key] = $value; 119 | } 120 | 121 | return $claims; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Claims/Claim.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | use Fluent\JWTAuth\Contracts\ClaimInterface; 16 | use Fluent\JWTAuth\Exceptions\InvalidClaimException; 17 | use JsonSerializable; 18 | use Tightenco\Collect\Contracts\Support\Arrayable; 19 | use Tightenco\Collect\Contracts\Support\Jsonable; 20 | 21 | use function json_encode; 22 | 23 | use const JSON_UNESCAPED_SLASHES; 24 | 25 | abstract class Claim implements Arrayable, ClaimInterface, Jsonable, JsonSerializable 26 | { 27 | /** 28 | * The claim name. 29 | * 30 | * @var string 31 | */ 32 | protected $name; 33 | 34 | /** 35 | * The claim value. 36 | * 37 | * @var mixed 38 | */ 39 | private $value; 40 | 41 | /** 42 | * @param mixed $value 43 | * @return void 44 | */ 45 | public function __construct($value) 46 | { 47 | $this->setValue($value); 48 | } 49 | 50 | /** 51 | * Set the claim value, and call a validate method. 52 | * 53 | * @param mixed $value 54 | * @throws InvalidClaimException 55 | * @return $this 56 | */ 57 | public function setValue($value) 58 | { 59 | $this->value = $this->validateCreate($value); 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Get the claim value. 66 | * 67 | * @return mixed 68 | */ 69 | public function getValue() 70 | { 71 | return $this->value; 72 | } 73 | 74 | /** 75 | * Set the claim name. 76 | * 77 | * @param string $name 78 | * @return $this 79 | */ 80 | public function setName($name) 81 | { 82 | $this->name = $name; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Get the claim name. 89 | * 90 | * @return string 91 | */ 92 | public function getName() 93 | { 94 | return $this->name; 95 | } 96 | 97 | /** 98 | * Validate the claim in a standalone Claim context. 99 | * 100 | * @param mixed $value 101 | * @return bool 102 | */ 103 | public function validateCreate($value) 104 | { 105 | return $value; 106 | } 107 | 108 | /** 109 | * Validate the Claim within a Payload context. 110 | * 111 | * @return bool 112 | */ 113 | public function validatePayload() 114 | { 115 | return $this->getValue(); 116 | } 117 | 118 | /** 119 | * Validate the Claim within a refresh context. 120 | * 121 | * @param int $refreshTTL 122 | * @return bool 123 | */ 124 | public function validateRefresh($refreshTTL) 125 | { 126 | return $this->getValue(); 127 | } 128 | 129 | /** 130 | * Checks if the value matches the claim. 131 | * 132 | * @param mixed $value 133 | * @param bool $strict 134 | * @return bool 135 | */ 136 | public function matches($value, $strict = true) 137 | { 138 | return $strict ? $this->value === $value : $this->value == $value; 139 | } 140 | 141 | /** 142 | * Convert the object into something JSON serializable. 143 | * 144 | * @return array 145 | */ 146 | #[\ReturnTypeWillChange] 147 | public function jsonSerialize() 148 | { 149 | return $this->toArray(); 150 | } 151 | 152 | /** 153 | * Build a key value array comprising of the claim name and value. 154 | * 155 | * @return array 156 | */ 157 | public function toArray() 158 | { 159 | return [$this->getName() => $this->getValue()]; 160 | } 161 | 162 | /** 163 | * Get the claim as JSON. 164 | * 165 | * @param int $options 166 | * @return string 167 | */ 168 | public function toJson($options = JSON_UNESCAPED_SLASHES) 169 | { 170 | return json_encode($this->toArray(), $options); 171 | } 172 | 173 | /** 174 | * Get the payload as a string. 175 | * 176 | * @return string 177 | */ 178 | public function __toString() 179 | { 180 | return $this->toJson(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Providers/JWT/Provider.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Providers\JWT; 14 | 15 | use Fluent\JWTAuth\Exceptions\JWTException; 16 | use Lcobucci\JWT\Signer\Key; 17 | use Tightenco\Collect\Support\Arr; 18 | 19 | abstract class Provider 20 | { 21 | /** 22 | * The secret. 23 | * 24 | * @var string 25 | */ 26 | protected $secret; 27 | 28 | /** 29 | * The array of keys. 30 | * 31 | * @var array 32 | */ 33 | protected $keys; 34 | 35 | /** 36 | * The used algorithm. 37 | * 38 | * @var string 39 | */ 40 | protected $algo; 41 | 42 | /** 43 | * @param string $secret 44 | * @param string $algo 45 | * @param array $keys 46 | * @return void 47 | */ 48 | public function __construct($secret, $algo, array $keys) 49 | { 50 | $this->secret = $secret; 51 | $this->algo = $algo; 52 | $this->keys = $keys; 53 | } 54 | 55 | /** 56 | * Set the algorithm used to sign the token. 57 | * 58 | * @param string $algo 59 | * @return $this 60 | */ 61 | public function setAlgo($algo) 62 | { 63 | $this->algo = $algo; 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * Get the algorithm used to sign the token. 70 | * 71 | * @return string 72 | */ 73 | public function getAlgo() 74 | { 75 | return $this->algo; 76 | } 77 | 78 | /** 79 | * Set the secret used to sign the token. 80 | * 81 | * @param string $secret 82 | * @return $this 83 | */ 84 | public function setSecret($secret) 85 | { 86 | $this->secret = $secret; 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * Get the secret used to sign the token. 93 | * 94 | * @return string 95 | */ 96 | public function getSecret() 97 | { 98 | return $this->secret; 99 | } 100 | 101 | /** 102 | * Set the keys used to sign the token. 103 | * 104 | * @param array $keys 105 | * @return $this 106 | */ 107 | public function setKeys(array $keys) 108 | { 109 | $this->keys = $keys; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Get the array of keys used to sign tokens 116 | * with an asymmetric algorithm. 117 | * 118 | * @return array 119 | */ 120 | public function getKeys() 121 | { 122 | return $this->keys; 123 | } 124 | 125 | /** 126 | * Get the public key used to sign tokens 127 | * with an asymmetric algorithm. 128 | * 129 | * @return resource|string 130 | */ 131 | public function getPublicKey() 132 | { 133 | return Arr::get($this->keys, 'public'); 134 | } 135 | 136 | /** 137 | * Get the private key used to sign tokens 138 | * with an asymmetric algorithm. 139 | * 140 | * @return resource|string 141 | */ 142 | public function getPrivateKey() 143 | { 144 | return Arr::get($this->keys, 'private'); 145 | } 146 | 147 | /** 148 | * Get the passphrase used to sign tokens 149 | * with an asymmetric algorithm. 150 | * 151 | * @return string 152 | */ 153 | public function getPassphrase() 154 | { 155 | return Arr::get($this->keys, 'passphrase'); 156 | } 157 | 158 | /** 159 | * Get the key used to sign the tokens. 160 | * 161 | * @return Key|null 162 | */ 163 | protected function getSigningKey() 164 | { 165 | return $this->isAsymmetric() ? $this->getPrivateKey() : $this->getSecret(); 166 | } 167 | 168 | /** 169 | * Get the key used to verify the tokens. 170 | * 171 | * @return resource|string 172 | */ 173 | protected function getVerificationKey() 174 | { 175 | return $this->isAsymmetric() ? $this->getPublicKey() : $this->getSecret(); 176 | } 177 | 178 | /** 179 | * Determine if the algorithm is asymmetric, and thus 180 | * requires a public/private key combo. 181 | * 182 | * @throws JWTException 183 | * @return bool 184 | */ 185 | abstract protected function isAsymmetric(); 186 | } 187 | -------------------------------------------------------------------------------- /src/Config/Services.php: -------------------------------------------------------------------------------- 1 | lockSubject(static::config('lock_subject')); 42 | } 43 | 44 | /** 45 | * Service manager. 46 | * 47 | * @return Manager 48 | */ 49 | public static function manager(bool $getShared = true) 50 | { 51 | if ($getShared) { 52 | return static::getSharedInstance('manager'); 53 | } 54 | 55 | return (new Manager( 56 | static::getSharedInstance('lcobuccy'), 57 | static::getSharedInstance('blacklist'), 58 | static::getSharedInstance('factory') 59 | )) 60 | ->setBlacklistEnabled(static::config('blacklist_enabled')) 61 | ->setPersistentClaims(static::config('persistent_claims')); 62 | } 63 | 64 | /** 65 | * Service lcobuccy. 66 | * 67 | * @return JWTInterface 68 | */ 69 | public static function lcobuccy(bool $getShared = true) 70 | { 71 | if ($getShared) { 72 | return static::getSharedInstance('lcobuccy'); 73 | } 74 | 75 | return new Lcobucci( 76 | new Builder(), 77 | new Parser(), 78 | static::config('secret'), 79 | static::config('algo'), 80 | static::config('keys') 81 | ); 82 | } 83 | 84 | /** 85 | * Service blacklist. 86 | * 87 | * @return Blacklist 88 | */ 89 | public static function blacklist(bool $getShared = true) 90 | { 91 | if ($getShared) { 92 | return static::getSharedInstance('blacklist'); 93 | } 94 | 95 | return (new Blacklist(new CacheStorage(static::getSharedInstance('cache')))) 96 | ->setGracePeriod(static::config('blacklist_grace_period')) 97 | ->setRefreshTTL(static::config('refresh_ttl')); 98 | } 99 | 100 | /** 101 | * Service factory. 102 | * 103 | * @return Factory 104 | */ 105 | public static function factory(bool $getShared = true) 106 | { 107 | if ($getShared) { 108 | return static::getSharedInstance('factory'); 109 | } 110 | 111 | return new Factory( 112 | (new ClaimsFactory(static::getSharedInstance('request'))) 113 | ->setTTL(static::config('ttl')) 114 | ->setLeeway(static::config('leeway')), 115 | (new PayloadValidator()) 116 | ->setRefreshTTL(static::config('refresh_ttl')) 117 | ->setRequiredClaims(static::config('required_claims')) 118 | ); 119 | } 120 | 121 | /** 122 | * Services httpparser. 123 | * 124 | * @return HttpParser 125 | */ 126 | public static function httpparser(bool $getShared = true) 127 | { 128 | if ($getShared) { 129 | return static::getSharedInstance('httpparser'); 130 | } 131 | 132 | return new HttpParser( 133 | static::getSharedInstance('request'), 134 | [ 135 | new AuthHeaders(), 136 | new Cookies(), 137 | new InputSource(), 138 | new QueryString(), 139 | ] 140 | ); 141 | } 142 | 143 | /** 144 | * Helper to get the config values. 145 | * 146 | * @param string $key 147 | * @return mixed 148 | */ 149 | protected static function config($key) 150 | { 151 | return Factories::config('JWT', ['getShared' => true])->$key; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Claims/Factory.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Claims; 14 | 15 | use CodeIgniter\Http\Request; 16 | use Fluent\JWTAuth\Claims\Claim; 17 | use Fluent\JWTAuth\Support\UtilsTrait; 18 | 19 | use function array_key_exists; 20 | use function bin2hex; 21 | use function method_exists; 22 | use function random_bytes; 23 | 24 | class Factory 25 | { 26 | /** 27 | * The request. 28 | * 29 | * @var Request 30 | */ 31 | protected $request; 32 | 33 | /** 34 | * The TTL. 35 | * 36 | * @var int 37 | */ 38 | protected $ttl = 60; 39 | 40 | /** 41 | * Time leeway in seconds. 42 | * 43 | * @var int 44 | */ 45 | protected $leeway = 0; 46 | 47 | /** 48 | * The classes map. 49 | * 50 | * @var array 51 | */ 52 | private $classMap = [ 53 | 'aud' => Audience::class, 54 | 'exp' => Expiration::class, 55 | 'iat' => IssuedAt::class, 56 | 'iss' => Issuer::class, 57 | 'jti' => JwtId::class, 58 | 'nbf' => NotBefore::class, 59 | 'sub' => Subject::class, 60 | ]; 61 | 62 | /** 63 | * @return void 64 | */ 65 | public function __construct(Request $request) 66 | { 67 | $this->request = $request; 68 | } 69 | 70 | /** 71 | * Get the instance of the claim when passing the name and value. 72 | * 73 | * @param string $name 74 | * @param mixed $value 75 | * @return Claim 76 | */ 77 | public function get($name, $value) 78 | { 79 | if ($this->has($name)) { 80 | $claim = new $this->classMap[$name]($value); 81 | 82 | return method_exists($claim, 'setLeeway') 83 | ? $claim->setLeeway($this->leeway) 84 | : $claim; 85 | } 86 | 87 | return new Custom($name, $value); 88 | } 89 | 90 | /** 91 | * Check whether the claim exists. 92 | * 93 | * @param string $name 94 | * @return bool 95 | */ 96 | public function has($name) 97 | { 98 | return array_key_exists($name, $this->classMap); 99 | } 100 | 101 | /** 102 | * Generate the initial value and return the Claim instance. 103 | * 104 | * @param string $name 105 | * @return Claim 106 | */ 107 | public function make($name) 108 | { 109 | return $this->get($name, $this->$name()); 110 | } 111 | 112 | /** 113 | * Get the Issuer (iss) claim. 114 | * 115 | * @return string 116 | */ 117 | public function iss() 118 | { 119 | return $this->request->getUri()->getScheme() . '://' . $this->request->getUri()->getHost(); 120 | } 121 | 122 | /** 123 | * Get the Issued At (iat) claim. 124 | * 125 | * @return int 126 | */ 127 | public function iat() 128 | { 129 | return UtilsTrait::now()->getTimestamp(); 130 | } 131 | 132 | /** 133 | * Get the Expiration (exp) claim. 134 | * 135 | * @return int 136 | */ 137 | public function exp() 138 | { 139 | return UtilsTrait::now()->addMinutes($this->ttl)->getTimestamp(); 140 | } 141 | 142 | /** 143 | * Get the Not Before (nbf) claim. 144 | * 145 | * @return int 146 | */ 147 | public function nbf() 148 | { 149 | return UtilsTrait::now()->getTimestamp(); 150 | } 151 | 152 | /** 153 | * Get the JWT Id (jti) claim. 154 | * 155 | * @return string 156 | */ 157 | public function jti() 158 | { 159 | return bin2hex(random_bytes(32)); 160 | } 161 | 162 | /** 163 | * Add a new claim mapping. 164 | * 165 | * @param string $name 166 | * @param string $classPath 167 | * @return $this 168 | */ 169 | public function extend($name, $classPath) 170 | { 171 | $this->classMap[$name] = $classPath; 172 | 173 | return $this; 174 | } 175 | 176 | /** 177 | * Set the request instance. 178 | * 179 | * @return $this 180 | */ 181 | public function setRequest(Request $request) 182 | { 183 | $this->request = $request; 184 | 185 | return $this; 186 | } 187 | 188 | /** 189 | * Set the token ttl (in minutes). 190 | * 191 | * @param int $ttl 192 | * @return $this 193 | */ 194 | public function setTTL($ttl) 195 | { 196 | $this->ttl = $ttl; 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * Get the token ttl. 203 | * 204 | * @return int 205 | */ 206 | public function getTTL() 207 | { 208 | return $this->ttl; 209 | } 210 | 211 | /** 212 | * Set the leeway in seconds. 213 | * 214 | * @param int $leeway 215 | * @return $this 216 | */ 217 | public function setLeeway($leeway) 218 | { 219 | $this->leeway = $leeway; 220 | 221 | return $this; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Providers/JWT/Lcobucci.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Providers\JWT; 14 | 15 | use Exception; 16 | use Fluent\JWTAuth\Contracts\Providers\JWTInterface; 17 | use Fluent\JWTAuth\Exceptions\JWTException; 18 | use Fluent\JWTAuth\Exceptions\TokenInvalidException; 19 | use Lcobucci\JWT\Builder; 20 | use Lcobucci\JWT\Parser; 21 | use Lcobucci\JWT\Signer; 22 | use Lcobucci\JWT\Signer\Ecdsa; 23 | use Lcobucci\JWT\Signer\Ecdsa\Sha256 as ES256; 24 | use Lcobucci\JWT\Signer\Ecdsa\Sha384 as ES384; 25 | use Lcobucci\JWT\Signer\Ecdsa\Sha512 as ES512; 26 | use Lcobucci\JWT\Signer\Hmac\Sha256 as HS256; 27 | use Lcobucci\JWT\Signer\Hmac\Sha384 as HS384; 28 | use Lcobucci\JWT\Signer\Hmac\Sha512 as HS512; 29 | use Lcobucci\JWT\Signer\Key; 30 | use Lcobucci\JWT\Signer\Rsa; 31 | use Lcobucci\JWT\Signer\Rsa\Sha256 as RS256; 32 | use Lcobucci\JWT\Signer\Rsa\Sha384 as RS384; 33 | use Lcobucci\JWT\Signer\Rsa\Sha512 as RS512; 34 | use ReflectionClass; 35 | use Tightenco\Collect\Support\Collection; 36 | 37 | use function array_key_exists; 38 | use function is_object; 39 | 40 | class Lcobucci extends Provider implements JWTInterface 41 | { 42 | /** 43 | * The Builder instance. 44 | * 45 | * @var Builder 46 | */ 47 | protected $builder; 48 | 49 | /** 50 | * The Parser instance. 51 | * 52 | * @var Parser 53 | */ 54 | protected $parser; 55 | 56 | /** 57 | * Create the Lcobucci provider. 58 | * 59 | * @param string $secret 60 | * @param string $algo 61 | * @param array $keys 62 | * @return void 63 | */ 64 | public function __construct( 65 | Builder $builder, 66 | Parser $parser, 67 | $secret, 68 | $algo, 69 | array $keys 70 | ) { 71 | parent::__construct($secret, $algo, $keys); 72 | 73 | $this->builder = $builder; 74 | $this->parser = $parser; 75 | $this->signer = $this->getSigner(); 76 | } 77 | 78 | /** 79 | * Signers that this provider supports. 80 | * 81 | * @var array 82 | */ 83 | protected $signers = [ 84 | 'HS256' => HS256::class, 85 | 'HS384' => HS384::class, 86 | 'HS512' => HS512::class, 87 | 'RS256' => RS256::class, 88 | 'RS384' => RS384::class, 89 | 'RS512' => RS512::class, 90 | 'ES256' => ES256::class, 91 | 'ES384' => ES384::class, 92 | 'ES512' => ES512::class, 93 | ]; 94 | 95 | /** 96 | * Create a JSON Web Token. 97 | * 98 | * @param array $payload 99 | * @throws JWTException 100 | * @return string 101 | */ 102 | public function encode(array $payload) 103 | { 104 | // Remove the signature on the builder instance first. 105 | $this->builder->unsign(); 106 | 107 | try { 108 | foreach ($payload as $key => $value) { 109 | $this->builder->set($key, $value); 110 | } 111 | $this->builder->sign($this->signer, $this->getSigningKey()); 112 | } catch (Exception $e) { 113 | throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e); 114 | } 115 | 116 | return (string) $this->builder->getToken(); 117 | } 118 | 119 | /** 120 | * Decode a JSON Web Token. 121 | * 122 | * @param string $token 123 | * @throws JWTException 124 | * @return array 125 | */ 126 | public function decode($token) 127 | { 128 | try { 129 | $jwt = $this->parser->parse($token); 130 | } catch (Exception $e) { 131 | throw new TokenInvalidException('Could not decode token: ' . $e->getMessage(), $e->getCode(), $e); 132 | } 133 | 134 | if (! $jwt->verify($this->signer, $this->getVerificationKey())) { 135 | throw new TokenInvalidException('Token Signature could not be verified.'); 136 | } 137 | 138 | return (new Collection($jwt->getClaims()))->map(function ($claim) { 139 | return is_object($claim) ? $claim->getValue() : $claim; 140 | })->toArray(); 141 | } 142 | 143 | /** 144 | * Get the signer instance. 145 | * 146 | * @throws JWTException 147 | * @return Signer 148 | */ 149 | protected function getSigner() 150 | { 151 | if (! array_key_exists($this->algo, $this->signers)) { 152 | throw new JWTException('The given algorithm could not be found'); 153 | } 154 | 155 | return new $this->signers[$this->algo](); 156 | } 157 | 158 | /** 159 | * {@inheritdoc} 160 | */ 161 | protected function isAsymmetric() 162 | { 163 | $reflect = new ReflectionClass($this->signer); 164 | 165 | return $reflect->isSubclassOf(Rsa::class) || $reflect->isSubclassOf(Ecdsa::class); 166 | } 167 | 168 | /** 169 | * {@inheritdoc} 170 | */ 171 | protected function getSigningKey() 172 | { 173 | return $this->isAsymmetric() 174 | ? new Key($this->getPrivateKey(), $this->getPassphrase()) 175 | : new Key($this->getSecret()); 176 | } 177 | 178 | /** 179 | * {@inheritdoc} 180 | */ 181 | protected function getVerificationKey() 182 | { 183 | return $this->isAsymmetric() 184 | ? new Key($this->getPublicKey()) 185 | : new Key($this->getSecret()); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Blacklist.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth; 14 | 15 | use Fluent\JWTAuth\Contracts\Providers\StorageInterface; 16 | use Fluent\JWTAuth\Payload; 17 | use Fluent\JWTAuth\Support\UtilsTrait; 18 | 19 | use function value; 20 | 21 | class Blacklist 22 | { 23 | /** 24 | * The storage. 25 | * 26 | * @var StorageInterface 27 | */ 28 | protected $storage; 29 | 30 | /** 31 | * The grace period when a token is blacklisted. In seconds. 32 | * 33 | * @var int 34 | */ 35 | protected $gracePeriod = 0; 36 | 37 | /** 38 | * Number of minutes from issue date in which a JWT can be refreshed. 39 | * 40 | * @var int 41 | */ 42 | protected $refreshTTL = 20160; 43 | 44 | /** 45 | * The unique key held within the blacklist. 46 | * 47 | * @var string 48 | */ 49 | protected $key = 'jti'; 50 | 51 | /** 52 | * @return void 53 | */ 54 | public function __construct(StorageInterface $storage) 55 | { 56 | $this->storage = $storage; 57 | } 58 | 59 | /** 60 | * Add the token (jti claim) to the blacklist. 61 | * 62 | * @return bool 63 | */ 64 | public function add(Payload $payload) 65 | { 66 | // if there is no exp claim then add the jwt to 67 | // the blacklist indefinitely 68 | if (! $payload->hasKey('exp')) { 69 | return $this->addForever($payload); 70 | } 71 | 72 | // if we have already added this token to the blacklist 73 | if (! empty($this->storage->get($this->getKey($payload)))) { 74 | return true; 75 | } 76 | 77 | $this->storage->add( 78 | $this->getKey($payload), 79 | ['valid_until' => $this->getGraceTimestamp()], 80 | $this->getMinutesUntilExpired($payload) 81 | ); 82 | 83 | return true; 84 | } 85 | 86 | /** 87 | * Get the number of minutes until the token expiry. 88 | * 89 | * @return int 90 | */ 91 | protected function getMinutesUntilExpired(Payload $payload) 92 | { 93 | $exp = UtilsTrait::timestamp($payload['exp']); 94 | $iat = UtilsTrait::timestamp($payload['iat']); 95 | 96 | // get the latter of the two expiration dates and find 97 | // the number of minutes until the expiration date, 98 | // plus 1 minute to avoid overlap 99 | return $exp->max($iat->addMinutes($this->refreshTTL))->addMinute()->diffInRealMinutes(); 100 | } 101 | 102 | /** 103 | * Add the token (jti claim) to the blacklist indefinitely. 104 | * 105 | * @return bool 106 | */ 107 | public function addForever(Payload $payload) 108 | { 109 | $this->storage->forever($this->getKey($payload), 'forever'); 110 | 111 | return true; 112 | } 113 | 114 | /** 115 | * Determine whether the token has been blacklisted. 116 | * 117 | * @return bool 118 | */ 119 | public function has(Payload $payload) 120 | { 121 | $val = $this->storage->get($this->getKey($payload)); 122 | 123 | // exit early if the token was blacklisted forever, 124 | if ($val === 'forever') { 125 | return true; 126 | } 127 | 128 | // check whether the expiry + grace has past 129 | return ! empty($val) && ! UtilsTrait::isFuture($val['valid_until']); 130 | } 131 | 132 | /** 133 | * Remove the token (jti claim) from the blacklist. 134 | * 135 | * @return bool 136 | */ 137 | public function remove(Payload $payload) 138 | { 139 | return $this->storage->destroy($this->getKey($payload)); 140 | } 141 | 142 | /** 143 | * Remove all tokens from the blacklist. 144 | * 145 | * @return bool 146 | */ 147 | public function clear() 148 | { 149 | $this->storage->flush(); 150 | 151 | return true; 152 | } 153 | 154 | /** 155 | * Get the timestamp when the blacklist comes into effect 156 | * This defaults to immediate (0 seconds). 157 | * 158 | * @return int 159 | */ 160 | protected function getGraceTimestamp() 161 | { 162 | return UtilsTrait::now()->addSeconds($this->gracePeriod)->getTimestamp(); 163 | } 164 | 165 | /** 166 | * Set the grace period. 167 | * 168 | * @param int $gracePeriod 169 | * @return $this 170 | */ 171 | public function setGracePeriod($gracePeriod) 172 | { 173 | $this->gracePeriod = (int) $gracePeriod; 174 | 175 | return $this; 176 | } 177 | 178 | /** 179 | * Get the grace period. 180 | * 181 | * @return int 182 | */ 183 | public function getGracePeriod() 184 | { 185 | return $this->gracePeriod; 186 | } 187 | 188 | /** 189 | * Get the unique key held within the blacklist. 190 | * 191 | * @return mixed 192 | */ 193 | public function getKey(Payload $payload) 194 | { 195 | return $payload($this->key); 196 | } 197 | 198 | /** 199 | * Set the unique key held within the blacklist. 200 | * 201 | * @param string $key 202 | * @return $this 203 | */ 204 | public function setKey($key) 205 | { 206 | $this->key = value($key); 207 | 208 | return $this; 209 | } 210 | 211 | /** 212 | * Set the refresh time limit. 213 | * 214 | * @param int $ttl 215 | * @return $this 216 | */ 217 | public function setRefreshTTL($ttl) 218 | { 219 | $this->refreshTTL = (int) $ttl; 220 | 221 | return $this; 222 | } 223 | 224 | /** 225 | * Get the refresh time limit. 226 | * 227 | * @return int 228 | */ 229 | public function getRefreshTTL() 230 | { 231 | return $this->refreshTTL; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth; 14 | 15 | use Fluent\JWTAuth\Claims\Claim; 16 | use Fluent\JWTAuth\Claims\Collection; 17 | use Fluent\JWTAuth\Claims\Factory as ClaimFactory; 18 | use Fluent\JWTAuth\Payload; 19 | use Fluent\JWTAuth\Support\CustomClaimsTrait; 20 | use Fluent\JWTAuth\Support\RefreshFlowTrait; 21 | use Fluent\JWTAuth\Validators\PayloadValidator; 22 | 23 | use function array_search; 24 | 25 | class Factory 26 | { 27 | use CustomClaimsTrait; 28 | use RefreshFlowTrait; 29 | 30 | /** 31 | * The claim factory. 32 | * 33 | * @var ClaimFactory 34 | */ 35 | protected $claimFactory; 36 | 37 | /** 38 | * The validator. 39 | * 40 | * @var PayloadValidator 41 | */ 42 | protected $validator; 43 | 44 | /** 45 | * The default claims. 46 | * 47 | * @var array 48 | */ 49 | protected $defaultClaims = [ 50 | 'iss', 51 | 'iat', 52 | 'exp', 53 | 'nbf', 54 | 'jti', 55 | ]; 56 | 57 | /** 58 | * The claims collection. 59 | * 60 | * @var Collection 61 | */ 62 | protected $claims; 63 | 64 | /** 65 | * @return void 66 | */ 67 | public function __construct(ClaimFactory $claimFactory, PayloadValidator $validator) 68 | { 69 | $this->claimFactory = $claimFactory; 70 | $this->validator = $validator; 71 | $this->claims = new Collection(); 72 | } 73 | 74 | /** 75 | * Create the Payload instance. 76 | * 77 | * @param bool $resetClaims 78 | * @return Payload 79 | */ 80 | public function make($resetClaims = false) 81 | { 82 | if ($resetClaims) { 83 | $this->emptyClaims(); 84 | } 85 | 86 | return $this->withClaims($this->buildClaimsCollection()); 87 | } 88 | 89 | /** 90 | * Empty the claims collection. 91 | * 92 | * @return $this 93 | */ 94 | public function emptyClaims() 95 | { 96 | $this->claims = new Collection(); 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Add an array of claims to the Payload. 103 | * 104 | * @param array $claims 105 | * @return $this 106 | */ 107 | protected function addClaims(array $claims) 108 | { 109 | foreach ($claims as $name => $value) { 110 | $this->addClaim($name, $value); 111 | } 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * Add a claim to the Payload. 118 | * 119 | * @param string $name 120 | * @param mixed $value 121 | * @return $this 122 | */ 123 | protected function addClaim($name, $value) 124 | { 125 | $this->claims->put($name, $value); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Build the default claims. 132 | * 133 | * @return $this 134 | */ 135 | protected function buildClaims() 136 | { 137 | // remove the exp claim if it exists and the ttl is null 138 | if ($this->claimFactory->getTTL() === null && $key = array_search('exp', $this->defaultClaims)) { 139 | unset($this->defaultClaims[$key]); 140 | } 141 | 142 | // add the default claims 143 | foreach ($this->defaultClaims as $claim) { 144 | $this->addClaim($claim, $this->claimFactory->make($claim)); 145 | } 146 | 147 | // add custom claims on top, allowing them to overwrite defaults 148 | return $this->addClaims($this->getCustomClaims()); 149 | } 150 | 151 | /** 152 | * Build out the Claim DTO's. 153 | * 154 | * @return Collection 155 | */ 156 | protected function resolveClaims() 157 | { 158 | return $this->claims->map(function ($value, $name) { 159 | return $value instanceof Claim ? $value : $this->claimFactory->get($name, $value); 160 | }); 161 | } 162 | 163 | /** 164 | * Build and get the Claims Collection. 165 | * 166 | * @return Collection 167 | */ 168 | public function buildClaimsCollection() 169 | { 170 | return $this->buildClaims()->resolveClaims(); 171 | } 172 | 173 | /** 174 | * Get a Payload instance with a claims collection. 175 | * 176 | * @return Payload 177 | */ 178 | public function withClaims(Collection $claims) 179 | { 180 | return new Payload($claims, $this->validator, $this->refreshFlow); 181 | } 182 | 183 | /** 184 | * Set the default claims to be added to the Payload. 185 | * 186 | * @param array $claims 187 | * @return $this 188 | */ 189 | public function setDefaultClaims(array $claims) 190 | { 191 | $this->defaultClaims = $claims; 192 | 193 | return $this; 194 | } 195 | 196 | /** 197 | * Helper to set the ttl. 198 | * 199 | * @param int $ttl 200 | * @return $this 201 | */ 202 | public function setTTL($ttl) 203 | { 204 | $this->claimFactory->setTTL($ttl); 205 | 206 | return $this; 207 | } 208 | 209 | /** 210 | * Helper to get the ttl. 211 | * 212 | * @return int 213 | */ 214 | public function getTTL() 215 | { 216 | return $this->claimFactory->getTTL(); 217 | } 218 | 219 | /** 220 | * Get the default claims. 221 | * 222 | * @return array 223 | */ 224 | public function getDefaultClaims() 225 | { 226 | return $this->defaultClaims; 227 | } 228 | 229 | /** 230 | * Get the PayloadValidator instance. 231 | * 232 | * @return PayloadValidator 233 | */ 234 | public function validator() 235 | { 236 | return $this->validator; 237 | } 238 | 239 | /** 240 | * Magically add a claim. 241 | * 242 | * @param string $method 243 | * @param array $parameters 244 | * @return $this 245 | */ 246 | public function __call($method, $parameters) 247 | { 248 | $this->addClaim($method, $parameters[0]); 249 | 250 | return $this; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/Manager.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth; 14 | 15 | use Fluent\JWTAuth\Blacklist; 16 | use Fluent\JWTAuth\Contracts\Providers\JWTInterface; 17 | use Fluent\JWTAuth\Exceptions\JWTException; 18 | use Fluent\JWTAuth\Exceptions\TokenBlacklistedException; 19 | use Fluent\JWTAuth\Factory; 20 | use Fluent\JWTAuth\Payload; 21 | use Fluent\JWTAuth\Support\CustomClaimsTrait; 22 | use Fluent\JWTAuth\Support\RefreshFlowTrait; 23 | use Fluent\JWTAuth\Token; 24 | 25 | use function array_merge; 26 | use function call_user_func; 27 | use function collect; 28 | 29 | class Manager 30 | { 31 | use CustomClaimsTrait; 32 | use RefreshFlowTrait; 33 | 34 | /** 35 | * The provider. 36 | * 37 | * @var JWTInterface 38 | */ 39 | protected $provider; 40 | 41 | /** 42 | * The blacklist. 43 | * 44 | * @var Blacklist 45 | */ 46 | protected $blacklist; 47 | 48 | /** 49 | * the payload factory. 50 | * 51 | * @var Factory 52 | */ 53 | protected $payloadFactory; 54 | 55 | /** 56 | * The blacklist flag. 57 | * 58 | * @var bool 59 | */ 60 | protected $blacklistEnabled = true; 61 | 62 | /** 63 | * the persistent claims. 64 | * 65 | * @var array 66 | */ 67 | protected $persistentClaims = []; 68 | 69 | /** 70 | * @return void 71 | */ 72 | public function __construct(JWTInterface $provider, Blacklist $blacklist, Factory $payloadFactory) 73 | { 74 | $this->provider = $provider; 75 | $this->blacklist = $blacklist; 76 | $this->payloadFactory = $payloadFactory; 77 | } 78 | 79 | /** 80 | * Encode a Payload and return the Token. 81 | * 82 | * @return Token 83 | */ 84 | public function encode(Payload $payload) 85 | { 86 | $token = $this->provider->encode($payload->get()); 87 | 88 | return new Token($token); 89 | } 90 | 91 | /** 92 | * Decode a Token and return the Payload. 93 | * 94 | * @param bool $checkBlacklist 95 | * @throws TokenBlacklistedException 96 | * @return Payload 97 | */ 98 | public function decode(Token $token, $checkBlacklist = true) 99 | { 100 | $payloadArray = $this->provider->decode($token->get()); 101 | 102 | $payload = $this->payloadFactory 103 | ->setRefreshFlow($this->refreshFlow) 104 | ->customClaims($payloadArray) 105 | ->make(); 106 | 107 | if ($checkBlacklist && $this->blacklistEnabled && $this->blacklist->has($payload)) { 108 | throw new TokenBlacklistedException('The token has been blacklisted'); 109 | } 110 | 111 | return $payload; 112 | } 113 | 114 | /** 115 | * Refresh a Token and return a new Token. 116 | * 117 | * @param bool $forceForever 118 | * @param bool $resetClaims 119 | * @return Token 120 | */ 121 | public function refresh(Token $token, $forceForever = false, $resetClaims = false) 122 | { 123 | $this->setRefreshFlow(); 124 | 125 | $claims = $this->buildRefreshClaims($this->decode($token)); 126 | 127 | if ($this->blacklistEnabled) { 128 | // Invalidate old token 129 | $this->invalidate($token, $forceForever); 130 | } 131 | 132 | // Return the new token 133 | return $this->encode( 134 | $this->payloadFactory->customClaims($claims)->make($resetClaims) 135 | ); 136 | } 137 | 138 | /** 139 | * Invalidate a Token by adding it to the blacklist. 140 | * 141 | * @param bool $forceForever 142 | * @throws JWTException 143 | * @return bool 144 | */ 145 | public function invalidate(Token $token, $forceForever = false) 146 | { 147 | if (! $this->blacklistEnabled) { 148 | throw new JWTException('You must have the blacklist enabled to invalidate a token.'); 149 | } 150 | 151 | return call_user_func( 152 | [$this->blacklist, $forceForever ? 'addForever' : 'add'], 153 | $this->decode($token, false) 154 | ); 155 | } 156 | 157 | /** 158 | * Build the claims to go into the refreshed token. 159 | * 160 | * @return array 161 | */ 162 | protected function buildRefreshClaims(Payload $payload) 163 | { 164 | // Get the claims to be persisted from the payload 165 | $persistentClaims = collect($payload->toArray()) 166 | ->only($this->persistentClaims) 167 | ->toArray(); 168 | 169 | // persist the relevant claims 170 | return array_merge( 171 | $this->customClaims, 172 | $persistentClaims, 173 | [ 174 | 'sub' => $payload['sub'], 175 | 'iat' => $payload['iat'], 176 | ] 177 | ); 178 | } 179 | 180 | /** 181 | * Get the Payload Factory instance. 182 | * 183 | * @return Factory 184 | */ 185 | public function getPayloadFactory() 186 | { 187 | return $this->payloadFactory; 188 | } 189 | 190 | /** 191 | * Get the JWTProvider instance. 192 | * 193 | * @return JWTContract 194 | */ 195 | public function getJWTProvider() 196 | { 197 | return $this->provider; 198 | } 199 | 200 | /** 201 | * Get the Blacklist instance. 202 | * 203 | * @return Blacklist 204 | */ 205 | public function getBlacklist() 206 | { 207 | return $this->blacklist; 208 | } 209 | 210 | /** 211 | * Set whether the blacklist is enabled. 212 | * 213 | * @param bool $enabled 214 | * @return $this 215 | */ 216 | public function setBlacklistEnabled($enabled) 217 | { 218 | $this->blacklistEnabled = $enabled; 219 | 220 | return $this; 221 | } 222 | 223 | /** 224 | * Set the claims to be persisted when refreshing a token. 225 | * 226 | * @param array $claims 227 | * @return $this 228 | */ 229 | public function setPersistentClaims(array $claims) 230 | { 231 | $this->persistentClaims = $claims; 232 | 233 | return $this; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/Payload.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth; 14 | 15 | use ArrayAccess; 16 | use BadMethodCallException; 17 | use Countable; 18 | use Fluent\JWTAuth\Claims\Claim; 19 | use Fluent\JWTAuth\Claims\Collection; 20 | use Fluent\JWTAuth\Exceptions\PayloadException; 21 | use Fluent\JWTAuth\Validators\PayloadValidator; 22 | use JsonSerializable; 23 | use Tightenco\Collect\Contracts\Support\Arrayable; 24 | use Tightenco\Collect\Contracts\Support\Jsonable; 25 | use Tightenco\Collect\Support\Arr; 26 | 27 | use function array_map; 28 | use function count; 29 | use function get_class; 30 | use function is_array; 31 | use function json_encode; 32 | use function preg_match; 33 | use function sprintf; 34 | use function value; 35 | 36 | use const JSON_UNESCAPED_SLASHES; 37 | 38 | class Payload implements ArrayAccess, Arrayable, Countable, Jsonable, JsonSerializable 39 | { 40 | /** 41 | * The collection of claims. 42 | * 43 | * @var Collection 44 | */ 45 | private $claims; 46 | 47 | /** 48 | * Build the Payload. 49 | * 50 | * @param bool $fefreshFlow 51 | * @return void 52 | */ 53 | public function __construct(Collection $claims, PayloadValidator $validator, $refreshFlow = false) 54 | { 55 | $this->claims = $validator->setRefreshFlow($refreshFlow)->check($claims); 56 | } 57 | 58 | /** 59 | * Get the array of claim instances. 60 | * 61 | * @return Collection 62 | */ 63 | public function getClaims() 64 | { 65 | return $this->claims; 66 | } 67 | 68 | /** 69 | * Checks if a payload matches some expected values. 70 | * 71 | * @param array $values 72 | * @param bool $strict 73 | * @return bool 74 | */ 75 | public function matches(array $values, $strict = false) 76 | { 77 | if (empty($values)) { 78 | return false; 79 | } 80 | 81 | $claims = $this->getClaims(); 82 | 83 | foreach ($values as $key => $value) { 84 | if (! $claims->has($key) || ! $claims->get($key)->matches($value, $strict)) { 85 | return false; 86 | } 87 | } 88 | 89 | return true; 90 | } 91 | 92 | /** 93 | * Checks if a payload strictly matches some expected values. 94 | * 95 | * @param array $values 96 | * @return bool 97 | */ 98 | public function matchesStrict(array $values) 99 | { 100 | return $this->matches($values, true); 101 | } 102 | 103 | /** 104 | * Get the payload. 105 | * 106 | * @param mixed $claim 107 | * @return mixed 108 | */ 109 | public function get($claim = null) 110 | { 111 | $claim = value($claim); 112 | 113 | if ($claim !== null) { 114 | if (is_array($claim)) { 115 | return array_map([$this, 'get'], $claim); 116 | } 117 | 118 | return Arr::get($this->toArray(), $claim); 119 | } 120 | 121 | return $this->toArray(); 122 | } 123 | 124 | /** 125 | * Get the underlying Claim instance. 126 | * 127 | * @param string $claim 128 | * @return Claim 129 | */ 130 | public function getInternal($claim) 131 | { 132 | return $this->claims->getByClaimName($claim); 133 | } 134 | 135 | /** 136 | * Determine whether the payload has the claim (by instance). 137 | * 138 | * @return bool 139 | */ 140 | public function has(Claim $claim) 141 | { 142 | return $this->claims->has($claim->getName()); 143 | } 144 | 145 | /** 146 | * Determine whether the payload has the claim (by key). 147 | * 148 | * @param string $claim 149 | * @return bool 150 | */ 151 | public function hasKey($claim) 152 | { 153 | return $this->offsetExists($claim); 154 | } 155 | 156 | /** 157 | * Get the array of claims. 158 | * 159 | * @return array 160 | */ 161 | public function toArray() 162 | { 163 | return $this->claims->toPlainArray(); 164 | } 165 | 166 | /** 167 | * Convert the object into something JSON serializable. 168 | * 169 | * @return array 170 | */ 171 | #[\ReturnTypeWillChange] 172 | public function jsonSerialize() 173 | { 174 | return $this->toArray(); 175 | } 176 | 177 | /** 178 | * Get the payload as JSON. 179 | * 180 | * @param int $options 181 | * @return string 182 | */ 183 | public function toJson($options = JSON_UNESCAPED_SLASHES) 184 | { 185 | return json_encode($this->toArray(), $options); 186 | } 187 | 188 | /** 189 | * Get the payload as a string. 190 | * 191 | * @return string 192 | */ 193 | public function __toString() 194 | { 195 | return $this->toJson(); 196 | } 197 | 198 | /** 199 | * Determine if an item exists at an offset. 200 | * 201 | * @param mixed $key 202 | * @return bool 203 | */ 204 | #[\ReturnTypeWillChange] 205 | public function offsetExists($key) 206 | { 207 | return Arr::has($this->toArray(), $key); 208 | } 209 | 210 | /** 211 | * Get an item at a given offset. 212 | * 213 | * @param mixed $key 214 | * @return mixed 215 | */ 216 | #[\ReturnTypeWillChange] 217 | public function offsetGet($key) 218 | { 219 | return Arr::get($this->toArray(), $key); 220 | } 221 | 222 | /** 223 | * Don't allow changing the payload as it should be immutable. 224 | * 225 | * @param mixed $key 226 | * @param mixed $value 227 | * @throws PayloadException 228 | */ 229 | #[\ReturnTypeWillChange] 230 | public function offsetSet($key, $value) 231 | { 232 | throw new PayloadException('The payload is immutable'); 233 | } 234 | 235 | /** 236 | * Don't allow changing the payload as it should be immutable. 237 | * 238 | * @param string $key 239 | * @throws PayloadException 240 | * @return void 241 | */ 242 | #[\ReturnTypeWillChange] 243 | public function offsetUnset($key) 244 | { 245 | throw new PayloadException('The payload is immutable'); 246 | } 247 | 248 | /** 249 | * Count the number of claims. 250 | * 251 | * @return int 252 | */ 253 | #[\ReturnTypeWillChange] 254 | public function count() 255 | { 256 | return count($this->toArray()); 257 | } 258 | 259 | /** 260 | * Invoke the Payload as a callable function. 261 | * 262 | * @param mixed $claim 263 | * @return mixed 264 | */ 265 | public function __invoke($claim = null) 266 | { 267 | return $this->get($claim); 268 | } 269 | 270 | /** 271 | * Magically get a claim value. 272 | * 273 | * @param string $method 274 | * @param array $parameters 275 | * @throws BadMethodCallException 276 | * @return mixed 277 | */ 278 | public function __call($method, $parameters) 279 | { 280 | if (preg_match('/get(.+)\b/i', $method, $matches)) { 281 | foreach ($this->claims as $claim) { 282 | if (get_class($claim) === 'Fluent\\JWTAuth\\Claims\\' . $matches[1]) { 283 | return $claim->getValue(); 284 | } 285 | } 286 | } 287 | 288 | throw new BadMethodCallException(sprintf('The claim [%s] does not exist on the payload.', $method)); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/Config/JWT.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth\Config; 14 | 15 | use CodeIgniter\Config\BaseConfig; 16 | 17 | class JWT extends BaseConfig 18 | { 19 | /** 20 | * -------------------------------------------------------------------------- 21 | * JWT Authentication Secret 22 | * -------------------------------------------------------------------------- 23 | * 24 | * Don't forget to set this in your .env file, as it will be used to sign 25 | * your tokens. A helper command is provided for this: 26 | * `php artisan jwt:secret` 27 | * 28 | * Note: This will be used for Symmetric algorithms only (HMAC), 29 | * since RSA and ECDSA use a private/public key combo (See below). 30 | * 31 | * @var string 32 | */ 33 | public $secret = ''; 34 | 35 | /** 36 | * -------------------------------------------------------------------------- 37 | * JWT Authentication Keys 38 | * -------------------------------------------------------------------------- 39 | * 40 | * The algorithm you are using, will determine whether your tokens are 41 | * signed with a random string (defined in `JWT_SECRET`) or using the 42 | * following public & private keys. 43 | * 44 | * Symmetric Algorithms: 45 | * HS256, HS384 & HS512 will use `JWT_SECRET`. 46 | * 47 | * Asymmetric Algorithms: 48 | * RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below. 49 | * 50 | * @var array 51 | */ 52 | public $keys = [ 53 | 54 | /** 55 | * -------------------------------------------------------------------------- 56 | * Public Key 57 | * -------------------------------------------------------------------------- 58 | * 59 | * A path or resource to your public key. 60 | * 61 | * E.g. 'file://path/to/public/key' 62 | * 63 | * @var array 64 | */ 65 | 'public' => '', 66 | 67 | /** 68 | * -------------------------------------------------------------------------- 69 | * Private Key 70 | * -------------------------------------------------------------------------- 71 | * 72 | * A path or resource to your private key. 73 | * 74 | * E.g. 'file://path/to/private/key' 75 | * 76 | * @var array 77 | */ 78 | 'private' => '', 79 | 80 | /** 81 | * -------------------------------------------------------------------------- 82 | * Passphrase 83 | * -------------------------------------------------------------------------- 84 | * 85 | * The passphrase for your private key. Can be null if none set. 86 | * 87 | * @var array 88 | */ 89 | 'passphrase' => '', 90 | ]; 91 | 92 | /** 93 | * -------------------------------------------------------------------------- 94 | * JWT time to live 95 | * -------------------------------------------------------------------------- 96 | * 97 | * Specify the length of time (in minutes) that the token will be valid for. 98 | * Defaults to 1 hour. 99 | * 100 | * You can also set this to null, to yield a never expiring token. Some people 101 | * may want this behaviour for e.g. a mobile app. This is not particularly 102 | * recommended, so make sure you have appropriate systems in place to 103 | * revoke the token if necessary. Notice: If you set this to null 104 | * you should remove 'exp' element from 'required_claims' list. 105 | * 106 | * @var int 107 | */ 108 | public $ttl = 60; 109 | 110 | /** 111 | * -------------------------------------------------------------------------- 112 | * Refresh time to live 113 | * -------------------------------------------------------------------------- 114 | * 115 | * Specify the length of time (in minutes) that the token can be refreshed 116 | * within. I.E. The user can refresh their token within a 2 week window of 117 | * the original token being created until they must re-authenticate. 118 | * Defaults to 2 weeks. 119 | * 120 | * You can also set this to null, to yield an infinite refresh time. 121 | * Some may want this instead of never expiring tokens for e.g. a mobile app. 122 | * This is not particularly recommended, so make sure you have appropriate 123 | * systems in place to revoke the token if necessary. 124 | * 125 | * @var int 126 | */ 127 | public $refresh_ttl = 20160; 128 | 129 | /** 130 | * -------------------------------------------------------------------------- 131 | * JWT hashing algorithm 132 | * -------------------------------------------------------------------------- 133 | * 134 | * Specify the hashing algorithm that will be used to sign the token. 135 | * 136 | * @see https://github.com/agungsugiarto/codeigniter4-authentication-jwt/blob/da2f8ad6429bb6ddc4e965cdc47953412044774d/src/Providers/JWT/Lcobucci.php#L83-L93 137 | * for possible values. 138 | * 139 | * @var string 140 | */ 141 | public $algo = 'HS256'; 142 | 143 | /** 144 | * -------------------------------------------------------------------------- 145 | * Required Claims 146 | * -------------------------------------------------------------------------- 147 | * 148 | * Specify the required claims that must exist in any token. 149 | * A TokenInvalidException will be thrown if any of these claims are not 150 | * present in the payload. 151 | * 152 | * @var array 153 | */ 154 | public $required_claims = [ 155 | 'iss', 156 | 'iat', 157 | 'exp', 158 | 'nbf', 159 | 'sub', 160 | 'jti', 161 | ]; 162 | 163 | /** 164 | * -------------------------------------------------------------------------- 165 | * Persistent Claims 166 | * -------------------------------------------------------------------------- 167 | * 168 | * Specify the claim keys to be persisted when refreshing a token. 169 | * `sub` and `iat` will automatically be persisted, in 170 | * addition to the these claims. 171 | * 172 | * Note: If a claim does not exist then it will be ignored. 173 | * 174 | * @var array 175 | */ 176 | public $persistent_claims = [ 177 | // 'foo', 178 | // 'bar', 179 | ]; 180 | 181 | /** 182 | * -------------------------------------------------------------------------- 183 | * Lock Subject 184 | * -------------------------------------------------------------------------- 185 | * 186 | * This will determine whether a `prv` claim is automatically added to 187 | * the token. The purpose of this is to ensure that if you have multiple 188 | * authentication models e.g. `App\User` & `App\OtherPerson`, then we 189 | * should prevent one authentication request from impersonating another, 190 | * if 2 tokens happen to have the same id across the 2 different models. 191 | * 192 | * Under specific circumstances, you may want to disable this behaviour 193 | * e.g. if you only have one authentication model, then you would save 194 | * a little on token size. 195 | * 196 | * @var bool 197 | */ 198 | public $lock_subject = true; 199 | 200 | /** 201 | * -------------------------------------------------------------------------- 202 | * Leeway 203 | * -------------------------------------------------------------------------- 204 | * 205 | * This property gives the jwt timestamp claims some "leeway". 206 | * Meaning that if you have any unavoidable slight clock skew on 207 | * any of your servers then this will afford you some level of cushioning. 208 | * 209 | * This applies to the claims `iat`, `nbf` and `exp`. 210 | * 211 | * Specify in seconds - only if you know you need it. 212 | * 213 | * @var int 214 | */ 215 | public $leeway = 0; 216 | 217 | /** 218 | * -------------------------------------------------------------------------- 219 | * Blacklist Enabled 220 | * -------------------------------------------------------------------------- 221 | * 222 | * In order to invalidate tokens, you must have the blacklist enabled. 223 | * If you do not want or need this functionality, then set this to false. 224 | * 225 | * @var bool 226 | */ 227 | public $blacklist_enabled = true; 228 | 229 | /** 230 | * ------------------------------------------------------------------------- 231 | * Blacklist Grace Period 232 | * ------------------------------------------------------------------------- 233 | * 234 | * When multiple concurrent requests are made with the same JWT, 235 | * it is possible that some of them fail, due to token regeneration 236 | * on every request. 237 | * 238 | * Set grace period in seconds to prevent parallel request failure. 239 | * 240 | * @var int 241 | */ 242 | public $blacklist_grace_period = 0; 243 | } 244 | -------------------------------------------------------------------------------- /src/JWT.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth; 14 | 15 | use BadMethodCallException; 16 | use CodeIgniter\Http\Request; 17 | use Fluent\JWTAuth\Blacklist; 18 | use Fluent\JWTAuth\Contracts\JWTSubjectInterface; 19 | use Fluent\JWTAuth\Exceptions\JWTException; 20 | use Fluent\JWTAuth\Factory; 21 | use Fluent\JWTAuth\Http\Parser\HttpParser; 22 | use Fluent\JWTAuth\Manager; 23 | use Fluent\JWTAuth\Payload; 24 | use Fluent\JWTAuth\Support\CustomClaimsTrait; 25 | use Fluent\JWTAuth\Token; 26 | 27 | use function array_merge; 28 | use function call_user_func_array; 29 | use function get_class; 30 | use function is_object; 31 | use function method_exists; 32 | use function sha1; 33 | 34 | class JWT 35 | { 36 | use CustomClaimsTrait; 37 | 38 | /** 39 | * The authentication manager. 40 | * 41 | * @var Manager 42 | */ 43 | protected $manager; 44 | 45 | /** 46 | * The HTTP parser. 47 | * 48 | * @var HttpParser 49 | */ 50 | protected $parser; 51 | 52 | /** 53 | * The token. 54 | * 55 | * @var Token|null 56 | */ 57 | protected $token; 58 | 59 | /** 60 | * Lock the subject. 61 | * 62 | * @var bool 63 | */ 64 | protected $lockSubject = true; 65 | 66 | /** 67 | * @return void 68 | */ 69 | public function __construct(Manager $manager, HttpParser $parser) 70 | { 71 | $this->manager = $manager; 72 | $this->parser = $parser; 73 | } 74 | 75 | /** 76 | * Generate a token for a given subject. 77 | * 78 | * @param JWTSubjectInterface $subject 79 | * @return string 80 | */ 81 | public function fromSubject($subject) 82 | { 83 | $payload = $this->makePayload($subject); 84 | 85 | return $this->manager->encode($payload)->get(); 86 | } 87 | 88 | /** 89 | * Alias to generate a token for a given user. 90 | * 91 | * @param JWTSubjectInterface $user 92 | * @return string 93 | */ 94 | public function fromUser($user) 95 | { 96 | return $this->fromSubject($user); 97 | } 98 | 99 | /** 100 | * Refresh an expired token. 101 | * 102 | * @param bool $forceForever 103 | * @param bool $resetClaims 104 | * @return string 105 | */ 106 | public function refresh($forceForever = false, $resetClaims = false) 107 | { 108 | $this->requireToken(); 109 | 110 | return $this->manager->customClaims($this->getCustomClaims()) 111 | ->refresh($this->token, $forceForever, $resetClaims) 112 | ->get(); 113 | } 114 | 115 | /** 116 | * Invalidate a token (add it to the blacklist). 117 | * 118 | * @param bool $forceForever 119 | * @return $this 120 | */ 121 | public function invalidate($forceForever = false) 122 | { 123 | $this->requireToken(); 124 | 125 | $this->manager->invalidate($this->token, $forceForever); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Alias to get the payload, and as a result checks that 132 | * the token is valid i.e. not expired or blacklisted. 133 | * 134 | * @throws JWTException 135 | * @return Payload 136 | */ 137 | public function checkOrFail() 138 | { 139 | return $this->getPayload(); 140 | } 141 | 142 | /** 143 | * Check that the token is valid. 144 | * 145 | * @param bool $getPayload 146 | * @return Payload|bool 147 | */ 148 | public function check($getPayload = false) 149 | { 150 | try { 151 | $payload = $this->checkOrFail(); 152 | } catch (JWTException $e) { 153 | return false; 154 | } 155 | 156 | return $getPayload ? $payload : true; 157 | } 158 | 159 | /** 160 | * Get the token. 161 | * 162 | * @return Token|null 163 | */ 164 | public function getToken() 165 | { 166 | if ($this->token === null) { 167 | try { 168 | $this->parseToken(); 169 | } catch (JWTException $e) { 170 | $this->token = null; 171 | } 172 | } 173 | 174 | return $this->token; 175 | } 176 | 177 | /** 178 | * Parse the token from the request. 179 | * 180 | * @throws JWTException 181 | * @return $this 182 | */ 183 | public function parseToken() 184 | { 185 | if (! $token = $this->parser->parseToken()) { 186 | throw new JWTException('The token could not be parsed from the request'); 187 | } 188 | 189 | return $this->setToken($token); 190 | } 191 | 192 | /** 193 | * Get the raw Payload instance. 194 | * 195 | * @return Payload 196 | */ 197 | public function getPayload() 198 | { 199 | $this->requireToken(); 200 | 201 | return $this->manager->decode($this->token); 202 | } 203 | 204 | /** 205 | * Alias for getPayload(). 206 | * 207 | * @return Payload 208 | */ 209 | public function payload() 210 | { 211 | return $this->getPayload(); 212 | } 213 | 214 | /** 215 | * Convenience method to get a claim value. 216 | * 217 | * @param string $claim 218 | * @return mixed 219 | */ 220 | public function getClaim($claim) 221 | { 222 | return $this->payload()->get($claim); 223 | } 224 | 225 | /** 226 | * Create a Payload instance. 227 | * 228 | * @param JWTSubjectInterface $subject 229 | * @return Payload 230 | */ 231 | public function makePayload($subject) 232 | { 233 | return $this->factory()->customClaims($this->getClaimsArray($subject))->make(); 234 | } 235 | 236 | /** 237 | * Build the claims array and return it. 238 | * 239 | * @param JWTSubjectInterface $subject 240 | * @return array 241 | */ 242 | protected function getClaimsArray($subject) 243 | { 244 | return array_merge( 245 | $this->getClaimsForSubject($subject), 246 | $subject->getJWTCustomClaims(), // custom claims from JWTSubjectInterface method 247 | $this->customClaims // custom claims from inline setter 248 | ); 249 | } 250 | 251 | /** 252 | * Get the claims associated with a given subject. 253 | * 254 | * @param JWTSubjectInterface $subject 255 | * @return array 256 | */ 257 | protected function getClaimsForSubject($subject) 258 | { 259 | return array_merge([ 260 | 'sub' => $subject->getJWTIdentifier(), 261 | ], $this->lockSubject ? ['prv' => $this->hashSubjectModel($subject)] : []); 262 | } 263 | 264 | /** 265 | * Hash the subject model and return it. 266 | * 267 | * @param string|object $model 268 | * @return string 269 | */ 270 | protected function hashSubjectModel($model) 271 | { 272 | return sha1(is_object($model) ? get_class($model) : $model); 273 | } 274 | 275 | /** 276 | * Check if the subject model matches the one saved in the token. 277 | * 278 | * @param string|object $model 279 | * @return bool 280 | */ 281 | public function checkSubjectModel($model) 282 | { 283 | if (($prv = $this->payload()->get('prv')) === null) { 284 | return true; 285 | } 286 | 287 | return $this->hashSubjectModel($model) === $prv; 288 | } 289 | 290 | /** 291 | * Set the token. 292 | * 293 | * @param Token|string $token 294 | * @return $this 295 | */ 296 | public function setToken($token) 297 | { 298 | $this->token = $token instanceof Token ? $token : new Token($token); 299 | 300 | return $this; 301 | } 302 | 303 | /** 304 | * Unset the current token. 305 | * 306 | * @return $this 307 | */ 308 | public function unsetToken() 309 | { 310 | $this->token = null; 311 | 312 | return $this; 313 | } 314 | 315 | /** 316 | * Ensure that a token is available. 317 | * 318 | * @throws JWTException 319 | * @return void 320 | */ 321 | protected function requireToken() 322 | { 323 | if (! $this->token) { 324 | throw new JWTException('A token is required'); 325 | } 326 | } 327 | 328 | /** 329 | * Set the request instance. 330 | * 331 | * @return $this 332 | */ 333 | public function setRequest(Request $request) 334 | { 335 | $this->parser->setRequest($request); 336 | 337 | return $this; 338 | } 339 | 340 | /** 341 | * Set whether the subject should be "locked". 342 | * 343 | * @param bool $lock 344 | * @return $this 345 | */ 346 | public function lockSubject($lock) 347 | { 348 | $this->lockSubject = $lock; 349 | 350 | return $this; 351 | } 352 | 353 | /** 354 | * Get the Manager instance. 355 | * 356 | * @return Manager 357 | */ 358 | public function manager() 359 | { 360 | return $this->manager; 361 | } 362 | 363 | /** 364 | * Get the Parser instance. 365 | * 366 | * @return HttpParser 367 | */ 368 | public function parser() 369 | { 370 | return $this->parser; 371 | } 372 | 373 | /** 374 | * Get the Payload Factory. 375 | * 376 | * @return Factory 377 | */ 378 | public function factory() 379 | { 380 | return $this->manager->getPayloadFactory(); 381 | } 382 | 383 | /** 384 | * Get the Blacklist. 385 | * 386 | * @return Blacklist 387 | */ 388 | public function blacklist() 389 | { 390 | return $this->manager->getBlacklist(); 391 | } 392 | 393 | /** 394 | * Magically call the JWT Manager. 395 | * 396 | * @param string $method 397 | * @param array $parameters 398 | * @throws BadMethodCallException 399 | * @return mixed 400 | */ 401 | public function __call($method, $parameters) 402 | { 403 | if (method_exists($this->manager, $method)) { 404 | return call_user_func_array([$this->manager, $method], $parameters); 405 | } 406 | 407 | throw new BadMethodCallException("Method [$method] does not exist."); 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/JWTGuard.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Agung Sugiarto 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Fluent\JWTAuth; 14 | 15 | use BadMethodCallException; 16 | use CodeIgniter\Events\Events; 17 | use CodeIgniter\HTTP\RequestInterface; 18 | use Exception; 19 | use Fluent\Auth\Contracts\AuthenticationInterface; 20 | use Fluent\Auth\Contracts\AuthenticatorInterface; 21 | use Fluent\Auth\Contracts\UserProviderInterface; 22 | use Fluent\Auth\Traits\GuardHelperTrait; 23 | use Fluent\JWTAuth\Exceptions\JWTException; 24 | use Fluent\JWTAuth\Exceptions\UserNotDefinedException; 25 | use Fluent\JWTAuth\JWT; 26 | use Fluent\JWTAuth\Payload; 27 | use Fluent\JWTAuth\Token; 28 | 29 | use function call_user_func_array; 30 | use function method_exists; 31 | 32 | class JWTGuard implements AuthenticationInterface 33 | { 34 | use GuardHelperTrait; 35 | 36 | /** @var AuthenticatorInterface */ 37 | protected $lastAttempted; 38 | 39 | /** 40 | * The JWT instance. 41 | * 42 | * @var JWT 43 | */ 44 | protected $jwt; 45 | 46 | /** @var RequestInterface */ 47 | protected $request; 48 | 49 | /** 50 | * Instantiate the class. 51 | * 52 | * @return void 53 | */ 54 | public function __construct(JWT $jwt, RequestInterface $request, UserProviderInterface $provider) 55 | { 56 | $this->jwt = $jwt; 57 | $this->request = $request; 58 | $this->provider = $provider; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function user() 65 | { 66 | if ($this->user !== null) { 67 | return $this->user; 68 | } 69 | 70 | if ( 71 | $this->jwt->setRequest($this->request)->getToken() && 72 | ($payload = $this->jwt->check(true)) 73 | ) { 74 | $this->user = $this->provider->findById($payload['sub']); 75 | 76 | Events::trigger('fireLoginEvent', $this->user, true); 77 | } 78 | 79 | return $this->user; 80 | } 81 | 82 | /** 83 | * Get the currently authenticated user or throws an exception. 84 | * 85 | * @return AuthenticatorInterface 86 | * @throws UserNotDefinedException 87 | */ 88 | public function userOrFail() 89 | { 90 | if (! $user = $this->user()) { 91 | throw new UserNotDefinedException(); 92 | } 93 | 94 | return $user; 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function validate(array $credentials): bool 101 | { 102 | $this->lastAttempted = $user = $this->provider->findByCredentials($credentials); 103 | 104 | return $this->hasValidCredentials($user, $credentials); 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function attempt(array $credentials, bool $remember = true) 111 | { 112 | Events::trigger('fireAttemptEvent', $credentials, $remember); 113 | 114 | $this->lastAttempted = $user = $this->provider->findByCredentials($credentials); 115 | 116 | if ($this->hasValidCredentials($user, $credentials)) { 117 | // We can return JWT token if pass second argument set to true, 118 | // otherwise will be return bool. 119 | return $this->login($user, $remember); 120 | } 121 | 122 | Events::trigger('fireFailedEvent', $user, $credentials); 123 | 124 | return false; 125 | } 126 | 127 | /** 128 | * {@inheritdoc} 129 | */ 130 | public function login(AuthenticatorInterface $user, bool $remember = true) 131 | { 132 | $token = $this->jwt->fromUser($user); 133 | 134 | $this->setToken($token)->setUser($user); 135 | 136 | Events::trigger('fireLoginEvent', $user, $remember); 137 | 138 | // Provide codeigniter4/authentitication-implementation 139 | Events::trigger('login', $user, $remember); 140 | 141 | return $remember ? $token : true; 142 | } 143 | 144 | /** 145 | * {@inheritdoc} 146 | */ 147 | public function logout($forceForever = false) 148 | { 149 | $this->requireToken()->invalidate($forceForever); 150 | 151 | Events::trigger('fireLogoutEvent', $this->user); 152 | 153 | // Provide codeigniter4/authentitication-implementation 154 | Events::trigger('logout', $this->user); 155 | 156 | $this->user = null; 157 | $this->jwt->unsetToken(); 158 | } 159 | 160 | /** 161 | * Refresh the token. 162 | * 163 | * @param bool $forceForever 164 | * @param bool $resetClaims 165 | * @return string 166 | */ 167 | public function refresh($forceForever = false, $resetClaims = false) 168 | { 169 | return $this->requireToken()->refresh($forceForever, $resetClaims); 170 | } 171 | 172 | /** 173 | * Invalidate the token. 174 | * 175 | * @param bool $forceForever 176 | * @return JWT 177 | */ 178 | public function invalidate($forceForever = false) 179 | { 180 | return $this->requireToken()->invalidate($forceForever); 181 | } 182 | 183 | /** 184 | * Create a new token by User id. 185 | * 186 | * @param mixed $id 187 | * @return string|null 188 | */ 189 | public function tokenById($id) 190 | { 191 | if ($user = $this->provider->findById($id)) { 192 | return $this->jwt->fromUser($user); 193 | } 194 | } 195 | 196 | /** 197 | * Log a user into the application using their credentials. 198 | * 199 | * @param array $credentials 200 | * @return bool 201 | */ 202 | public function once(array $credentials = []) 203 | { 204 | if ($this->validate($credentials)) { 205 | $this->setUser($this->lastAttempted); 206 | 207 | return true; 208 | } 209 | 210 | return false; 211 | } 212 | 213 | /** 214 | * Log the given User into the application. 215 | * 216 | * @param mixed $id 217 | * @return bool 218 | */ 219 | public function onceUsingId($id) 220 | { 221 | if ($user = $this->provider->findById($id)) { 222 | $this->setUser($user); 223 | 224 | return true; 225 | } 226 | 227 | return false; 228 | } 229 | 230 | /** 231 | * {@inheritdoc} 232 | */ 233 | public function loginById($userId, bool $remember = false) 234 | { 235 | if ($user = $this->provider->findById($userId)) { 236 | $this->login($user, $remember); 237 | 238 | return $user; 239 | } 240 | 241 | return false; 242 | } 243 | 244 | /** 245 | * Add any custom claims. 246 | * 247 | * @param array $claims 248 | * @return $this 249 | */ 250 | public function claims(array $claims) 251 | { 252 | $this->jwt->claims($claims); 253 | 254 | return $this; 255 | } 256 | 257 | /** 258 | * Get the raw Payload instance. 259 | * 260 | * @return Payload 261 | */ 262 | public function getPayload() 263 | { 264 | return $this->requireToken()->getPayload(); 265 | } 266 | 267 | /** 268 | * Alias for getPayload(). 269 | * 270 | * @return Payload 271 | */ 272 | public function payload() 273 | { 274 | return $this->getPayload(); 275 | } 276 | 277 | /** 278 | * Set the token. 279 | * 280 | * @param Token|string $token 281 | * @return $this 282 | */ 283 | public function setToken($token) 284 | { 285 | $this->jwt->setToken($token); 286 | 287 | return $this; 288 | } 289 | 290 | /** 291 | * Set the token ttl. 292 | * 293 | * @param int $ttl 294 | * @return $this 295 | */ 296 | public function setTTL($ttl) 297 | { 298 | $this->jwt->factory()->setTTL($ttl); 299 | 300 | return $this; 301 | } 302 | 303 | /** 304 | * {@inheritdoc} 305 | */ 306 | public function getProvider() 307 | { 308 | return $this->provider; 309 | } 310 | 311 | /** 312 | * {@inheritdoc} 313 | */ 314 | public function setProvider(UserProviderInterface $provider) 315 | { 316 | $this->provider = $provider; 317 | 318 | return $this; 319 | } 320 | 321 | /** 322 | * Return the currently cached user. 323 | * 324 | * @return AuthenticatorInterface|null 325 | */ 326 | public function getUser() 327 | { 328 | return $this->user; 329 | } 330 | 331 | /** 332 | * Get the current request instance. 333 | * 334 | * @return RequestInterface 335 | */ 336 | public function getRequest() 337 | { 338 | return $this->request; 339 | } 340 | 341 | /** 342 | * Set the current request instance. 343 | * 344 | * @return $this 345 | */ 346 | public function setRequest(RequestInterface $request) 347 | { 348 | $this->request = $request; 349 | 350 | return $this; 351 | } 352 | 353 | /** 354 | * Get the last user we attempted to authenticate. 355 | * 356 | * @return AuthenticatorInterface 357 | */ 358 | public function getLastAttempted() 359 | { 360 | return $this->lastAttempted; 361 | } 362 | 363 | /** 364 | * Determine if the user matches the credentials. 365 | * 366 | * @param mixed $user 367 | * @param array $credentials 368 | * @return bool 369 | */ 370 | protected function hasValidCredentials($user, $credentials) 371 | { 372 | $validated = $user !== null && $this->provider->validateCredentials($user, $credentials); 373 | 374 | if ($validated) { 375 | Events::trigger('fireValidatedEvent', $user); 376 | } 377 | 378 | return $validated; 379 | } 380 | 381 | /** 382 | * Ensure that a token is available in the request. 383 | * 384 | * @throws JWTException 385 | * @return JWT 386 | */ 387 | protected function requireToken() 388 | { 389 | if (! $this->jwt->setRequest($this->getRequest())->getToken()) { 390 | throw new JWTException('Token could not be parsed from the request.'); 391 | } 392 | 393 | return $this->jwt; 394 | } 395 | 396 | /** 397 | * {@inheritdoc} 398 | */ 399 | public function getSessionName() 400 | { 401 | throw new Exception('Not implemented.'); 402 | } 403 | 404 | /** 405 | * {@inheritdoc} 406 | */ 407 | public function getCookieName() 408 | { 409 | throw new Exception('Not implemented.'); 410 | } 411 | 412 | /** 413 | * {@inheritdoc} 414 | */ 415 | public function viaRemember() 416 | { 417 | throw new Exception('Not implemented.'); 418 | } 419 | 420 | /** 421 | * Magically call the JWT instance. 422 | * 423 | * @param string $method 424 | * @param array $parameters 425 | * @throws BadMethodCallException 426 | * @return mixed 427 | */ 428 | public function __call($method, $parameters) 429 | { 430 | if (method_exists($this->jwt, $method)) { 431 | return call_user_func_array([$this->jwt, $method], $parameters); 432 | } 433 | 434 | throw new BadMethodCallException("Method [$method] does not exist."); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeIgniter4 Authentication JWT 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/agungsugiarto/codeigniter4-authentication-jwt/v)](https://github.com/agungsugiarto/codeigniter4-authentication-jwt/releases) 4 | [![Total Downloads](https://poser.pugx.org/agungsugiarto/codeigniter4-authentication-jwt/downloads)](https://packagist.org/packages/agungsugiarto/codeigniter4-authentication-jwt/stats) 5 | [![Latest Unstable Version](https://poser.pugx.org/agungsugiarto/codeigniter4-authentication-jwt/v/unstable)](https://packagist.org/packages/agungsugiarto/codeigniter4-authentication-jwt) 6 | [![License](https://poser.pugx.org/agungsugiarto/codeigniter4-authentication-jwt/license)](https://github.com/agungsugiarto/codeigniter4-authentication-jwt/blob/master/LICENSE.md) 7 | ## About 8 | JSON Web Token for codeigniter4-authentication. This package is port from [tymondesigns/jwt-auth](https://github.com/tymondesigns/jwt-auth) for compability with [agungsugiarto/codeigniter4-authentication](https://github.com/agungsugiarto/codeigniter4-authentication). 9 | 10 | ## Documentation 11 | ### Install Via Composer 12 | ```sh 13 | composer require agungsugiarto/codeigniter4-authentication-jwt 14 | ``` 15 | 16 | ### Copy the config 17 | Copy the config file from `vendor/agungsugiarto/codeigniter4-authentication-jwt/src/Config/JWT.php` to config folder of your codeigniter4 application and change class extends from `BaseConfig` to `\Fluent\JWTAuth\Config\JWT` 18 | 19 | ### Update your User entities 20 | Firstly you need to implement the `Fluent\JWTAuth\Contracts\JWTSubjectInterface` contract on your User entities, which requires that you implement the 2 methods `getJWTIdentifier()` and `getJWTCustomClaims()`. 21 | 22 | The example below should give you an idea of how this could look. Obviously you should make any changes, as necessary, to suit your own needs. 23 | ```php 24 | namespace App\Entities; 25 | 26 | //.. 27 | use Fluent\JWTAuth\Contracts\JWTSubjectInterface; 28 | 29 | class User extends Entity implements 30 | //.. 31 | JWTSubjectInterface 32 | { 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getJWTIdentifier() 37 | { 38 | return $this->id; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function getJWTCustomClaims() 45 | { 46 | return []; 47 | } 48 | } 49 | ``` 50 | 51 | ### Adding `\Fluent\JWTAuth\JWTGuard::class` Guards 52 | 53 | We need to define `\Fluent\JWTAuth\JWTGuard::class` authentication guards using the `extend` method on the `Auth` facade or service. You should place your call to the `extend` method within a service provider. Since codeigniter4-authentication already ships with an AuthServiceProvider, we can place the code in that provider. Open `\App\Providers\AuthServiceProvider`: 54 | ```php 55 | namespace App\Providers; 56 | 57 | use Fluent\Auth\AbstractServiceProvider; 58 | use Fluent\Auth\Facades\Auth; 59 | use Fluent\JWTAuth\Config\Services; 60 | use Fluent\JWTAuth\JWTGuard; 61 | 62 | class AuthServiceProvider extends AbstractServiceProvider 63 | { 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public static function register() 68 | { 69 | Auth::extend(JWTGuard::class, function ($auth, $name, array $config) { 70 | return new JWTGuard( 71 | Services::getSharedInstance('jwt'), 72 | Services::getSharedInstance('request'), 73 | $auth->createUserProvider($config['provider']), 74 | ); 75 | }); 76 | } 77 | } 78 | ``` 79 | ### Configure Auth guard 80 | 81 | Inside the `app/Config/Auth.php` file you will need to make a few changes to configure codeigniter4-authentication to use the jwt guard to power your application authentication. 82 | 83 | Make the following changes to the file: 84 | ```php 85 | public $guards = [ 86 | //.. 87 | 'api' => [ 88 | 'driver' => \Fluent\JWTAuth\JWTGuard::class, 89 | 'provider' => 'users', 90 | ], 91 | ]; 92 | ``` 93 | 94 | Here we are telling the api guard to use the `\Fluent\JWTAuth\JWTGuard::class` driver, and we are setting the api guard. 95 | 96 | Next we need to register this `App\Providers\AuthServiceProvider` to lifecycle application. Open `App\Config\Events` add this line: 97 | ```php 98 | Events::on('pre_system', [\App\Providers\AuthServiceProvider::class, 'register']); 99 | ``` 100 | 101 | We can now use codeigniter4-authentication built in Auth system, with codeigniter4-authentication-jwt doing the work behind the scenes! 102 | 103 | ### Add some basic authentication routes 104 | First let's add some routes in app/Config/Routes.php as follows: 105 | ```php 106 | $routes->group('jwt', function ($routes) { 107 | $routes->post('login', 'JwtauthController::login'); 108 | $routes->post('logout', 'JwtauthController::logout', ['filter' => 'auth:api']); 109 | $routes->post('refresh', 'JwtauthController::refresh', ['filter' => 'auth:api']); 110 | $routes->match(['get', 'post'], 'user', 'JwtauthController::user', ['filter' => 'auth:api']); 111 | }); 112 | ``` 113 | 114 | ### Create the AuthController 115 | Then create the `JwtauthController`, either manually or by running the spark command: 116 | ```sh 117 | php spark make:controller JwtauthController 118 | ``` 119 | Then add the following: 120 | ```php 121 | validate(['email' => 'required|valid_email', 'password' => 'required'])) { 141 | return $this->fail($this->validator->getErrors()); 142 | } 143 | 144 | $credentials = [ 145 | 'email' => $this->request->getPost('email'), 146 | 'password' => $this->request->getPost('password') 147 | ]; 148 | 149 | if (! $token = auth('api')->attempt($credentials)) { 150 | return $this->fail(lang('Auth.failed'), 401); 151 | } 152 | 153 | return $this->respondWithToken($token); 154 | } 155 | 156 | /** 157 | * Get the authenticated User. 158 | * 159 | * @return \CodeIgniter\Http\Response 160 | */ 161 | public function user() 162 | { 163 | return $this->response->setJson(auth('api')->user()); 164 | } 165 | 166 | /** 167 | * Log the user out (Invalidate the token). 168 | * 169 | * @return \CodeIgniter\Http\Response 170 | */ 171 | public function logout() 172 | { 173 | auth('api')->logout(); 174 | 175 | return $this->response->setJson(['message' => 'Successfully logged out']); 176 | } 177 | 178 | /** 179 | * Refresh a token. 180 | * 181 | * @return \CodeIgniter\Http\Response 182 | */ 183 | public function refresh() 184 | { 185 | return $this->respondWithToken(auth('api')->refresh()); 186 | } 187 | 188 | /** 189 | * Get the token array structure. 190 | * 191 | * @param string $token 192 | * 193 | * @return \CodeIgniter\Http\Response 194 | */ 195 | protected function respondWithToken($token) 196 | { 197 | return $this->response->setJson([ 198 | 'access_token' => $token, 199 | 'token_type' => 'bearer', 200 | 'expires_in' => auth('api')->factory()->getTTL() * 60, 201 | ]); 202 | } 203 | } 204 | ``` 205 | You should now be able to POST to the login endpoint (e.g. http://example.dev/jwt/login) with some valid credentials and see a response like: 206 | ```json 207 | { 208 | "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ", 209 | "token_type": "bearer", 210 | "expires_in": 3600 211 | } 212 | ``` 213 | This token can then be used to make authenticated requests to your application. 214 | 215 | ### Authenticated requests 216 | There are a number of ways to send the token via http: 217 | 218 | **Authorization header** 219 | 220 | ```Authorization: Bearer eyJhbGciOiJIUzI1NiI...``` 221 | 222 | **Query string parameter** 223 | 224 | ```http://example.dev/me?token=eyJhbGciOiJIUzI1NiI...``` 225 | 226 | **Post parameter** 227 | 228 | **Cookies** 229 | 230 | ## Methods 231 | The following methods are available on the Auth guard instance. 232 | 233 | ### Multiple Guards 234 | If the newly created 'api' guard is not set as a default guard or you have defined multiple guards to handle authentication, you should specify the guard when calling auth(). 235 | 236 | ```php 237 | $token = auth('api')->attempt($credentials); 238 | ``` 239 | 240 | ### attempt() 241 | Attempt to authenticate a user via some credentials. 242 | 243 | ```php 244 | // Generate a token for the user if the credentials are valid 245 | $token = auth('api')->attempt($credentials); 246 | ``` 247 | This will return either a jwt or boolean 248 | 249 | ### login() 250 | Log a user in and return a jwt for them. 251 | 252 | ```php 253 | // Get some user from somewhere 254 | $user = (new UserModel())->first(); 255 | 256 | // Get the token 257 | $token = auth('api')->login($user); 258 | ``` 259 | 260 | ### user() 261 | Get the currently authenticated user. 262 | 263 | ```php 264 | // Get the currently authenticated user 265 | $user = auth('api')->user(); 266 | ``` 267 | If the user is not then authenticated, then null will be returned. 268 | 269 | ### userOrFail() 270 | Get the currently authenticated user or throw an exception. 271 | 272 | ```php 273 | try { 274 | $user = auth('api')->userOrFail(); 275 | } catch (\Fluent\JWTAuth\Exceptions\UserNotDefinedException $e) { 276 | // do something 277 | } 278 | ``` 279 | If the user is not set, then a `Fluent\JWTAuth\Exceptions\UserNotDefinedException` will be thrown 280 | 281 | ### logout() 282 | Log the user out - which will invalidate the current token and unset the authenticated user. 283 | 284 | ```php 285 | auth('api')->logout(); 286 | 287 | // Pass true to force the token to be blacklisted "forever" 288 | auth('api')->logout(true); 289 | ``` 290 | 291 | ### refresh() 292 | Refresh a token, which invalidates the current one 293 | 294 | ```php 295 | $newToken = auth('api')->refresh(); 296 | 297 | // Pass true as the first param to force the token to be blacklisted "forever". 298 | // The second parameter will reset the claims for the new token 299 | $newToken = auth('api')->refresh(true, true); 300 | ``` 301 | 302 | ### invalidate() 303 | Invalidate the token (add it to the blacklist) 304 | 305 | ```php 306 | auth('api')->invalidate(); 307 | 308 | // Pass true as the first param to force the token to be blacklisted "forever". 309 | auth('api')->invalidate(true); 310 | ``` 311 | 312 | ### tokenById() 313 | Get a token based on a given user's id. 314 | 315 | ```php 316 | $token = auth('api')->tokenById(123); 317 | 318 | ``` 319 | 320 | ### payload() 321 | Get the raw JWT payload 322 | 323 | ```php 324 | $payload = auth('api')->payload(); 325 | 326 | // then you can access the claims directly e.g. 327 | $payload->get('sub'); // = 123 328 | $payload['jti']; // = 'asfe4fq434asdf' 329 | $payload('exp') // = 123456 330 | $payload->toArray(); // = ['sub' => 123, 'exp' => 123456, 'jti' => 'asfe4fq434asdf'] etc 331 | ``` 332 | 333 | ### validate() 334 | Validate a user's credentials 335 | 336 | 337 | ```php 338 | if (auth('api')->validate($credentials)) { 339 | // credentials are valid 340 | } 341 | ``` 342 | 343 | ## More advanced usage 344 | ### Adding custom claims 345 | ```php 346 | $token = auth('api')->claims(['foo' => 'bar'])->attempt($credentials); 347 | ``` 348 | 349 | ### Set the token explicitly 350 | ```php 351 | $user = auth('api')->setToken('eyJhb...')->user(); 352 | ``` 353 | 354 | ### Set the request instance explicitly 355 | ```php 356 | $user = auth('api')->setRequest($request)->user(); 357 | ``` 358 | 359 | ### Override the token ttl 360 | ```php 361 | $token = auth('api')->setTTL(7200)->attempt($credentials); 362 | ``` 363 | 364 | ## Contributing 365 | Contributions are very welcome. 366 | 367 | ## License 368 | 369 | Released under the MIT License, see [LICENSE](LICENSE.md). 370 | --------------------------------------------------------------------------------