Initial commit

This commit is contained in:
Evgeny Kuchuk 2020-05-05 23:42:18 +03:00
commit 13d763a2d4
65 changed files with 4876 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Sean Tymon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

40
README.md Normal file
View File

@ -0,0 +1,40 @@
# jwt-auth
> JSON Web Token Authentication for Laravel
[![Build Status](http://img.shields.io/travis/tymondesigns/jwt-auth/master.svg?style=flat-square)](https://travis-ci.org/tymondesigns/jwt-auth)
[![Scrutinizer Code Quality](http://img.shields.io/scrutinizer/g/tymondesigns/jwt-auth.svg?style=flat-square)](https://scrutinizer-ci.com/g/tymondesigns/jwt-auth/)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/tymondesigns/jwt-auth.svg?style=flat-square)](https://scrutinizer-ci.com/g/tymondesigns/jwt-auth/code-structure)
[![StyleCI](https://styleci.io/repos/23680678/shield?style=flat-square)](https://styleci.io/repos/23680678)
[![HHVM](https://img.shields.io/hhvm/tymon/jwt-auth.svg?style=flat-square)](http://hhvm.h4cc.de/package/tymon/jwt-auth)
[![Latest Version](http://img.shields.io/packagist/v/tymon/jwt-auth.svg?style=flat-square)](https://packagist.org/packages/tymon/jwt-auth)
[![Latest Dev Version](https://img.shields.io/packagist/vpre/tymon/jwt-auth.svg?style=flat-square)](https://packagist.org/packages/tymon/jwt-auth#dev-develop)
[![Monthly Downloads](https://img.shields.io/packagist/dm/tymon/jwt-auth.svg?style=flat-square)](https://packagist.org/packages/tymon/jwt-auth)
See the [WIKI](https://github.com/tymondesigns/jwt-auth/wiki) for documentation
## License
The MIT License (MIT)
Copyright (c) 2014 Sean Tymon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[![Gratipay](https://img.shields.io/gratipay/tymondesigns.svg?style=flat-square)](https://gratipay.com/~tymondesigns)

51
composer.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "tymon/jwt-auth",
"description": "JSON Web Token Authentication for Laravel 4 and 5",
"keywords": [
"jwt",
"auth",
"authentication",
"tymon",
"laravel",
"json web token"
],
"homepage": "https://github.com/tymondesigns/jwt-auth",
"license": "MIT",
"authors": [
{
"name": "Sean Tymon",
"email": "tymon148@gmail.com",
"homepage": "http://tymondesigns.com",
"role": "Developer"
}
],
"require": {
"php": ">=5.4.0",
"illuminate/support": "~5.0",
"illuminate/http": "~5.0",
"namshi/jose": "^5.0 || ^7.0",
"nesbot/carbon": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "4.*",
"mockery/mockery": "0.9.*",
"illuminate/auth": "~5.0",
"illuminate/database": "~5.0",
"illuminate/console" : "~5.0"
},
"autoload": {
"psr-4": {
"Tymon\\JWTAuth\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Tymon\\JWTAuth\\Test\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-develop": "0.5-dev"
}
}
}

34
phpunit.xml.dist Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="League Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
<exclude>
<file>src/Providers/JWTAuthServiceProvider.php</file>
<directory suffix=".php">src/config/</directory>
<directory suffix=".php">src/Facades/</directory>
</exclude>
</whitelist>
</filter>
<logging>
<log type="tap" target="build/report.tap"/>
<log type="junit" target="build/report.junit.xml"/>
<log type="coverage-html" target="build/coverage" charset="UTF-8" yui="true" highlight="true"/>
<log type="coverage-text" target="build/coverage.txt"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>

113
src/Blacklist.php Normal file
View File

@ -0,0 +1,113 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth;
use Tymon\JWTAuth\Providers\Storage\StorageInterface;
class Blacklist
{
/**
* @var \Tymon\JWTAuth\Providers\Storage\StorageInterface
*/
protected $storage;
/**
* Number of minutes from issue date in which a JWT can be refreshed.
*
* @var int
*/
protected $refreshTTL = 20160;
/**
* @param \Tymon\JWTAuth\Providers\Storage\StorageInterface $storage
*/
public function __construct(StorageInterface $storage)
{
$this->storage = $storage;
}
/**
* Add the token (jti claim) to the blacklist.
*
* @param \Tymon\JWTAuth\Payload $payload
* @return bool
*/
public function add(Payload $payload)
{
$exp = Utils::timestamp($payload['exp']);
$refreshExp = Utils::timestamp($payload['iat'])->addMinutes($this->refreshTTL);
// there is no need to add the token to the blacklist
// if the token has already expired AND the refresh_ttl
// has gone by
if ($exp->isPast() && $refreshExp->isPast()) {
return false;
}
// Set the cache entry's lifetime to be equal to the amount
// of refreshable time it has remaining (which is the larger
// of `exp` and `iat+refresh_ttl`), rounded up a minute
$cacheLifetime = $exp->max($refreshExp)->addMinute()->diffInMinutes();
$this->storage->add($payload['jti'], [], $cacheLifetime);
return true;
}
/**
* Determine whether the token has been blacklisted.
*
* @param \Tymon\JWTAuth\Payload $payload
* @return bool
*/
public function has(Payload $payload)
{
return $this->storage->has($payload['jti']);
}
/**
* Remove the token (jti claim) from the blacklist.
*
* @param \Tymon\JWTAuth\Payload $payload
* @return bool
*/
public function remove(Payload $payload)
{
return $this->storage->destroy($payload['jti']);
}
/**
* Remove all tokens from the blacklist.
*
* @return bool
*/
public function clear()
{
$this->storage->flush();
return true;
}
/**
* Set the refresh time limit.
*
* @param int
*
* @return $this
*/
public function setRefreshTTL($ttl)
{
$this->refreshTTL = (int) $ttl;
return $this;
}
}

22
src/Claims/Audience.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class Audience extends Claim
{
/**
* The claim name.
*
* @var string
*/
protected $name = 'aud';
}

121
src/Claims/Claim.php Normal file
View File

@ -0,0 +1,121 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
use Tymon\JWTAuth\Exceptions\InvalidClaimException;
abstract class Claim implements ClaimInterface
{
/**
* The claim name.
*
* @var string
*/
protected $name;
/**
* The claim value.
*
* @var mixed
*/
private $value;
/**
* @param mixed $value
*/
public function __construct($value)
{
$this->setValue($value);
}
/**
* Set the claim value, and call a validate method if available.
*
* @param $value
* @throws \Tymon\JWTAuth\Exceptions\InvalidClaimException
* @return $this
*/
public function setValue($value)
{
if (! $this->validate($value)) {
throw new InvalidClaimException('Invalid value provided for claim "'.$this->getName().'": '.$value);
}
$this->value = $value;
return $this;
}
/**
* Get the claim value.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Set the claim name.
*
* @param string $name
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get the claim name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Validate the Claim value.
*
* @param $value
* @return bool
*/
protected function validate($value)
{
return true;
}
/**
* Build a key value array comprising of the claim name and value.
*
* @return array
*/
public function toArray()
{
return [$this->getName() => $this->getValue()];
}
/**
* Get the claim as a string.
*
* @return string
*/
public function __toString()
{
return json_encode($this->toArray(), JSON_UNESCAPED_SLASHES);
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
interface ClaimInterface
{
/**
* Set the claim value, and call a validate method if available.
*
* @param mixed
* @return Claim
*/
public function setValue($value);
/**
* Get the claim value.
*
* @return mixed
*/
public function getValue();
/**
* Set the claim name.
*
* @param string $name
* @return Claim
*/
public function setName($name);
/**
* Get the claim name.
*
* @return string
*/
public function getName();
}

25
src/Claims/Custom.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class Custom extends Claim
{
/**
* @param string $name
* @param mixed $value
*/
public function __construct($name, $value)
{
parent::__construct($value);
$this->setName($name);
}
}

33
src/Claims/Expiration.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class Expiration extends Claim
{
/**
* The claim name.
*
* @var string
*/
protected $name = 'exp';
/**
* Validate the expiry claim.
*
* @param mixed $value
* @return bool
*/
protected function validate($value)
{
return is_numeric($value);
}
}

55
src/Claims/Factory.php Normal file
View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class Factory
{
/**
* @var array
*/
private static $classMap = [
'aud' => 'Tymon\JWTAuth\Claims\Audience',
'exp' => 'Tymon\JWTAuth\Claims\Expiration',
'iat' => 'Tymon\JWTAuth\Claims\IssuedAt',
'iss' => 'Tymon\JWTAuth\Claims\Issuer',
'jti' => 'Tymon\JWTAuth\Claims\JwtId',
'nbf' => 'Tymon\JWTAuth\Claims\NotBefore',
'sub' => 'Tymon\JWTAuth\Claims\Subject',
];
/**
* Get the instance of the claim when passing the name and value.
*
* @param string $name
* @param mixed $value
* @return \Tymon\JWTAuth\Claims\Claim
*/
public function get($name, $value)
{
if ($this->has($name)) {
return new self::$classMap[$name]($value);
}
return new Custom($name, $value);
}
/**
* Check whether the claim exists.
*
* @param string $name
* @return bool
*/
public function has($name)
{
return array_key_exists($name, self::$classMap);
}
}

33
src/Claims/IssuedAt.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class IssuedAt extends Claim
{
/**
* The claim name.
*
* @var string
*/
protected $name = 'iat';
/**
* Validate the issued at claim.
*
* @param mixed $value
* @return bool
*/
protected function validate($value)
{
return is_numeric($value);
}
}

22
src/Claims/Issuer.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class Issuer extends Claim
{
/**
* The claim name.
*
* @var string
*/
protected $name = 'iss';
}

22
src/Claims/JwtId.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class JwtId extends Claim
{
/**
* The claim name.
*
* @var string
*/
protected $name = 'jti';
}

33
src/Claims/NotBefore.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class NotBefore extends Claim
{
/**
* The claim name.
*
* @var string
*/
protected $name = 'nbf';
/**
* Validate the not before claim.
*
* @param mixed $value
* @return bool
*/
protected function validate($value)
{
return is_numeric($value);
}
}

22
src/Claims/Subject.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Claims;
class Subject extends Claim
{
/**
* The claim name.
*
* @var string
*/
protected $name = 'sub';
}

View File

@ -0,0 +1,81 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Commands;
use Illuminate\Support\Str;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
class JWTGenerateCommand extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'jwt:generate';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Set the JWTAuth secret key used to sign the tokens';
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$key = $this->getRandomKey();
if ($this->option('show')) {
return $this->line('<comment>'.$key.'</comment>');
}
$path = config_path('jwt.php');
if (file_exists($path)) {
file_put_contents($path, str_replace(
$this->laravel['config']['jwt.secret'], $key, file_get_contents($path)
));
}
$this->laravel['config']['jwt.secret'] = $key;
$this->info("jwt-auth secret [$key] set successfully.");
}
/**
* Generate a random key for the JWT Auth secret.
*
* @return string
*/
protected function getRandomKey()
{
return Str::random(32);
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['show', null, InputOption::VALUE_NONE, 'Simply display the key instead of modifying files.'],
];
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Exceptions;
class InvalidClaimException extends JWTException
{
/**
* @var int
*/
protected $statusCode = 400;
}

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Exceptions;
class JWTException extends \Exception
{
/**
* @var int
*/
protected $statusCode = 500;
/**
* @param string $message
* @param int $statusCode
*/
public function __construct($message = 'An error occurred', $statusCode = null)
{
parent::__construct($message);
if (! is_null($statusCode)) {
$this->setStatusCode($statusCode);
}
}
/**
* @param int $statusCode
*/
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
}
/**
* @return int the status code
*/
public function getStatusCode()
{
return $this->statusCode;
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Exceptions;
class PayloadException extends JWTException
{
/**
* @var int
*/
protected $statusCode = 500;
}

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Exceptions;
class TokenBlacklistedException extends TokenInvalidException
{
/**
* @var int
*/
protected $statusCode = 401;
}

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Exceptions;
class TokenExpiredException extends JWTException
{
/**
* @var int
*/
protected $statusCode = 401;
}

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Exceptions;
class TokenInvalidException extends JWTException
{
/**
* @var int
*/
protected $statusCode = 400;
}

27
src/Facades/JWTAuth.php Normal file
View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Facades;
use Illuminate\Support\Facades\Facade;
class JWTAuth extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'tymon.jwt.auth';
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Facades;
use Illuminate\Support\Facades\Facade;
class JWTFactory extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'tymon.jwt.payload.factory';
}
}

343
src/JWTAuth.php Normal file
View File

@ -0,0 +1,343 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Providers\Auth\AuthInterface;
use Tymon\JWTAuth\Providers\User\UserInterface;
class JWTAuth
{
/**
* @var \Tymon\JWTAuth\JWTManager
*/
protected $manager;
/**
* @var \Tymon\JWTAuth\Providers\User\UserInterface
*/
protected $user;
/**
* @var \Tymon\JWTAuth\Providers\Auth\AuthInterface
*/
protected $auth;
/**
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* @var string
*/
protected $identifier = 'id';
/**
* @var \Tymon\JWTAuth\Token
*/
protected $token;
/**
* @param \Tymon\JWTAuth\JWTManager $manager
* @param \Tymon\JWTAuth\Providers\User\UserInterface $user
* @param \Tymon\JWTAuth\Providers\Auth\AuthInterface $auth
* @param \Illuminate\Http\Request $request
*/
public function __construct(JWTManager $manager, UserInterface $user, AuthInterface $auth, Request $request)
{
$this->manager = $manager;
$this->user = $user;
$this->auth = $auth;
$this->request = $request;
}
/**
* Find a user using the user identifier in the subject claim.
*
* @param bool|string $token
*
* @return mixed
*/
public function toUser($token = false)
{
$payload = $this->getPayload($token);
if (! $user = $this->user->getBy($this->identifier, $payload['sub'])) {
return false;
}
return $user;
}
/**
* Generate a token using the user identifier as the subject claim.
*
* @param mixed $user
* @param array $customClaims
*
* @return string
*/
public function fromUser($user, array $customClaims = [])
{
$payload = $this->makePayload($user->{$this->identifier}, $customClaims);
return $this->manager->encode($payload)->get();
}
/**
* Attempt to authenticate the user and return the token.
*
* @param array $credentials
* @param array $customClaims
*
* @return false|string
*/
public function attempt(array $credentials = [], array $customClaims = [])
{
if (! $this->auth->byCredentials($credentials)) {
return false;
}
return $this->fromUser($this->auth->user(), $customClaims);
}
/**
* Authenticate a user via a token.
*
* @param mixed $token
*
* @return mixed
*/
public function authenticate($token = false)
{
$id = $this->getPayload($token)->get('sub');
if (! $this->auth->byId($id)) {
return false;
}
return $this->auth->user();
}
/**
* Refresh an expired token.
*
* @param mixed $token
*
* @return string
*/
public function refresh($token = false)
{
$this->requireToken($token);
return $this->manager->refresh($this->token)->get();
}
/**
* Invalidate a token (add it to the blacklist).
*
* @param mixed $token
*
* @return bool
*/
public function invalidate($token = false)
{
$this->requireToken($token);
return $this->manager->invalidate($this->token);
}
/**
* Get the token.
*
* @return bool|string
*/
public function getToken()
{
if (! $this->token) {
try {
$this->parseToken();
} catch (JWTException $e) {
return false;
}
}
return $this->token;
}
/**
* Get the raw Payload instance.
*
* @param mixed $token
*
* @return \Tymon\JWTAuth\Payload
*/
public function getPayload($token = false)
{
$this->requireToken($token);
return $this->manager->decode($this->token);
}
/**
* Parse the token from the request.
*
* @param string $query
*
* @return JWTAuth
*/
public function parseToken($method = 'bearer', $header = 'authorization', $query = 'token')
{
if (! $token = $this->parseAuthHeader($header, $method)) {
if (! $token = $this->request->query($query, false)) {
throw new JWTException('The token could not be parsed from the request', 400);
}
}
return $this->setToken($token);
}
/**
* Parse token from the authorization header.
*
* @param string $header
* @param string $method
*
* @return false|string
*/
protected function parseAuthHeader($header = 'authorization', $method = 'bearer')
{
$header = $this->request->headers->get($header);
if (! starts_with(strtolower($header), $method)) {
return false;
}
return trim(str_ireplace($method, '', $header));
}
/**
* Create a Payload instance.
*
* @param mixed $subject
* @param array $customClaims
*
* @return \Tymon\JWTAuth\Payload
*/
protected function makePayload($subject, array $customClaims = [])
{
return $this->manager->getPayloadFactory()->make(
array_merge($customClaims, ['sub' => $subject])
);
}
/**
* Set the identifier.
*
* @param string $identifier
*
* @return $this
*/
public function setIdentifier($identifier)
{
$this->identifier = $identifier;
return $this;
}
/**
* Get the identifier.
*
* @return string
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* Set the token.
*
* @param string $token
*
* @return $this
*/
public function setToken($token)
{
$this->token = new Token($token);
return $this;
}
/**
* Ensure that a token is available.
*
* @param mixed $token
*
* @return JWTAuth
*
* @throws \Tymon\JWTAuth\Exceptions\JWTException
*/
protected function requireToken($token)
{
if ($token) {
return $this->setToken($token);
} elseif ($this->token) {
return $this;
} else {
throw new JWTException('A token is required', 400);
}
}
/**
* Set the request instance.
*
* @param Request $request
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* Get the JWTManager instance.
*
* @return \Tymon\JWTAuth\JWTManager
*/
public function manager()
{
return $this->manager;
}
/**
* Magically call the JWT Manager.
*
* @param string $method
* @param array $parameters
*
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (method_exists($this->manager, $method)) {
return call_user_func_array([$this->manager, $method], $parameters);
}
throw new \BadMethodCallException("Method [$method] does not exist.");
}
}

183
src/JWTManager.php Normal file
View File

@ -0,0 +1,183 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Providers\JWT\JWTInterface;
use Tymon\JWTAuth\Exceptions\TokenBlacklistedException;
class JWTManager
{
/**
* @var \Tymon\JWTAuth\Providers\JWT\JWTInterface
*/
protected $jwt;
/**
* @var \Tymon\JWTAuth\Blacklist
*/
protected $blacklist;
/**
* @var \Tymon\JWTAuth\PayloadFactory
*/
protected $payloadFactory;
/**
* @var bool
*/
protected $blacklistEnabled = true;
/**
* @var bool
*/
protected $refreshFlow = false;
/**
* @param \Tymon\JWTAuth\Providers\JWT\JWTInterface $jwt
* @param \Tymon\JWTAuth\Blacklist $blacklist
* @param \Tymon\JWTAuth\PayloadFactory $payloadFactory
*/
public function __construct(JWTInterface $jwt, Blacklist $blacklist, PayloadFactory $payloadFactory)
{
$this->jwt = $jwt;
$this->blacklist = $blacklist;
$this->payloadFactory = $payloadFactory;
}
/**
* Encode a Payload and return the Token.
*
* @param \Tymon\JWTAuth\Payload $payload
* @return \Tymon\JWTAuth\Token
*/
public function encode(Payload $payload)
{
$token = $this->jwt->encode($payload->get());
return new Token($token);
}
/**
* Decode a Token and return the Payload.
*
* @param \Tymon\JWTAuth\Token $token
* @return Payload
* @throws TokenBlacklistedException
*/
public function decode(Token $token)
{
$payloadArray = $this->jwt->decode($token->get());
$payload = $this->payloadFactory->setRefreshFlow($this->refreshFlow)->make($payloadArray);
if ($this->blacklistEnabled && $this->blacklist->has($payload)) {
throw new TokenBlacklistedException('The token has been blacklisted');
}
return $payload;
}
/**
* Refresh a Token and return a new Token.
*
* @param \Tymon\JWTAuth\Token $token
* @return \Tymon\JWTAuth\Token
*/
public function refresh(Token $token)
{
$payload = $this->setRefreshFlow()->decode($token);
if ($this->blacklistEnabled) {
// invalidate old token
$this->blacklist->add($payload);
}
// return the new token
return $this->encode(
$this->payloadFactory->make([
'sub' => $payload['sub'],
'iat' => $payload['iat'],
])
);
}
/**
* Invalidate a Token by adding it to the blacklist.
*
* @param Token $token
* @return bool
*/
public function invalidate(Token $token)
{
if (! $this->blacklistEnabled) {
throw new JWTException('You must have the blacklist enabled to invalidate a token.');
}
return $this->blacklist->add($this->decode($token));
}
/**
* Get the PayloadFactory instance.
*
* @return \Tymon\JWTAuth\PayloadFactory
*/
public function getPayloadFactory()
{
return $this->payloadFactory;
}
/**
* Get the JWTProvider instance.
*
* @return \Tymon\JWTAuth\Providers\JWT\JWTInterface
*/
public function getJWTProvider()
{
return $this->jwt;
}
/**
* Get the Blacklist instance.
*
* @return \Tymon\JWTAuth\Blacklist
*/
public function getBlacklist()
{
return $this->blacklist;
}
/**
* Set whether the blacklist is enabled.
*
* @param bool $enabled
*/
public function setBlacklistEnabled($enabled)
{
$this->blacklistEnabled = $enabled;
return $this;
}
/**
* Set the refresh flow.
*
* @param bool $refreshFlow
* @return $this
*/
public function setRefreshFlow($refreshFlow = true)
{
$this->refreshFlow = $refreshFlow;
return $this;
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Middleware;
use Tymon\JWTAuth\JWTAuth;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Routing\ResponseFactory;
abstract class BaseMiddleware
{
/**
* @var \Illuminate\Contracts\Routing\ResponseFactory
*/
protected $response;
/**
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $events;
/**
* @var \Tymon\JWTAuth\JWTAuth
*/
protected $auth;
/**
* Create a new BaseMiddleware instance.
*
* @param \Illuminate\Contracts\Routing\ResponseFactory $response
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @param \Tymon\JWTAuth\JWTAuth $auth
*/
public function __construct(ResponseFactory $response, Dispatcher $events, JWTAuth $auth)
{
$this->response = $response;
$this->events = $events;
$this->auth = $auth;
}
/**
* Fire event and return the response.
*
* @param string $event
* @param string $error
* @param int $status
* @param array $payload
* @return mixed
*/
protected function respond($event, $error, $status, $payload = [])
{
$response = $this->events->fire($event, $payload, true);
return $response ?: $this->response->json(['error' => $error], $status);
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Middleware;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
class GetUserFromToken extends BaseMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
{
if (! $token = $this->auth->setRequest($request)->getToken()) {
return $this->respond('tymon.jwt.absent', 'token_not_provided', 400);
}
try {
$user = $this->auth->authenticate($token);
} catch (TokenExpiredException $e) {
return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
} catch (JWTException $e) {
return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
}
if (! $user) {
return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404);
}
$this->events->fire('tymon.jwt.valid', $user);
return $next($request);
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Middleware;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
class RefreshToken extends BaseMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
{
$response = $next($request);
try {
$newToken = $this->auth->setRequest($request)->parseToken()->refresh();
} catch (TokenExpiredException $e) {
return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
} catch (JWTException $e) {
return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
}
// send the refreshed token back to the client
$response->headers->set('Authorization', 'Bearer '.$newToken);
return $response;
}
}

175
src/Payload.php Normal file
View File

@ -0,0 +1,175 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth;
use Tymon\JWTAuth\Claims\Claim;
use Tymon\JWTAuth\Exceptions\PayloadException;
use Tymon\JWTAuth\Validators\PayloadValidator;
class Payload implements \ArrayAccess
{
/**
* The array of claims.
*
* @var \Tymon\JWTAuth\Claims\Claim[]
*/
private $claims = [];
/**
* Build the Payload.
*
* @param array $claims
* @param \Tymon\JWTAuth\Validators\PayloadValidator $validator
* @param bool $refreshFlow
*/
public function __construct(array $claims, PayloadValidator $validator, $refreshFlow = false)
{
$this->claims = $claims;
$validator->setRefreshFlow($refreshFlow)->check($this->toArray());
}
/**
* Get the array of claim instances.
*
* @return \Tymon\JWTAuth\Claims\Claim[]
*/
public function getClaims()
{
return $this->claims;
}
/**
* Get the array of claims.
*
* @return array
*/
public function toArray()
{
$results = [];
foreach ($this->claims as $claim) {
$results[$claim->getName()] = $claim->getValue();
}
return $results;
}
/**
* Get the payload.
*
* @param string $claim
* @return mixed
*/
public function get($claim = null)
{
if (! is_null($claim)) {
if (is_array($claim)) {
return array_map([$this, 'get'], $claim);
}
return array_get($this->toArray(), $claim, false);
}
return $this->toArray();
}
/**
* Determine whether the payload has the claim.
*
* @param \Tymon\JWTAuth\Claims\Claim $claim
* @return bool
*/
public function has(Claim $claim)
{
return in_array($claim, $this->claims);
}
/**
* Get the payload as a string.
*
* @return string
*/
public function __toString()
{
return json_encode($this->toArray());
}
/**
* Determine if an item exists at an offset.
*
* @param mixed $key
* @return bool
*/
public function offsetExists($key)
{
return array_key_exists($key, $this->toArray());
}
/**
* Get an item at a given offset.
*
* @param mixed $key
* @return mixed
*/
public function offsetGet($key)
{
return array_get($this->toArray(), $key, []);
}
/**
* Don't allow changing the payload as it should be immutable.
*
* @param mixed $key
* @param mixed $value
* @throws Exceptions\PayloadException
* @return void
*/
public function offsetSet($key, $value)
{
throw new PayloadException('The payload is immutable');
}
/**
* Don't allow changing the payload as it should be immutable.
*
* @param string $key
* @throws Exceptions\PayloadException
* @return void
*/
public function offsetUnset($key)
{
throw new PayloadException('The payload is immutable');
}
/**
* Magically get a claim value.
*
* @param string $method
* @param array $parameters
* @return mixed
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (! method_exists($this, $method) && starts_with($method, 'get')) {
$class = sprintf('Tymon\\JWTAuth\\Claims\\%s', substr($method, 3));
foreach ($this->claims as $claim) {
if (get_class($claim) === $class) {
return $claim->getValue();
}
}
}
throw new \BadMethodCallException(sprintf('The claim [%s] does not exist on the payload.', $method));
}
}

245
src/PayloadFactory.php Normal file
View File

@ -0,0 +1,245 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Claims\Factory;
use Tymon\JWTAuth\Validators\PayloadValidator;
class PayloadFactory
{
/**
* @var \Tymon\JWTAuth\Claims\Factory
*/
protected $claimFactory;
/**
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* @var \Tymon\JWTAuth\Validators\PayloadValidator
*/
protected $validator;
/**
* @var int
*/
protected $ttl = 60;
/**
* @var bool
*/
protected $refreshFlow = false;
/**
* @var array
*/
protected $defaultClaims = ['iss', 'iat', 'exp', 'nbf', 'jti'];
/**
* @var array
*/
protected $claims = [];
/**
* @param \Tymon\JWTAuth\Claims\Factory $claimFactory
* @param \Illuminate\Http\Request $request
* @param \Tymon\JWTAuth\Validators\PayloadValidator $validator
*/
public function __construct(Factory $claimFactory, Request $request, PayloadValidator $validator)
{
$this->claimFactory = $claimFactory;
$this->request = $request;
$this->validator = $validator;
}
/**
* Create the Payload instance.
*
* @param array $customClaims
* @return \Tymon\JWTAuth\Payload
*/
public function make(array $customClaims = [])
{
$claims = $this->buildClaims($customClaims)->resolveClaims();
return new Payload($claims, $this->validator, $this->refreshFlow);
}
/**
* Add an array of claims to the Payload.
*
* @param array $claims
* @return $this
*/
public function addClaims(array $claims)
{
foreach ($claims as $name => $value) {
$this->addClaim($name, $value);
}
return $this;
}
/**
* Add a claim to the Payload.
*
* @param string $name
* @param mixed $value
* @return $this
*/
public function addClaim($name, $value)
{
$this->claims[$name] = $value;
return $this;
}
/**
* Build the default claims.
*
* @param array $customClaims
* @return $this
*/
protected function buildClaims(array $customClaims)
{
// add any custom claims first
$this->addClaims($customClaims);
foreach ($this->defaultClaims as $claim) {
if (! array_key_exists($claim, $customClaims)) {
$this->addClaim($claim, $this->$claim());
}
}
return $this;
}
/**
* Build out the Claim DTO's.
*
* @return array
*/
public function resolveClaims()
{
$resolved = [];
foreach ($this->claims as $name => $value) {
$resolved[] = $this->claimFactory->get($name, $value);
}
return $resolved;
}
/**
* Set the Issuer (iss) claim.
*
* @return string
*/
public function iss()
{
return $this->request->url();
}
/**
* Set the Issued At (iat) claim.
*
* @return int
*/
public function iat()
{
return Utils::now()->timestamp;
}
/**
* Set the Expiration (exp) claim.
*
* @return int
*/
public function exp()
{
return Utils::now()->addMinutes($this->ttl)->timestamp;
}
/**
* Set the Not Before (nbf) claim.
*
* @return int
*/
public function nbf()
{
return Utils::now()->timestamp;
}
/**
* Set a unique id (jti) for the token.
*
* @return string
*/
protected function jti()
{
return Str::random();
}
/**
* Set the token ttl (in minutes).
*
* @param int $ttl
* @return $this
*/
public function setTTL($ttl)
{
$this->ttl = $ttl;
return $this;
}
/**
* Get the token ttl.
*
* @return int
*/
public function getTTL()
{
return $this->ttl;
}
/**
* Set the refresh flow.
*
* @param bool $refreshFlow
* @return $this
*/
public function setRefreshFlow($refreshFlow = true)
{
$this->refreshFlow = $refreshFlow;
return $this;
}
/**
* Magically add a claim.
*
* @param string $method
* @param array $parameters
* @return PayloadFactory
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
$this->addClaim($method, $parameters[0]);
return $this;
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\Auth;
interface AuthInterface
{
/**
* Check a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function byCredentials(array $credentials = []);
/**
* Authenticate a user via the id.
*
* @param mixed $id
* @return bool
*/
public function byId($id);
/**
* Get the currently authenticated user.
*
* @return mixed
*/
public function user();
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\Auth;
use Illuminate\Auth\AuthManager;
class IlluminateAuthAdapter implements AuthInterface
{
/**
* @var \Illuminate\Auth\AuthManager
*/
protected $auth;
/**
* @param \Illuminate\Auth\AuthManager $auth
*/
public function __construct(AuthManager $auth)
{
$this->auth = $auth;
}
/**
* Check a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function byCredentials(array $credentials = [])
{
return $this->auth->once($credentials);
}
/**
* Authenticate a user via the id.
*
* @param mixed $id
* @return bool
*/
public function byId($id)
{
return $this->auth->onceUsingId($id);
}
/**
* Get the currently authenticated user.
*
* @return mixed
*/
public function user()
{
return $this->auth->user();
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\JWT;
interface JWTInterface
{
/**
* @param array $payload
* @return string
*/
public function encode(array $payload);
/**
* @param string $token
* @return array
*/
public function decode($token);
}

View File

@ -0,0 +1,82 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\JWT;
abstract class JWTProvider
{
/**
* @var string
*/
protected $secret;
/**
* @var string
*/
protected $algo;
/**
* @param string $secret
* @param string $algo
*/
public function __construct($secret, $algo = 'HS256')
{
$this->secret = $secret;
$this->algo = $algo;
}
/**
* Set the algorithm used to sign the token.
*
* @param string $algo
* @return self
*/
public function setAlgo($algo)
{
$this->algo = $algo;
return $this;
}
/**
* Get the algorithm used to sign the token.
*
* @return string
*/
public function getAlgo()
{
return $this->algo;
}
/**
* Set the secret used to sign the token.
*
* @param string $secret
*
* @return $this
*/
public function setSecret($secret)
{
$this->secret = $secret;
return $this;
}
/**
* Get the secret used to sign the token.
*
* @return string
*/
public function getSecret()
{
return $this->secret;
}
}

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\JWT;
use Exception;
use Namshi\JOSE\JWS;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class NamshiAdapter extends JWTProvider implements JWTInterface
{
/**
* @var \Namshi\JOSE\JWS
*/
protected $jws;
/**
* @param string $secret
* @param string $algo
* @param null $driver
*/
public function __construct($secret, $algo, $driver = null)
{
parent::__construct($secret, $algo);
$this->jws = $driver ?: new JWS(['typ' => 'JWT', 'alg' => $algo]);
}
/**
* Create a JSON Web Token.
*
* @return string
* @throws \Tymon\JWTAuth\Exceptions\JWTException
*/
public function encode(array $payload)
{
try {
$this->jws->setPayload($payload)->sign($this->secret);
return $this->jws->getTokenString();
} catch (Exception $e) {
throw new JWTException('Could not create token: '.$e->getMessage());
}
}
/**
* Decode a JSON Web Token.
*
* @param string $token
* @return array
* @throws \Tymon\JWTAuth\Exceptions\JWTException
*/
public function decode($token)
{
try {
$jws = JWS::load($token);
} catch (Exception $e) {
throw new TokenInvalidException('Could not decode token: '.$e->getMessage());
}
if (! $jws->verify($this->secret, $this->algo)) {
throw new TokenInvalidException('Token Signature could not be verified.');
}
return $jws->getPayload();
}
}

View File

@ -0,0 +1,278 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers;
use Tymon\JWTAuth\JWTAuth;
use Tymon\JWTAuth\Blacklist;
use Tymon\JWTAuth\JWTManager;
use Tymon\JWTAuth\Claims\Factory;
use Tymon\JWTAuth\PayloadFactory;
use Illuminate\Support\ServiceProvider;
use Tymon\JWTAuth\Commands\JWTGenerateCommand;
use Tymon\JWTAuth\Validators\PayloadValidator;
class JWTAuthServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Boot the service provider.
*/
public function boot()
{
$this->publishes([
__DIR__.'/../config/config.php' => config_path('jwt.php'),
], 'config');
$this->bootBindings();
$this->commands('tymon.jwt.generate');
}
/**
* Bind some Interfaces and implementations.
*/
protected function bootBindings()
{
$this->app->singleton('Tymon\JWTAuth\JWTAuth', function ($app) {
return $app['tymon.jwt.auth'];
});
$this->app->singleton('Tymon\JWTAuth\Providers\User\UserInterface', function ($app) {
return $app['tymon.jwt.provider.user'];
});
$this->app->singleton('Tymon\JWTAuth\Providers\JWT\JWTInterface', function ($app) {
return $app['tymon.jwt.provider.jwt'];
});
$this->app->singleton('Tymon\JWTAuth\Providers\Auth\AuthInterface', function ($app) {
return $app['tymon.jwt.provider.auth'];
});
$this->app->singleton('Tymon\JWTAuth\Providers\Storage\StorageInterface', function ($app) {
return $app['tymon.jwt.provider.storage'];
});
$this->app->singleton('Tymon\JWTAuth\JWTManager', function ($app) {
return $app['tymon.jwt.manager'];
});
$this->app->singleton('Tymon\JWTAuth\Blacklist', function ($app) {
return $app['tymon.jwt.blacklist'];
});
$this->app->singleton('Tymon\JWTAuth\PayloadFactory', function ($app) {
return $app['tymon.jwt.payload.factory'];
});
$this->app->singleton('Tymon\JWTAuth\Claims\Factory', function ($app) {
return $app['tymon.jwt.claim.factory'];
});
$this->app->singleton('Tymon\JWTAuth\Validators\PayloadValidator', function ($app) {
return $app['tymon.jwt.validators.payload'];
});
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
// register providers
$this->registerUserProvider();
$this->registerJWTProvider();
$this->registerAuthProvider();
$this->registerStorageProvider();
$this->registerJWTBlacklist();
$this->registerClaimFactory();
$this->registerJWTManager();
$this->registerJWTAuth();
$this->registerPayloadValidator();
$this->registerPayloadFactory();
$this->registerJWTCommand();
$this->mergeConfigFrom(__DIR__.'/../config/config.php', 'jwt');
}
/**
* Register the bindings for the User provider.
*/
protected function registerUserProvider()
{
$this->app->singleton('tymon.jwt.provider.user', function ($app) {
$provider = $this->config('providers.user');
$model = $app->make($this->config('user'));
return new $provider($model);
});
}
/**
* Register the bindings for the JSON Web Token provider.
*/
protected function registerJWTProvider()
{
$this->app->singleton('tymon.jwt.provider.jwt', function ($app) {
$secret = $this->config('secret');
$algo = $this->config('algo');
$provider = $this->config('providers.jwt');
return new $provider($secret, $algo);
});
}
/**
* Register the bindings for the Auth provider.
*/
protected function registerAuthProvider()
{
$this->app->singleton('tymon.jwt.provider.auth', function ($app) {
return $this->getConfigInstance($this->config('providers.auth'));
});
}
/**
* Register the bindings for the Storage provider.
*/
protected function registerStorageProvider()
{
$this->app->singleton('tymon.jwt.provider.storage', function ($app) {
return $this->getConfigInstance($this->config('providers.storage'));
});
}
/**
* Register the bindings for the Payload Factory.
*/
protected function registerClaimFactory()
{
$this->app->singleton('tymon.jwt.claim.factory', function () {
return new Factory();
});
}
/**
* Register the bindings for the JWT Manager.
*/
protected function registerJWTManager()
{
$this->app->singleton('tymon.jwt.manager', function ($app) {
$instance = new JWTManager(
$app['tymon.jwt.provider.jwt'],
$app['tymon.jwt.blacklist'],
$app['tymon.jwt.payload.factory']
);
return $instance->setBlacklistEnabled((bool) $this->config('blacklist_enabled'));
});
}
/**
* Register the bindings for the main JWTAuth class.
*/
protected function registerJWTAuth()
{
$this->app->singleton('tymon.jwt.auth', function ($app) {
$auth = new JWTAuth(
$app['tymon.jwt.manager'],
$app['tymon.jwt.provider.user'],
$app['tymon.jwt.provider.auth'],
$app['request']
);
return $auth->setIdentifier($this->config('identifier'));
});
}
/**
* Register the bindings for the main JWTAuth class.
*/
protected function registerJWTBlacklist()
{
$this->app->singleton('tymon.jwt.blacklist', function ($app) {
$instance = new Blacklist($app['tymon.jwt.provider.storage']);
return $instance->setRefreshTTL($this->config('refresh_ttl'));
});
}
/**
* Register the bindings for the payload validator.
*/
protected function registerPayloadValidator()
{
$this->app->singleton('tymon.jwt.validators.payload', function () {
return with(new PayloadValidator())->setRefreshTTL($this->config('refresh_ttl'))->setRequiredClaims($this->config('required_claims'));
});
}
/**
* Register the bindings for the Payload Factory.
*/
protected function registerPayloadFactory()
{
$this->app->singleton('tymon.jwt.payload.factory', function ($app) {
$factory = new PayloadFactory($app['tymon.jwt.claim.factory'], $app['request'], $app['tymon.jwt.validators.payload']);
return $factory->setTTL($this->config('ttl'));
});
}
/**
* Register the Artisan command.
*/
protected function registerJWTCommand()
{
$this->app->singleton('tymon.jwt.generate', function () {
return new JWTGenerateCommand();
});
}
/**
* Helper to get the config values.
*
* @param string $key
* @return string
*/
protected function config($key, $default = null)
{
return config("jwt.$key", $default);
}
/**
* Get an instantiable configuration instance. Pinched from dingo/api :).
*
* @param mixed $instance
* @return object
*/
protected function getConfigInstance($instance)
{
if (is_callable($instance)) {
return call_user_func($instance, $this->app);
} elseif (is_string($instance)) {
return $this->app->make($instance);
}
return $instance;
}
}

View File

@ -0,0 +1,94 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\Storage;
use Illuminate\Cache\CacheManager;
class IlluminateCacheAdapter implements StorageInterface
{
/**
* @var \Illuminate\Cache\CacheManager
*/
protected $cache;
/**
* @var string
*/
protected $tag = 'tymon.jwt';
/**
* @param \Illuminate\Cache\CacheManager $cache
*/
public function __construct(CacheManager $cache)
{
$this->cache = $cache;
}
/**
* Add a new item into storage.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function add($key, $value, $minutes)
{
$this->cache()->put($key, $value, $minutes);
}
/**
* Check whether a key exists in storage.
*
* @param string $key
* @return bool
*/
public function has($key)
{
return $this->cache()->has($key);
}
/**
* Remove an item from storage.
*
* @param string $key
* @return bool
*/
public function destroy($key)
{
return $this->cache()->forget($key);
}
/**
* Remove all items associated with the tag.
*
* @return void
*/
public function flush()
{
$this->cache()->flush();
}
/**
* Return the cache instance with tags attached.
*
* @return \Illuminate\Cache\CacheManager
*/
protected function cache()
{
if (! method_exists($this->cache, 'tags')) {
return $this->cache;
}
return $this->cache->tags($this->tag);
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\Storage;
interface StorageInterface
{
/**
* @param string $key
* @param int $minutes
* @return void
*/
public function add($key, $value, $minutes);
/**
* @param string $key
* @return bool
*/
public function has($key);
/**
* @param string $key
* @return bool
*/
public function destroy($key);
/**
* @return void
*/
public function flush();
}

View File

@ -0,0 +1,44 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\User;
use Illuminate\Database\Eloquent\Model;
class EloquentUserAdapter implements UserInterface
{
/**
* @var \Illuminate\Database\Eloquent\Model
*/
protected $user;
/**
* Create a new User instance.
*
* @param \Illuminate\Database\Eloquent\Model $user
*/
public function __construct(Model $user)
{
$this->user = $user;
}
/**
* Get the user by the given key, value.
*
* @param mixed $key
* @param mixed $value
* @return Illuminate\Database\Eloquent\Model
*/
public function getBy($key, $value)
{
return $this->user->where($key, $value)->first();
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Providers\User;
interface UserInterface
{
/**
* Get the user by the given key, value.
*
* @param string $key
* @param mixed $value
* @return Illuminate\Database\Eloquent\Model|null
*/
public function getBy($key, $value);
}

54
src/Token.php Normal file
View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth;
use Tymon\JWTAuth\Validators\TokenValidator;
class Token
{
/**
* @var string
*/
private $value;
/**
* Create a new JSON Web Token.
*
* @param string $value
*/
public function __construct($value)
{
with(new TokenValidator)->check($value);
$this->value = $value;
}
/**
* Get the token.
*
* @return string
*/
public function get()
{
return $this->value;
}
/**
* Get the token when casting to string.
*
* @return string
*/
public function __toString()
{
return (string) $this->value;
}
}

38
src/Utils.php Normal file
View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth;
use Carbon\Carbon;
class Utils
{
/**
* Get the Carbon instance for the current time.
*
* @return \Carbon\Carbon
*/
public static function now()
{
return Carbon::now();
}
/**
* Get the Carbon instance for the timestamp.
*
* @param int $timestamp
* @return \Carbon\Carbon
*/
public static function timestamp($timestamp)
{
return Carbon::createFromTimeStampUTC($timestamp);
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Validators;
use Tymon\JWTAuth\Exceptions\JWTException;
abstract class AbstractValidator implements ValidatorInterface
{
/**
* @var bool
*/
protected $refreshFlow = false;
/**
* Helper function to return a boolean.
*
* @param array $value
* @return bool
*/
public function isValid($value)
{
try {
$this->check($value);
} catch (JWTException $e) {
return false;
}
return true;
}
/**
* Set the refresh flow flag.
*
* @param bool $refreshFlow
* @return $this
*/
public function setRefreshFlow($refreshFlow = true)
{
$this->refreshFlow = $refreshFlow;
return $this;
}
}

View File

@ -0,0 +1,127 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Validators;
use Tymon\JWTAuth\Utils;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class PayloadValidator extends AbstractValidator
{
/**
* @var array
*/
protected $requiredClaims = ['iss', 'iat', 'exp', 'nbf', 'sub', 'jti'];
/**
* @var int
*/
protected $refreshTTL = 20160;
/**
* Run the validations on the payload array.
*
* @param array $value
* @return void
*/
public function check($value)
{
$this->validateStructure($value);
if (! $this->refreshFlow) {
$this->validateTimestamps($value);
} else {
$this->validateRefresh($value);
}
}
/**
* Ensure the payload contains the required claims and
* the claims have the relevant type.
*
* @param array $payload
* @throws \Tymon\JWTAuth\Exceptions\TokenInvalidException
* @return bool
*/
protected function validateStructure(array $payload)
{
if (count(array_diff($this->requiredClaims, array_keys($payload))) !== 0) {
throw new TokenInvalidException('JWT payload does not contain the required claims');
}
return true;
}
/**
* Validate the payload timestamps.
*
* @param array $payload
* @throws \Tymon\JWTAuth\Exceptions\TokenExpiredException
* @throws \Tymon\JWTAuth\Exceptions\TokenInvalidException
* @return bool
*/
protected function validateTimestamps(array $payload)
{
if (isset($payload['nbf']) && Utils::timestamp($payload['nbf'])->isFuture()) {
throw new TokenInvalidException('Not Before (nbf) timestamp cannot be in the future', 400);
}
if (isset($payload['iat']) && Utils::timestamp($payload['iat'])->isFuture()) {
throw new TokenInvalidException('Issued At (iat) timestamp cannot be in the future', 400);
}
if (Utils::timestamp($payload['exp'])->isPast()) {
throw new TokenExpiredException('Token has expired');
}
return true;
}
/**
* Check the token in the refresh flow context.
*
* @param $payload
* @return bool
*/
protected function validateRefresh(array $payload)
{
if (isset($payload['iat']) && Utils::timestamp($payload['iat'])->addMinutes($this->refreshTTL)->isPast()) {
throw new TokenExpiredException('Token has expired and can no longer be refreshed', 400);
}
return true;
}
/**
* Set the required claims.
*
* @param array $claims
*/
public function setRequiredClaims(array $claims)
{
$this->requiredClaims = $claims;
return $this;
}
/**
* Set the refresh ttl.
*
* @param int $ttl
*/
public function setRefreshTTL($ttl)
{
$this->refreshTTL = $ttl;
return $this;
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Validators;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class TokenValidator extends AbstractValidator
{
/**
* Check the structure of the token.
*
* @param string $value
* @return void
*/
public function check($value)
{
$this->validateStructure($value);
}
/**
* @param string $token
* @throws \Tymon\JWTAuth\Exceptions\TokenInvalidException
* @return bool
*/
protected function validateStructure($token)
{
if (count(explode('.', $token)) !== 3) {
throw new TokenInvalidException('Wrong number of segments');
}
return true;
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Validators;
interface ValidatorInterface
{
/**
* Perform some checks on the value.
*
* @param mixed $value
* @return void
*/
public function check($value);
/**
* Helper function to return a boolean.
*
* @param array $value
* @return bool
*/
public function isValid($value);
}

173
src/config/config.php Normal file
View File

@ -0,0 +1,173 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
/*
|--------------------------------------------------------------------------
| JWT Authentication Secret
|--------------------------------------------------------------------------
|
| Don't forget to set this, as it will be used to sign your tokens.
| A helper command is provided for this: `php artisan jwt:generate`
|
*/
'secret' => env('JWT_SECRET', 'changeme'),
/*
|--------------------------------------------------------------------------
| JWT time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token will be valid for.
| Defaults to 1 hour
|
*/
'ttl' => 60,
/*
|--------------------------------------------------------------------------
| Refresh time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token can be refreshed
| within. I.E. The user can refresh their token within a 2 week window of
| the original token being created until they must re-authenticate.
| Defaults to 2 weeks
|
*/
'refresh_ttl' => 20160,
/*
|--------------------------------------------------------------------------
| JWT hashing algorithm
|--------------------------------------------------------------------------
|
| Specify the hashing algorithm that will be used to sign the token.
|
| See here: https://github.com/namshi/jose/tree/2.2.0/src/Namshi/JOSE/Signer
| for possible values
|
*/
'algo' => 'HS256',
/*
|--------------------------------------------------------------------------
| User Model namespace
|--------------------------------------------------------------------------
|
| Specify the full namespace to your User model.
| e.g. 'Acme\Entities\User'
|
*/
'user' => 'App\User',
/*
|--------------------------------------------------------------------------
| User identifier
|--------------------------------------------------------------------------
|
| Specify a unique property of the user that will be added as the 'sub'
| claim of the token payload.
|
*/
'identifier' => 'id',
/*
|--------------------------------------------------------------------------
| Required Claims
|--------------------------------------------------------------------------
|
| Specify the required claims that must exist in any token.
| A TokenInvalidException will be thrown if any of these claims are not
| present in the payload.
|
*/
'required_claims' => ['iss', 'iat', 'exp', 'nbf', 'sub', 'jti'],
/*
|--------------------------------------------------------------------------
| Blacklist Enabled
|--------------------------------------------------------------------------
|
| In order to invalidate tokens, you must have the blacklist enabled.
| If you do not want or need this functionality, then set this to false.
|
*/
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| Specify the various providers used throughout the package.
|
*/
'providers' => [
/*
|--------------------------------------------------------------------------
| User Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to find the user based
| on the subject claim
|
*/
'user' => 'Tymon\JWTAuth\Providers\User\EloquentUserAdapter',
/*
|--------------------------------------------------------------------------
| JWT Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to create and decode the tokens.
|
*/
'jwt' => 'Tymon\JWTAuth\Providers\JWT\NamshiAdapter',
/*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to authenticate users.
|
*/
'auth' => 'Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter',
/*
|--------------------------------------------------------------------------
| Storage Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to store tokens in the blacklist
|
*/
'storage' => 'Tymon\JWTAuth\Providers\Storage\IlluminateCacheAdapter',
],
];

153
tests/BlacklistTest.php Normal file
View File

@ -0,0 +1,153 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\JWT;
use Mockery;
use Carbon\Carbon;
use Tymon\JWTAuth\Payload;
use Tymon\JWTAuth\Blacklist;
use Tymon\JWTAuth\Claims\JwtId;
use Tymon\JWTAuth\Claims\Issuer;
use Tymon\JWTAuth\Claims\Subject;
use Tymon\JWTAuth\Claims\IssuedAt;
use Tymon\JWTAuth\Claims\NotBefore;
use Tymon\JWTAuth\Claims\Expiration;
class BlacklistTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
Carbon::setTestNow(Carbon::createFromTimeStampUTC(123));
$this->storage = Mockery::mock('Tymon\JWTAuth\Providers\Storage\StorageInterface');
$this->blacklist = new Blacklist($this->storage);
$this->blacklist->setRefreshTTL(20160);
$this->validator = Mockery::mock('Tymon\JWTAuth\Validators\PayloadValidator');
$this->validator->shouldReceive('setRefreshFlow->check');
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_add_a_valid_token_to_the_blacklist()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(100 + 3600),
new NotBefore(100),
new IssuedAt(100),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator);
$this->storage->shouldReceive('add')->once()->with('foo', [], 20160);
$this->assertTrue($this->blacklist->add($payload));
}
/** @test */
public function it_should_return_true_when_adding_a_refreshable_expired_token_to_the_blacklist()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(101),
new NotBefore(100),
new IssuedAt(100),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator, true);
$this->storage->shouldReceive('add')->once()->with('foo', [], 20160);
$this->assertTrue($this->blacklist->add($payload));
}
/** @test */
public function it_should_return_false_when_adding_an_unrefreshable_token_to_the_blacklist()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(100), // default refresh_ttl
new NotBefore(100),
new IssuedAt(100 - 20160 * 60),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator, true);
$this->storage->shouldReceive('add')->never();
$this->assertFalse($this->blacklist->add($payload));
}
/** @test */
public function it_should_return_false_when_adding_a_unrefreshable_token_after_modifying_refresh_ttl()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(101),
new NotBefore(100),
new IssuedAt(100),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator, true);
$this->storage->shouldReceive('add')->never();
$this->blacklist->setRefreshTTL(0);
$this->assertFalse($this->blacklist->add($payload));
}
/** @test */
public function it_should_check_whether_a_token_has_been_blacklisted()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 + 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foobar'),
];
$payload = new Payload($claims, $this->validator);
$this->storage->shouldReceive('has')->once()->with('foobar')->andReturn(true);
$this->assertTrue($this->blacklist->has($payload));
}
/** @test */
public function it_should_remove_a_token_from_the_blacklist()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 + 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foobar'),
];
$payload = new Payload($claims, $this->validator);
$this->storage->shouldReceive('destroy')->once()->with('foobar')->andReturn(true);
$this->assertTrue($this->blacklist->remove($payload));
}
/** @test */
public function it_should_empty_the_blacklist()
{
$this->storage->shouldReceive('flush')->once();
$this->assertTrue($this->blacklist->clear());
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test;
use Illuminate\Foundation\Application;
use Tymon\JWTAuth\Commands\JWTGenerateCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Tester\CommandTester;
class JWTGenerateCommandTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->command = new JWTGenerateCommand();
$this->tester = new CommandTester($this->command);
}
/** @test */
public function it_shoud_generate_random_key()
{
// $app = new Application();
// $app['path.base'] = '';
// $this->command->setLaravel($app);
// $this->runCommand($this->command);
}
protected function runCommand($command, $input = [])
{
return $command->run(new ArrayInput($input), new NullOutput);
}
}

235
tests/JWTAuthTest.php Normal file
View File

@ -0,0 +1,235 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test;
use Mockery;
use Tymon\JWTAuth\Token;
use Tymon\JWTAuth\JWTAuth;
use Illuminate\Http\Request;
class JWTAuthTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->user = Mockery::mock('Tymon\JWTAuth\Providers\User\UserInterface');
$this->manager = Mockery::mock('Tymon\JWTAuth\JWTManager');
$this->auth = Mockery::mock('Tymon\JWTAuth\Providers\Auth\AuthInterface');
$this->jwtAuth = new JWTAuth($this->manager, $this->user, $this->auth, Request::create('/foo', 'GET'));
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_return_a_user_when_passing_a_token_containing_a_valid_subject_claim()
{
$payload = Mockery::mock('Tymon\JWTAuth\Payload');
$payload->shouldReceive('offsetGet')->once()->andReturn(1);
$this->manager->shouldReceive('decode')->once()->andReturn($payload);
$this->user->shouldReceive('getBy')->once()->andReturn((object) ['id' => 1]);
$user = $this->jwtAuth->toUser('foo.bar.baz');
$this->assertEquals(1, $user->id);
}
/** @test */
public function it_should_return_false_when_passing_a_token_containing_an_invalid_subject_claim()
{
$payload = Mockery::mock('Tymon\JWTAuth\Payload');
$payload->shouldReceive('offsetGet')->once()->andReturn(1);
$this->manager->shouldReceive('decode')->once()->andReturn($payload);
$this->user->shouldReceive('getBy')->once()->andReturn(false);
$user = $this->jwtAuth->toUser('foo.bar.baz');
$this->assertFalse($user);
}
/** @test */
public function it_should_return_a_token_when_passing_a_user()
{
$this->manager->shouldReceive('getPayloadFactory->make')->once()->andReturn(Mockery::mock('Tymon\JWTAuth\Payload'));
$this->manager->shouldReceive('encode->get')->once()->andReturn('foo.bar.baz');
$token = $this->jwtAuth->fromUser((object) ['id' => 1]);
$this->assertEquals($token, 'foo.bar.baz');
}
/** @test */
public function it_should_return_a_token_when_passing_valid_credentials_to_attempt_method()
{
$this->manager->shouldReceive('getPayloadFactory->make')->once()->andReturn(Mockery::mock('Tymon\JWTAuth\Payload'));
$this->manager->shouldReceive('encode->get')->once()->andReturn('foo.bar.baz');
$this->auth->shouldReceive('byCredentials')->once()->andReturn(true);
$this->auth->shouldReceive('user')->once()->andReturn((object) ['id' => 1]);
$token = $this->jwtAuth->attempt();
$this->assertEquals($token, 'foo.bar.baz');
}
/** @test */
public function it_should_return_false_when_passing_invalid_credentials_to_attempt_method()
{
$this->manager->shouldReceive('encode->get')->never();
$this->auth->shouldReceive('byCredentials')->once()->andReturn(false);
$this->auth->shouldReceive('user')->never();
$token = $this->jwtAuth->attempt();
$this->assertFalse($token);
}
/** @test */
public function it_should_throw_an_exception_when_not_providing_a_token()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\JWTException');
$this->jwtAuth->toUser();
}
/** @test */
public function it_should_return_the_owning_user_from_a_token_containing_an_existing_user()
{
$payload = Mockery::mock('Tymon\JWTAuth\Payload');
$payload->shouldReceive('get')->once()->with('sub')->andReturn(1);
$this->manager->shouldReceive('decode')->once()->andReturn($payload);
$this->auth->shouldReceive('byId')->once()->with(1)->andReturn(true);
$this->auth->shouldReceive('user')->once()->andReturn((object) ['id' => 1]);
$user = $this->jwtAuth->authenticate('foo.bar.baz');
$this->assertEquals($user->id, 1);
}
/** @test */
public function it_should_return_false_when_passing_a_token_not_containing_an_existing_user()
{
$payload = Mockery::mock('Tymon\JWTAuth\Payload');
$payload->shouldReceive('get')->once()->with('sub')->andReturn(1);
$this->manager->shouldReceive('decode')->once()->andReturn($payload);
$this->auth->shouldReceive('byId')->once()->with(1)->andReturn(false);
$this->auth->shouldReceive('user')->never();
$user = $this->jwtAuth->authenticate('foo.bar.baz');
$this->assertFalse($user);
}
/** @test */
public function it_should_refresh_a_token()
{
$newToken = Mockery::mock('Tymon\JWTAuth\Token');
$newToken->shouldReceive('get')->once()->andReturn('baz.bar.foo');
$this->manager->shouldReceive('refresh')->once()->andReturn($newToken);
$result = $this->jwtAuth->setToken('foo.bar.baz')->refresh();
$this->assertEquals($result, 'baz.bar.foo');
}
/** @test */
public function it_should_invalidate_a_token()
{
$this->manager->shouldReceive('invalidate')->once()->andReturn(true);
$result = $this->jwtAuth->invalidate('foo.bar.baz');
$this->assertTrue($result);
}
/** @test */
public function it_should_retrieve_the_token_from_the_auth_header()
{
$request = Request::create('/foo', 'GET');
$request->headers->set('authorization', 'Bearer foo.bar.baz');
$jwtAuth = new JWTAuth($this->manager, $this->user, $this->auth, $request);
$this->assertInstanceOf('Tymon\JWTAuth\Token', $jwtAuth->parseToken()->getToken());
$this->assertEquals($jwtAuth->getToken(), 'foo.bar.baz');
}
/** @test */
public function it_should_retrieve_the_token_from_the_query_string()
{
$request = Request::create('/foo', 'GET', ['token' => 'foo.bar.baz']);
$jwtAuth = new JWTAuth($this->manager, $this->user, $this->auth, $request);
$this->assertInstanceOf('Tymon\JWTAuth\Token', $jwtAuth->parseToken()->getToken());
$this->assertEquals($jwtAuth->getToken(), 'foo.bar.baz');
}
/** @test */
public function it_should_throw_an_exception_when_token_not_present_in_request()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\JWTException');
$request = Request::create('/foo', 'GET');
$jwtAuth = new JWTAuth($this->manager, $this->user, $this->auth, $request);
$jwtAuth->parseToken();
}
/** @test */
public function it_should_return_false_when_no_token_is_set()
{
$this->assertFalse($this->jwtAuth->getToken());
}
/** @test */
public function it_should_set_the_identifier()
{
$this->jwtAuth->setIdentifier('foo');
$this->assertEquals($this->jwtAuth->getIdentifier(), 'foo');
}
/** @test */
public function it_should_magically_call_the_manager()
{
$this->manager->shouldReceive('getBlacklist')->andReturn(new \StdClass);
$blacklist = $this->jwtAuth->getBlacklist();
$this->assertInstanceOf('StdClass', $blacklist);
}
/** @test */
public function it_should_set_the_request()
{
$request = Request::create('/foo', 'GET', ['token' => 'some.random.token']);
$token = $this->jwtAuth->setRequest($request)->getToken();
$this->assertEquals('some.random.token', $token);
}
/** @test */
public function it_should_get_the_manager_instance()
{
$manager = $this->jwtAuth->manager();
$this->assertInstanceOf('Tymon\JWTAuth\JWTManager', $manager);
}
}

188
tests/JWTManagerTest.php Normal file
View File

@ -0,0 +1,188 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\JWT;
use Mockery;
use Tymon\JWTAuth\Token;
use Tymon\JWTAuth\Payload;
use Tymon\JWTAuth\JWTManager;
use Tymon\JWTAuth\Claims\JwtId;
use Tymon\JWTAuth\Claims\Issuer;
use Tymon\JWTAuth\Claims\Subject;
use Tymon\JWTAuth\Claims\IssuedAt;
use Tymon\JWTAuth\Claims\NotBefore;
use Tymon\JWTAuth\Claims\Expiration;
class JWTManagerTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->jwt = Mockery::mock('Tymon\JWTAuth\Providers\JWT\JWTInterface');
$this->blacklist = Mockery::mock('Tymon\JWTAuth\Blacklist');
$this->factory = Mockery::mock('Tymon\JWTAuth\PayloadFactory');
$this->manager = new JWTManager($this->jwt, $this->blacklist, $this->factory);
$this->validator = Mockery::mock('Tymon\JWTAuth\Validators\PayloadValidator');
$this->validator->shouldReceive('setRefreshFlow->check');
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_encode_a_payload()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 + 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator);
$this->jwt->shouldReceive('encode')->with($payload->toArray())->andReturn('foo.bar.baz');
$token = $this->manager->encode($payload);
$this->assertEquals($token, 'foo.bar.baz');
}
/** @test */
public function it_should_decode_a_token()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 + 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator);
$token = new Token('foo.bar.baz');
$this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray());
$this->factory->shouldReceive('setRefreshFlow->make')->with($payload->toArray())->andReturn($payload);
$this->blacklist->shouldReceive('has')->with($payload)->andReturn(false);
$payload = $this->manager->decode($token);
$this->assertInstanceOf('Tymon\JWTAuth\Payload', $payload);
}
/** @test */
public function it_should_throw_exception_when_token_is_blacklisted()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenBlacklistedException');
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 + 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator);
$token = new Token('foo.bar.baz');
$this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray());
$this->factory->shouldReceive('setRefreshFlow->make')->with($payload->toArray())->andReturn($payload);
$this->blacklist->shouldReceive('has')->with($payload)->andReturn(true);
$this->manager->decode($token);
}
/** @test */
public function it_should_refresh_a_token()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 - 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator, true);
$token = new Token('foo.bar.baz');
$this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray());
$this->jwt->shouldReceive('encode')->with($payload->toArray())->andReturn('baz.bar.foo');
$this->factory->shouldReceive('setRefreshFlow')->andReturn($this->factory);
$this->factory->shouldReceive('make')->andReturn($payload);
$this->blacklist->shouldReceive('has')->with($payload)->andReturn(false);
$this->blacklist->shouldReceive('add')->once()->with($payload);
$token = $this->manager->refresh($token);
$this->assertInstanceOf('Tymon\JWTAuth\Token', $token);
$this->assertEquals('baz.bar.foo', $token);
}
/** @test */
public function it_should_invalidate_a_token()
{
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 + 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foo'),
];
$payload = new Payload($claims, $this->validator);
$token = new Token('foo.bar.baz');
$this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray());
$this->factory->shouldReceive('setRefreshFlow->make')->with($payload->toArray())->andReturn($payload);
$this->blacklist->shouldReceive('has')->with($payload)->andReturn(false);
$this->blacklist->shouldReceive('add')->with($payload)->andReturn(true);
$this->manager->invalidate($token);
}
/** @test */
public function it_should_throw_an_exception_when_enable_blacklist_is_set_to_false()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\JWTException');
$token = new Token('foo.bar.baz');
$this->manager->setBlacklistEnabled(false)->invalidate($token);
}
/** @test */
public function it_should_get_the_payload_factory()
{
$this->assertInstanceOf('Tymon\JWTAuth\PayloadFactory', $this->manager->getPayloadFactory());
}
/** @test */
public function it_should_get_the_jwt_provider()
{
$this->assertInstanceOf('Tymon\JWTAuth\Providers\JWT\JWTInterface', $this->manager->getJWTProvider());
}
/** @test */
public function it_should_get_the_blacklist()
{
$this->assertInstanceOf('Tymon\JWTAuth\Blacklist', $this->manager->getBlacklist());
}
}

View File

@ -0,0 +1,108 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test;
use Mockery;
use Tymon\JWTAuth\Middleware\GetUserFromToken;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class GetUserFromTokenTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
$this->auth = Mockery::mock('Tymon\JWTAuth\JWTAuth');
$this->request = Mockery::mock('Illuminate\Http\Request');
$this->response = Mockery::mock('Illuminate\Contracts\Routing\ResponseFactory');
$this->middleware = new GetUserFromToken($this->response, $this->events, $this->auth);
$this->auth->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth);
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_fire_an_event_when_no_token_is_available()
{
$this->auth->shouldReceive('getToken')->once()->andReturn(false);
$this->events->shouldReceive('fire')->once()->with('tymon.jwt.absent', [], true);
$this->response->shouldReceive('json')->with(['error' => 'token_not_provided'], 400);
$this->middleware->handle($this->request, function () {
});
}
/** @test */
public function it_should_fire_an_event_when_the_token_has_expired()
{
$exception = new TokenExpiredException;
$this->auth->shouldReceive('getToken')->once()->andReturn('foo');
$this->auth->shouldReceive('authenticate')->once()->with('foo')->andThrow($exception);
$this->events->shouldReceive('fire')->once()->with('tymon.jwt.expired', [$exception], true);
$this->response->shouldReceive('json')->with(['error' => 'token_expired'], 401);
$this->middleware->handle($this->request, function () {
});
}
/** @test */
public function it_should_fire_an_event_when_the_token_is_invalid()
{
$exception = new TokenInvalidException;
$this->auth->shouldReceive('getToken')->once()->andReturn('foo');
$this->auth->shouldReceive('authenticate')->once()->with('foo')->andThrow($exception);
$this->events->shouldReceive('fire')->once()->with('tymon.jwt.invalid', [$exception], true);
$this->response->shouldReceive('json')->with(['error' => 'token_invalid'], 400);
$this->middleware->handle($this->request, function () {
});
}
/** @test */
public function it_should_fire_an_event_when_no_user_is_found()
{
$this->auth->shouldReceive('getToken')->once()->andReturn('foo');
$this->auth->shouldReceive('authenticate')->once()->with('foo')->andReturn(false);
$this->events->shouldReceive('fire')->once()->with('tymon.jwt.user_not_found', [], true);
$this->response->shouldReceive('json')->with(['error' => 'user_not_found'], 404);
$this->middleware->handle($this->request, function () {
});
}
/** @test */
public function it_should_fire_an_event_when_the_token_has_been_decoded_and_user_is_found()
{
$user = (object) ['id' => 1];
$this->auth->shouldReceive('getToken')->once()->andReturn('foo');
$this->auth->shouldReceive('authenticate')->once()->with('foo')->andReturn($user);
$this->events->shouldReceive('fire')->once()->with('tymon.jwt.valid', $user);
$this->response->shouldReceive('json')->never();
$this->middleware->handle($this->request, function () {
});
}
}

View File

@ -0,0 +1,136 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\JWT;
use Mockery;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Claims\JwtId;
use Tymon\JWTAuth\Claims\Custom;
use Tymon\JWTAuth\Claims\Issuer;
use Tymon\JWTAuth\Claims\Subject;
use Tymon\JWTAuth\PayloadFactory;
use Tymon\JWTAuth\Claims\IssuedAt;
use Tymon\JWTAuth\Claims\NotBefore;
use Tymon\JWTAuth\Claims\Expiration;
class PayloadFactoryTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
Carbon::setTestNow(Carbon::createFromTimeStampUTC(123));
$this->claimFactory = Mockery::mock('Tymon\JWTAuth\Claims\Factory');
$this->validator = Mockery::mock('Tymon\JWTAuth\Validators\PayloadValidator');
$this->factory = new PayloadFactory($this->claimFactory, Request::create('/foo', 'GET'), $this->validator);
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_return_a_payload_when_passing_an_array_of_claims_to_make_method()
{
$this->validator->shouldReceive('setRefreshFlow->check');
$expTime = 123 + 3600;
$this->claimFactory->shouldReceive('get')->once()->with('sub', 1)->andReturn(new Subject(1));
$this->claimFactory->shouldReceive('get')->once()->with('iss', Mockery::any())->andReturn(new Issuer('/foo'));
$this->claimFactory->shouldReceive('get')->once()->with('iat', 123)->andReturn(new IssuedAt(123));
$this->claimFactory->shouldReceive('get')->once()->with('jti', 'foo')->andReturn(new JwtId('foo'));
$this->claimFactory->shouldReceive('get')->once()->with('nbf', 123)->andReturn(new NotBefore(123));
$this->claimFactory->shouldReceive('get')->once()->with('exp', $expTime)->andReturn(new Expiration($expTime));
$payload = $this->factory->make(['sub' => 1, 'jti' => 'foo', 'iat' => 123]);
$this->assertEquals($payload->get('sub'), 1);
$this->assertEquals($payload->get('iat'), 123);
$this->assertEquals($payload['exp'], $expTime);
$this->assertInstanceOf('Tymon\JWTAuth\Payload', $payload);
}
/** @test **/
public function it_should_check_custom_claim_keys_accurately_and_accept_numeric_claims()
{
$this->validator->shouldReceive('setRefreshFlow->check');
$this->claimFactory->shouldReceive('get')->once()->with('iss', Mockery::any())->andReturn(new Issuer('/foo'));
$this->claimFactory->shouldReceive('get')->once()->with('exp', 123 + 3600)->andReturn(new Expiration(123 + 3600));
$this->claimFactory->shouldReceive('get')->once()->with('iat', 123)->andReturn(new IssuedAt(123));
$this->claimFactory->shouldReceive('get')->once()->with('jti', Mockery::any())->andReturn(new JwtId('foo'));
$this->claimFactory->shouldReceive('get')->once()->with('nbf', 123)->andReturn(new NotBefore(123));
$this->claimFactory->shouldReceive('get')->once()->with(1, 'claim one')->andReturn(new Custom(1, 'claim one'));
$payload = $this->factory->make([1 => 'claim one']);
// if the checker doesn't compare defaults properly, numeric-keyed claims might be ignored
$this->assertEquals('claim one', $payload->get(1));
// iat is $defaultClaims[1], so verify it wasn't skipped due to a bad k-v comparison
$this->assertEquals(123, $payload->get('iat'));
}
/** @test */
public function it_should_return_a_payload_when_chaining_claim_methods()
{
$this->validator->shouldReceive('setRefreshFlow->check');
$this->claimFactory->shouldReceive('get')->once()->with('sub', 1)->andReturn(new Subject(1));
$this->claimFactory->shouldReceive('get')->once()->with('iss', Mockery::any())->andReturn(new Issuer('/foo'));
$this->claimFactory->shouldReceive('get')->once()->with('exp', 123 + 3600)->andReturn(new Expiration(123 + 3600));
$this->claimFactory->shouldReceive('get')->once()->with('iat', 123)->andReturn(new IssuedAt(123));
$this->claimFactory->shouldReceive('get')->once()->with('jti', Mockery::any())->andReturn(new JwtId('foo'));
$this->claimFactory->shouldReceive('get')->once()->with('nbf', 123)->andReturn(new NotBefore(123));
$this->claimFactory->shouldReceive('get')->once()->with('foo', 'baz')->andReturn(new Custom('foo', 'baz'));
$payload = $this->factory->sub(1)->foo('baz')->make();
$this->assertEquals($payload['sub'], 1);
$this->assertEquals($payload->get('jti'), 'foo');
$this->assertEquals($payload->get('foo'), 'baz');
$this->assertInstanceOf('Tymon\JWTAuth\Payload', $payload);
}
/** @test */
public function it_should_return_a_payload_when_passing_miltidimensional_claims()
{
$this->validator->shouldReceive('setRefreshFlow->check');
$userObject = ['name' => 'example'];
$this->claimFactory->shouldReceive('get')->once()->with('sub', $userObject)->andReturn(new Subject($userObject));
$this->claimFactory->shouldReceive('get')->once()->with('iss', Mockery::any())->andReturn(new Issuer('/foo'));
$this->claimFactory->shouldReceive('get')->once()->with('exp', Mockery::any())->andReturn(new Expiration(123 + 3600));
$this->claimFactory->shouldReceive('get')->once()->with('iat', Mockery::any())->andReturn(new IssuedAt(123));
$this->claimFactory->shouldReceive('get')->once()->with('jti', Mockery::any())->andReturn(new JwtId('foo'));
$this->claimFactory->shouldReceive('get')->once()->with('nbf', Mockery::any())->andReturn(new NotBefore(123));
$this->claimFactory->shouldReceive('get')->once()->with('foo', ['bar' => [0, 0, 0]])->andReturn(new Custom('foo', ['bar' => [0, 0, 0]]));
$payload = $this->factory->sub($userObject)->foo(['bar' => [0, 0, 0]])->make();
$this->assertEquals($payload->get('sub'), $userObject);
$this->assertEquals($payload->get('foo'), ['bar' => [0, 0, 0]]);
$this->assertInstanceOf('Tymon\JWTAuth\Payload', $payload);
}
/** @test */
public function it_should_set_the_ttl()
{
$this->factory->setTTL(12345);
$this->assertEquals($this->factory->getTTL(), 12345);
}
}

136
tests/PayloadTest.php Normal file
View File

@ -0,0 +1,136 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\JWT;
use Mockery;
use Carbon\Carbon;
use Tymon\JWTAuth\Payload;
use Tymon\JWTAuth\Claims\JwtId;
use Tymon\JWTAuth\Claims\Issuer;
use Tymon\JWTAuth\Claims\Subject;
use Tymon\JWTAuth\Claims\Audience;
use Tymon\JWTAuth\Claims\IssuedAt;
use Tymon\JWTAuth\Claims\NotBefore;
use Tymon\JWTAuth\Claims\Expiration;
class PayloadTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
Carbon::setTestNow(Carbon::createFromTimeStampUTC(123));
$claims = [
new Subject(1),
new Issuer('http://example.com'),
new Expiration(123 + 3600),
new NotBefore(123),
new IssuedAt(123),
new JwtId('foo'),
];
$this->validator = Mockery::mock('Tymon\JWTAuth\Validators\PayloadValidator');
$this->validator->shouldReceive('setRefreshFlow->check');
$this->payload = new Payload($claims, $this->validator);
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_throws_an_exception_when_trying_to_add_to_the_payload()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\PayloadException');
$this->payload['foo'] = 'bar';
}
/** @test */
public function it_throws_an_exception_when_trying_to_remove_a_key_from_the_payload()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\PayloadException');
unset($this->payload['foo']);
}
/** @test */
public function it_should_cast_the_payload_to_a_string_as_json()
{
$this->assertEquals((string) $this->payload, json_encode($this->payload->get()));
$this->assertJsonStringEqualsJsonString((string) $this->payload, json_encode($this->payload->get()));
}
/** @test */
public function it_should_allow_array_access_on_the_payload()
{
$this->assertTrue(isset($this->payload['iat']));
$this->assertEquals($this->payload['sub'], 1);
$this->assertArrayHasKey('exp', $this->payload);
}
/** @test */
public function it_should_get_properties_of_payload_via_get_method()
{
$this->assertInternalType('array', $this->payload->get());
$this->assertEquals($this->payload->get('sub'), 1);
}
/** @test */
public function it_should_get_multiple_properties_when_passing_an_array_to_the_get_method()
{
$values = $this->payload->get(['sub', 'jti']);
list($sub, $jti) = $values;
$this->assertInternalType('array', $values);
$this->assertEquals($sub, 1);
$this->assertEquals($jti, 'foo');
}
/** @test */
public function it_should_determine_whether_the_payload_has_a_claim()
{
$this->assertTrue($this->payload->has(new Subject(1)));
$this->assertFalse($this->payload->has(new Audience(1)));
}
/** @test */
public function it_should_magically_get_a_property()
{
$sub = $this->payload->getSubject();
$jti = $this->payload->getJwtId();
$iss = $this->payload->getIssuer();
$this->assertEquals($sub, 1);
$this->assertEquals($jti, 'foo');
$this->assertEquals($iss, 'http://example.com');
}
/** @test */
public function it_should_throw_an_exception_when_magically_getting_a_property_that_does_not_exist()
{
$this->setExpectedException('\BadMethodCallException');
$this->payload->getFoo();
}
/** @test */
public function it_should_get_the_claims()
{
$claims = $this->payload->getClaims();
$this->assertInstanceOf('Tymon\JWTAuth\Claims\Expiration', $claims[2]);
$this->assertInstanceOf('Tymon\JWTAuth\Claims\JwtId', $claims[5]);
}
}

View File

@ -0,0 +1,65 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\Auth;
use Mockery;
use Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter;
class IlluminateAuthAdapterTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->authManager = Mockery::mock('Illuminate\Auth\AuthManager');
$this->auth = new IlluminateAuthAdapter($this->authManager);
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_return_true_if_credentials_are_valid()
{
$this->authManager->shouldReceive('once')->once()->with(['email' => 'foo@bar.com', 'password' => 'foobar'])->andReturn(true);
$this->assertTrue($this->auth->byCredentials(['email' => 'foo@bar.com', 'password' => 'foobar']));
}
/** @test */
public function it_should_return_true_if_user_is_found()
{
$this->authManager->shouldReceive('onceUsingId')->once()->with(123)->andReturn(true);
$this->assertTrue($this->auth->byId(123));
}
/** @test */
public function it_should_return_false_if_user_is_not_found()
{
$this->authManager->shouldReceive('onceUsingId')->once()->with(123)->andReturn(false);
$this->assertFalse($this->auth->byId(123));
}
/** @test */
public function it_should_bubble_exceptions_from_auth()
{
$this->authManager->shouldReceive('onceUsingId')->once()->with(123)->andThrow(new \Exception('Some auth failure'));
$this->setExpectedException('Exception', 'Some auth failure');
$this->auth->byId(123);
}
/** @test */
public function it_should_return_the_currently_authenticated_user()
{
$this->authManager->shouldReceive('user')->once()->andReturn((object) ['id' => 1]);
$this->assertEquals($this->auth->user()->id, 1);
}
}

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\JWT;
use Mockery;
use Tymon\JWTAuth\Test\Stubs\JWTProviderStub;
class JWTProviderTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->provider = new JWTProviderStub('secret', 'HS256');
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_set_the_algo()
{
$this->provider->setAlgo('HS512');
$this->assertEquals('HS512', $this->provider->getAlgo());
}
}

View File

@ -0,0 +1,77 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\JWT;
use Mockery;
use Carbon\Carbon;
use Tymon\JWTAuth\Providers\JWT\NamshiAdapter;
class NamshiAdapterTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
Carbon::setTestNow(Carbon::createFromTimeStampUTC(123));
$this->jws = Mockery::mock('Namshi\JOSE\JWS');
$this->provider = new NamshiAdapter('secret', 'HS256', $this->jws);
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_return_the_token_when_passing_a_valid_subject_to_encode()
{
$payload = ['sub' => 1, 'exp' => 123, 'iat' => 123, 'iss' => '/foo'];
$this->jws->shouldReceive('setPayload')->once()->with($payload)->andReturn(Mockery::self());
$this->jws->shouldReceive('sign')->once()->with('secret')->andReturn(Mockery::self());
$this->jws->shouldReceive('getTokenString')->once()->andReturn('foo.bar.baz');
$token = $this->provider->encode($payload);
$this->assertEquals('foo.bar.baz', $token);
}
/** @test */
public function it_should_throw_an_invalid_exception_when_the_payload_could_not_be_encoded()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\JWTException');
$this->jws->shouldReceive('sign')->andThrow(new \Exception);
$payload = ['sub' => 1, 'exp' => 123, 'iat' => 123, 'iss' => '/foo'];
$this->provider->encode($payload);
}
/** @test */
// public function it_should_return_the_payload_when_passing_a_valid_token_to_decode()
// {
// $this->jws->shouldReceive('load')->once()->with('foo.bar.baz')->andReturn(true);
// $this->jws->shouldReceive('verify')->andReturn(true);
// $payload = $this->provider->decode('foo.bar.baz');
// }
/** @test */
public function it_should_throw_a_token_invalid_exception_when_the_token_could_not_be_decoded()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenInvalidException');
$this->jws->shouldReceive('verify')->andReturn(false);
$token = $this->provider->decode('foo');
}
}

View File

@ -0,0 +1,63 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\Storage;
use Mockery;
use Tymon\JWTAuth\Providers\Storage\IlluminateCacheAdapter;
class IlluminateCacheAdapterTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->cache = Mockery::mock('Illuminate\Cache\CacheManager');
$this->storage = new IlluminateCacheAdapter($this->cache);
$this->cache->shouldReceive('tags')->andReturn(Mockery::self());
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_add_the_item_to_storage()
{
$this->cache->shouldReceive('tags->put')->with('foo', 'bar', 10);
$this->storage->add('foo', 'bar', 10);
}
/** @test */
public function it_should_check_if_the_item_exists_in_storage()
{
$this->cache->shouldReceive('tags->has')->with('foo')->andReturn(true);
$this->assertTrue($this->storage->has('foo'));
}
/** @test */
public function it_should_remove_the_item_from_storage()
{
$this->cache->shouldReceive('tags->forget')->with('foo')->andReturn(true);
$this->assertTrue($this->storage->destroy('foo'));
}
/** @test */
public function it_should_remove_all_items_from_storage()
{
$this->cache->shouldReceive('tags->flush')->withNoArgs();
$this->storage->flush();
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\User;
use Mockery;
use Tymon\JWTAuth\Providers\User\EloquentUserAdapter;
class EloquentUserAdapterTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->builder = Mockery::mock('Illuminate\Database\Query\Builder');
$this->model = Mockery::mock('Illuminate\Database\Eloquent\Model');
$this->user = new EloquentUserAdapter($this->model);
}
public function tearDown()
{
Mockery::close();
}
/** @test */
public function it_should_return_the_user_if_found()
{
$this->builder->shouldReceive('first')->once()->withNoArgs()->andReturn((object) ['id' => 1]);
$this->model->shouldReceive('where')->once()->with('foo', 'bar')->andReturn($this->builder);
$user = $this->user->getBy('foo', 'bar');
$this->assertEquals(1, $user->id);
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Stubs;
use Tymon\JWTAuth\Providers\JWT\JWTProvider;
class JWTProviderStub extends JWTProvider
{
}

34
tests/TokenTest.php Normal file
View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test\Providers\JWT;
use Tymon\JWTAuth\Token;
class TokenTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->token = new Token('foo.bar.baz');
}
/** @test */
public function it_should_return_the_token_when_casting_to_a_string()
{
$this->assertEquals((string) $this->token, $this->token);
}
/** @test */
public function it_should_return_the_token_when_calling_get_method()
{
$this->assertInternalType('string', $this->token->get());
}
}

View File

@ -0,0 +1,142 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test;
use Carbon\Carbon;
use Tymon\JWTAuth\Validators\PayloadValidator;
class PayloadValidatorTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
Carbon::setTestNow(Carbon::createFromTimeStampUTC(123));
$this->validator = new PayloadValidator();
}
/** @test */
public function it_should_return_true_when_providing_a_valid_payload()
{
$payload = [
'iss' => 'http://example.com',
'iat' => 100,
'nbf' => 100,
'exp' => 100 + 3600,
'sub' => 1,
'jti' => 'foo',
];
$this->assertTrue($this->validator->isValid($payload));
}
/** @test */
public function it_should_throw_an_exception_when_providing_an_expired_payload()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenExpiredException');
$payload = [
'iss' => 'http://example.com',
'iat' => 20,
'nbf' => 20,
'exp' => 120,
'sub' => 1,
'jti' => 'foo',
];
$this->validator->check($payload);
}
/** @test */
public function it_should_throw_an_exception_when_providing_an_invalid_nbf_claim()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenInvalidException');
$payload = [
'iss' => 'http://example.com',
'iat' => 100,
'nbf' => 150,
'exp' => 150 + 3600,
'sub' => 1,
'jti' => 'foo',
];
$this->validator->check($payload);
}
/** @test */
public function it_should_throw_an_exception_when_providing_an_invalid_iat_claim()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenInvalidException');
$payload = [
'iss' => 'http://example.com',
'iat' => 150,
'nbf' => 100,
'exp' => 150 + 3600,
'sub' => 1,
'jti' => 'foo',
];
$this->validator->check($payload);
}
/** @test */
public function it_should_throw_an_exception_when_providing_an_invalid_payload()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenInvalidException');
$payload = [
'iss' => 'http://example.com',
'sub' => 1,
];
$this->validator->check($payload);
}
/** @test */
public function it_should_throw_an_exception_when_providing_an_invalid_expiry()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenInvalidException');
$payload = [
'iss' => 'http://example.com',
'iat' => 100,
'exp' => 'foo',
'sub' => 1,
'jti' => 'foo',
];
$this->validator->check($payload);
}
/** @test **/
public function it_should_throw_an_exception_when_required_claims_are_missing()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenInvalidException');
$payload = [
'iss' => 'http://example.com',
'foo' => 'bar',
// these are inserted to check for regression to a previous bug
// where the check would only compare keys of autoindexed name arrays
// (There are enough to account for all of the required claims' indices)
'autoindexed',
'autoindexed',
'autoindexed',
'autoindexed',
'autoindexed',
'autoindexed',
'autoindexed',
];
$this->validator->check($payload);
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Test;
use Tymon\JWTAuth\Validators\TokenValidator;
class TokenValidatorTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->validator = new TokenValidator();
}
/** @test */
public function it_should_return_true_when_providing_a_well_formed_token()
{
$this->assertTrue($this->validator->isValid('one.two.three'));
}
/** @test */
public function it_should_return_false_when_providing_a_malformed_token()
{
$this->assertFalse($this->validator->isValid('one.two.three.four.five'));
}
/** @test */
public function it_should_throw_an_axception_when_providing_a_malformed_token()
{
$this->setExpectedException('Tymon\JWTAuth\Exceptions\TokenInvalidException');
$this->validator->check('one.two.three.four.five');
}
}