├── .coveralls.yml
├── .gitignore
├── .php_cs
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
├── JwtMiddleware.php
├── JwtToken.php
├── Manager
│ └── JwtManager.php
├── Persistence
│ ├── NullTokenPersistence.php
│ ├── SimpleCacheTokenPersistence.php
│ └── TokenPersistenceInterface.php
└── Strategy
│ └── Auth
│ ├── AbstractBaseAuthStrategy.php
│ ├── AuthStrategyInterface.php
│ ├── FormAuthStrategy.php
│ ├── HttpBasicAuthStrategy.php
│ ├── JsonAuthStrategy.php
│ └── QueryAuthStrategy.php
└── tests
├── JwtMiddlewareTest.php
├── JwtTokenTest.php
├── Manager
└── JwtManagerTest.php
├── Persistence
└── TokenPersistenceTest.php
└── Strategy
└── Auth
└── AuthStrategyTest.php
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | composer.phar
3 | vendor/
4 | bin/*
5 | .php_cs.cache
6 | .idea/
7 | .phpunit.result.cache
8 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | setUsingCache(true)
5 | ->finder(
6 | Symfony\CS\Finder\DefaultFinder::create()
7 | ->in(__DIR__.'/Tests')
8 | ->in(__DIR__.'/src')
9 | )
10 | ;
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | cache:
4 | directories:
5 | - bin
6 | - vendor
7 |
8 | php:
9 | - 7.4
10 | - 8.0
11 |
12 | before_script:
13 | - composer selfupdate
14 | - composer install --dev --no-interaction --prefer-source
15 |
16 | script:
17 | - mkdir -p build/logs
18 | - bin/phpunit --coverage-clover build/logs/clover.xml
19 |
20 | after_success:
21 | - travis_retry wget https://scrutinizer-ci.com/ocular.phar
22 | - travis_retry php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
23 | - travis_retry php bin/coveralls -v
24 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 | =========
3 |
4 | This changelog will be used for bugfix for this version
5 |
6 | # 0.7
7 |
8 | * add property accessor to change node path for token and expire fields
9 |
10 | # 0.7.1
11 |
12 | Fix deprecated notice from composer (road to composer 2.0)
13 |
14 | # 1.0.0
15 |
16 | Stable version !
17 |
18 | # 1.0.1
19 |
20 | Fix sipmple-cache requirements, thanks to @neclimdul
21 |
22 | # 2.0.0
23 |
24 | For PHP 8 only
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 eljam
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Guzzle Jwt middleware
2 |
3 | [](https://travis-ci.org/eljam/guzzle-jwt-middleware)
4 | [](https://scrutinizer-ci.com/g/eljam/guzzle-jwt-middleware/?branch=master)
5 | [](https://coveralls.io/r/eljam/guzzle-jwt-middleware)
6 | [](https://insight.sensiolabs.com/projects/87bbdd85-2cd8-4556-94c6-5ed9f501cf7d)
7 | [](https://packagist.org/packages/eljam/guzzle-jwt-middleware)
8 | [](https://packagist.org/packages/eljam/guzzle-jwt-middleware)
9 | [](https://packagist.org/packages/eljam/guzzle-jwt-middleware)
10 | [](https://github.com/eljam/guzzle-jwt-middleware/blob/master/LICENSE)
11 |
12 | ## Introduction
13 |
14 | Works great with [LexikJWTAuthenticationBundle](https://github.com/lexik/LexikJWTAuthenticationBundle)
15 |
16 | ## Installation
17 |
18 | `composer require eljam/guzzle-jwt-middleware`
19 |
20 | ## Usage
21 |
22 | ```php
23 | 'admin', 'password' => 'admin']);
35 |
36 | //Optionnal: create your persistence strategy
37 | $persistenceStrategy = null;
38 |
39 | $baseUri = 'http://api.example.org/';
40 |
41 | // Create authClient
42 | $authClient = new Client(['base_uri' => $baseUri]);
43 |
44 | //Create the JwtManager
45 | $jwtManager = new JwtManager(
46 | $authClient,
47 | $authStrategy,
48 | $persistenceStrategy,
49 | [
50 | 'token_url' => '/api/token',
51 | ]
52 | );
53 |
54 | // Create a HandlerStack
55 | $stack = HandlerStack::create();
56 |
57 | // Add middleware
58 | $stack->push(new JwtMiddleware($jwtManager));
59 |
60 | $client = new Client(['handler' => $stack, 'base_uri' => $baseUri]);
61 |
62 | try {
63 | $response = $client->get('/api/ping');
64 | echo($response->getBody());
65 | } catch (TransferException $e) {
66 | echo $e->getMessage();
67 | }
68 |
69 | //response
70 | //{"data":"pong"}
71 |
72 | ```
73 |
74 | ## Auth Strategies
75 |
76 | ### QueryAuthStrategy
77 |
78 | ```php
79 | $authStrategy = new QueryAuthStrategy(
80 | [
81 | 'username' => 'admin',
82 | 'password' => 'admin',
83 | 'query_fields' => ['username', 'password'],
84 | ]
85 | );
86 | ```
87 |
88 | ### FormAuthStrategy
89 |
90 | ```php
91 | $authStrategy = new FormAuthStrategy(
92 | [
93 | 'username' => 'admin',
94 | 'password' => 'admin',
95 | 'form_fields' => ['username', 'password'],
96 | ]
97 | );
98 | ```
99 |
100 | ### HttpBasicAuthStrategy
101 |
102 | ```php
103 | $authStrategy = new HttpBasicAuthStrategy(
104 | [
105 | 'username' => 'admin',
106 | 'password' => 'password',
107 | ]
108 | );
109 | ```
110 | ### JsonAuthStrategy
111 |
112 | ```php
113 | $authStrategy = new JsonAuthStrategy(
114 | [
115 | 'username' => 'admin',
116 | 'password' => 'admin',
117 | 'json_fields' => ['username', 'password'],
118 | ]
119 | );
120 | ```
121 |
122 | ## Persistence
123 |
124 | To avoid requesting a token everytime php runs, you can pass to `JwtManager` an implementation of `TokenPersistenceInterface`.
125 | By default `NullTokenPersistence` will be used.
126 |
127 | ### Simpe cache adapter (PSR-16)
128 |
129 | If you have any [PSR-16 compatible cache](https://www.php-fig.org/psr/psr-16/), you can use it as a persistence handler:
130 |
131 | ```php
132 | '/api/token',
247 | 'token_key' => 'payload.token',
248 | 'expire_key' => 'expires_in'
249 | ]
250 | );
251 | ```
252 |
253 | ## Default behavior
254 | By default this library assumes your json response has a key `token`, something like this:
255 |
256 | ```javascript
257 | {
258 | token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9..."
259 | }
260 | ```
261 |
262 | but now you can change the token_key in the JwtManager options:
263 |
264 | ```php
265 | $jwtManager = new JwtManager(
266 | $authClient,
267 | $authStrategy,
268 | $persistenceStrategy,
269 | [
270 | 'token_url' => '/api/token',
271 | 'token_key' => 'access_token',
272 | ]
273 | );
274 | ```
275 |
276 | ## Authorization Header Type
277 |
278 | Some endpoints use different Authorization header types (Bearer, JWT, etc...).
279 |
280 | The default is Bearer, but another type can be supplied in the middleware:
281 |
282 | ```php
283 | $stack->push(new JwtMiddleware($jwtManager, 'JWT'));
284 | ```
285 |
286 | ## Cached token
287 |
288 | To avoid too many calls between multiple request, there is a cache system.
289 |
290 | Json example:
291 |
292 | ```javascript
293 | {
294 | token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9...",
295 | expires_in: "3600"
296 | }
297 | ```
298 |
299 | ```php
300 | $jwtManager = new JwtManager(
301 | $authClient,
302 | $authStrategy,
303 | $persistenceStrategy,
304 | [
305 | 'token_url' => '/api/token',
306 | 'token_key' => 'access_token',
307 | 'expire_key' => 'expires_in', # default is expires_in if not set
308 | ]
309 | );
310 | ```
311 |
312 | The bundle natively supports the [exp field](https://tools.ietf.org/html/rfc7519.html#section-4.1.4) in the JWT payload.
313 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eljam/guzzle-jwt-middleware",
3 | "description": "A jwt authentication middleware for guzzle 6",
4 | "keywords": ["guzzle", "guzzle6", "jwt", "auth", "http", "psr7", "handler", "middleware"],
5 | "homepage": "https://github.com/eljam/guzzle-jwt-middleware",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Guillaume Cavana",
10 | "email": "guillaume.cavana@gmail.com"
11 | }
12 | ],
13 | "autoload" : {
14 | "psr-4" : {
15 | "Eljam\\GuzzleJwt\\" : ["src/"]
16 | }
17 | },
18 | "autoload-dev" : {
19 | "psr-4": {
20 | "Eljam\\GuzzleJwt\\Tests\\": ["tests/"]
21 | }
22 | },
23 | "require-dev": {
24 | "phpunit/phpunit": "^9.5",
25 | "kodus/mock-cache": "^1.0",
26 | "php-coveralls/php-coveralls": "^2.4"
27 | },
28 | "require": {
29 | "php" : ">=8.0.0",
30 | "guzzlehttp/guzzle": "^7.0",
31 | "psr/simple-cache": "^1 || ^2 || ^3",
32 | "symfony/options-resolver": ">=2.8",
33 | "symfony/property-access":">=2.8"
34 | },
35 | "config": {
36 | "bin-dir" : "bin/"
37 | },
38 | "extra": {
39 | "branch-alias": {
40 | "dev-master": "1.0.0-dev"
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
20 | .
21 |
22 |
23 | tests
24 | vendor
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/JwtMiddleware.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class JwtMiddleware
12 | {
13 | /**
14 | * $JwtManager.
15 | *
16 | * @var JwtManager
17 | */
18 | protected $jwtManager;
19 |
20 | /**
21 | * The Authorization Header Type (defaults to Bearer)
22 | *
23 | * @var string
24 | */
25 | protected $authorizationHeaderType;
26 |
27 | /**
28 | * Constructor.
29 | *
30 | * @param JwtManager $jwtManager
31 | * @param string $authorizationHeaderType
32 | */
33 | public function __construct(JwtManager $jwtManager, $authorizationHeaderType = 'Bearer')
34 | {
35 | $this->jwtManager = $jwtManager;
36 | $this->authorizationHeaderType = $authorizationHeaderType;
37 | }
38 |
39 | /**
40 | * Called when the middleware is handled by the client.
41 | *
42 | * @param callable $handler
43 | *
44 | * @return callable
45 | */
46 | public function __invoke(callable $handler)
47 | {
48 | $manager = $this->jwtManager;
49 |
50 | return function (
51 | RequestInterface $request,
52 | array $options
53 | ) use (
54 | $handler,
55 | $manager
56 | ) {
57 | $token = $manager->getJwtToken()->getToken();
58 |
59 | return $handler($request->withHeader(
60 | 'Authorization',
61 | sprintf('%s %s', $this->authorizationHeaderType, $token)
62 | ), $options);
63 | };
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/JwtToken.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | class JwtToken
9 | {
10 | /**
11 | * $token.
12 | *
13 | * @var string
14 | */
15 | private $token;
16 |
17 | /**
18 | * @var \DateTime|null
19 | */
20 | private $expiration;
21 |
22 | /**
23 | * Constructor.
24 | *
25 | * @param string $token
26 | * @param \DateTime $expiration
27 | */
28 | public function __construct($token, ?\DateTime $expiration = null)
29 | {
30 | $this->token = $token;
31 | $this->expiration = $expiration;
32 | }
33 |
34 | /**
35 | * getToken.
36 | *
37 | * @return string
38 | */
39 | public function getToken()
40 | {
41 | return $this->token;
42 | }
43 |
44 | /**
45 | * @return bool
46 | */
47 | public function isValid()
48 | {
49 | if (!$this->expiration) {
50 | return false;
51 | }
52 |
53 | return (new \DateTime('now + 1 seconds')) < $this->expiration;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Manager/JwtManager.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class JwtManager
20 | {
21 | /**
22 | * $client Guzzle Client.
23 | *
24 | * @var ClientInterface
25 | */
26 | protected $client;
27 |
28 | /**
29 | * $auth Authentication Strategy.
30 | *
31 | * @var AuthStrategyInterface
32 | */
33 | protected $auth;
34 |
35 | /**
36 | * $options.
37 | *
38 | * @var array
39 | */
40 | protected $options;
41 |
42 | /**
43 | * $token.
44 | *
45 | * @var JwtToken
46 | */
47 | protected $token;
48 |
49 | /**
50 | * @var TokenPersistenceInterface
51 | */
52 | protected $tokenPersistence;
53 |
54 | /**
55 | * $propertyAccessor.
56 | *
57 | * @var PropertyAccessor
58 | */
59 | protected $propertyAccessor;
60 | /**
61 | * Constructor.
62 | *
63 | * @param ClientInterface $client
64 | * @param AuthStrategyInterface $auth
65 | * @param TokenPersistenceInterface $tokenPersistence
66 | * @param array $options
67 | */
68 | public function __construct(
69 | ClientInterface $client,
70 | AuthStrategyInterface $auth,
71 | ?TokenPersistenceInterface $tokenPersistence = null,
72 | array $options = []
73 | ) {
74 | $this->client = $client;
75 | $this->auth = $auth;
76 |
77 | if ($tokenPersistence === null) {
78 | $tokenPersistence = new NullTokenPersistence();
79 | }
80 | $this->tokenPersistence = $tokenPersistence;
81 |
82 | $resolver = new OptionsResolver();
83 | $resolver->setDefaults([
84 | 'token_url' => '/token',
85 | 'timeout' => 1,
86 | 'token_key' => 'token',
87 | 'expire_key' => 'expires_in',
88 | ]);
89 |
90 | $resolver->setRequired(['token_url', 'timeout']);
91 |
92 | $this->options = $resolver->resolve($options);
93 |
94 | $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
95 | }
96 |
97 | /**
98 | * getToken.
99 | *
100 | * @return JwtToken
101 | */
102 | public function getJwtToken()
103 | {
104 | // If token is not set try to get it from the persistent storage.
105 | if ($this->token === null) {
106 | $this->token = $this->tokenPersistence->restoreToken();
107 | }
108 |
109 | if ($this->token !== null && $this->token->isValid()) {
110 | return $this->token;
111 | }
112 |
113 | $this->tokenPersistence->deleteToken();
114 |
115 | $url = $this->options['token_url'];
116 |
117 | $requestOptions = array_merge(
118 | $this->getDefaultHeaders(),
119 | $this->auth->getRequestOptions()
120 | );
121 |
122 | $response = $this->client->request('POST', $url, $requestOptions);
123 | $body = json_decode($response->getBody());
124 |
125 | //Will be throw because it's mandatory
126 | $tokenValue = $this->propertyAccessor->getValue($body, $this->options['token_key']);
127 |
128 | try {
129 | $expiresIn = $this->propertyAccessor->getValue($body, $this->options['expire_key']);
130 | } catch (NoSuchPropertyException $e) {
131 | $expiresIn = null;
132 | }
133 |
134 | if ($expiresIn) {
135 | $expiration = new \DateTime('now + ' . $expiresIn . ' seconds');
136 | } elseif (count($jwtParts = explode('.', $tokenValue)) === 3
137 | && is_array($payload = json_decode(base64_decode($jwtParts[1]), true))
138 | // https://tools.ietf.org/html/rfc7519.html#section-4.1.4
139 | && array_key_exists('exp', $payload)
140 | ) {
141 | // Manually process the payload part to avoid having to drag in a new library
142 | $expiration = new \DateTime('@' . $payload['exp']);
143 | } else {
144 | $expiration = null;
145 | }
146 |
147 | $this->token = new JwtToken($tokenValue, $expiration);
148 | $this->tokenPersistence->saveToken($this->token);
149 |
150 | return $this->token;
151 | }
152 |
153 | /**
154 | * getHeaders. Return defaults header.
155 | *
156 | * @return array
157 | */
158 | private function getDefaultHeaders()
159 | {
160 | return [
161 | \GuzzleHttp\RequestOptions::HEADERS => [
162 | 'timeout' => $this->options['timeout'],
163 | ],
164 | ];
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/Persistence/NullTokenPersistence.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class NullTokenPersistence implements TokenPersistenceInterface
11 | {
12 | public function saveToken(JwtToken $token)
13 | {
14 | return;
15 | }
16 |
17 | public function restoreToken()
18 | {
19 | return null;
20 | }
21 |
22 | public function deleteToken()
23 | {
24 | return;
25 | }
26 |
27 | public function hasToken()
28 | {
29 | return false;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Persistence/SimpleCacheTokenPersistence.php:
--------------------------------------------------------------------------------
1 | cache = $cache;
33 | $this->ttl = $ttl;
34 | $this->cacheKey = $cacheKey;
35 | }
36 |
37 | /**
38 | * @inheritDoc
39 | */
40 | public function saveToken(JwtToken $token)
41 | {
42 | /*
43 | * TTL does not need to match token expiration,
44 | * it'll be revalidated by manager so we can safely
45 | * return a stale token.
46 | */
47 | $this->cache->set($this->cacheKey, $token, $this->ttl);
48 | return;
49 | }
50 |
51 | /**
52 | * @inheritDoc
53 | */
54 | public function restoreToken()
55 | {
56 | return $this->cache->get($this->cacheKey);
57 | }
58 |
59 | /**
60 | * @inheritDoc
61 | */
62 | public function deleteToken()
63 | {
64 | $this->cache->delete($this->cacheKey);
65 | return;
66 | }
67 |
68 | /**
69 | * @inheritDoc
70 | */
71 | public function hasToken()
72 | {
73 | return $this->cache->has($this->cacheKey);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Persistence/TokenPersistenceInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface TokenPersistenceInterface
11 | {
12 | /**
13 | * Restore the token data into the give token.
14 | *
15 | * @return JwtToken Restored token
16 | */
17 | public function restoreToken();
18 |
19 | /**
20 | * Save the token data.
21 | *
22 | * @param JwtToken $token
23 | */
24 | public function saveToken(JwtToken $token);
25 |
26 | /**
27 | * Delete the saved token data.
28 | */
29 | public function deleteToken();
30 |
31 | /**
32 | * Returns true if a token exists (although it may not be valid)
33 | *
34 | * @return bool
35 | */
36 | public function hasToken();
37 | }
38 |
--------------------------------------------------------------------------------
/src/Strategy/Auth/AbstractBaseAuthStrategy.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | abstract class AbstractBaseAuthStrategy implements AuthStrategyInterface
11 | {
12 | /**
13 | * $options.
14 | *
15 | * @var array
16 | */
17 | protected $options;
18 |
19 | /**
20 | * Constructor.
21 | *
22 | * @param array $options
23 | */
24 | public function __construct(array $options = array())
25 | {
26 | $resolver = new OptionsResolver();
27 | $this->configureOptions($resolver);
28 |
29 | $this->options = $resolver->resolve($options);
30 | }
31 |
32 | /**
33 | * configureOptions.
34 | *
35 | * @param OptionsResolver $resolver
36 | */
37 | public function configureOptions(OptionsResolver $resolver)
38 | {
39 | $resolver->setDefaults(array(
40 | 'username' => '',
41 | 'password' => '',
42 | ));
43 |
44 | $resolver->setRequired(['username', 'password']);
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | abstract public function getRequestOptions();
51 | }
52 |
--------------------------------------------------------------------------------
/src/Strategy/Auth/AuthStrategyInterface.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | interface AuthStrategyInterface
9 | {
10 | /**
11 | * getRequestOptions.
12 | *
13 | * @return array
14 | */
15 | public function getRequestOptions();
16 | }
17 |
--------------------------------------------------------------------------------
/src/Strategy/Auth/FormAuthStrategy.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class FormAuthStrategy extends AbstractBaseAuthStrategy
11 | {
12 | /**
13 | * {@inheritdoc}
14 | */
15 | public function configureOptions(OptionsResolver $resolver)
16 | {
17 | parent::configureOptions($resolver);
18 |
19 | $resolver->setDefaults([
20 | 'form_fields' => ['_username', '_password'],
21 | ]);
22 |
23 | $resolver->setRequired(['form_fields']);
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function getRequestOptions()
30 | {
31 | return [
32 | \GuzzleHttp\RequestOptions::FORM_PARAMS => [
33 | $this->options['form_fields'][0] => $this->options['username'],
34 | $this->options['form_fields'][1] => $this->options['password'],
35 | ],
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Strategy/Auth/HttpBasicAuthStrategy.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | class HttpBasicAuthStrategy extends AbstractBaseAuthStrategy
9 | {
10 | /**
11 | * {@inheritdoc}
12 | */
13 | public function getRequestOptions()
14 | {
15 | return [
16 | \GuzzleHttp\RequestOptions::AUTH => [
17 | $this->options['username'],
18 | $this->options['password'],
19 | ],
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Strategy/Auth/JsonAuthStrategy.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class JsonAuthStrategy extends AbstractBaseAuthStrategy
11 | {
12 | /**
13 | * {@inheritdoc}
14 | */
15 | public function configureOptions(OptionsResolver $resolver)
16 | {
17 | parent::configureOptions($resolver);
18 |
19 | $resolver->setDefaults([
20 | 'json_fields' => ['_username', '_password'],
21 | ]);
22 |
23 | $resolver->setRequired(['json_fields']);
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function getRequestOptions()
30 | {
31 | return [
32 | \GuzzleHttp\RequestOptions::JSON => [
33 | $this->options['json_fields'][0] => $this->options['username'],
34 | $this->options['json_fields'][1] => $this->options['password'],
35 | ],
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Strategy/Auth/QueryAuthStrategy.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class QueryAuthStrategy extends AbstractBaseAuthStrategy
11 | {
12 | /**
13 | * {@inheritdoc}
14 | */
15 | public function configureOptions(OptionsResolver $resolver)
16 | {
17 | parent::configureOptions($resolver);
18 |
19 | $resolver->setDefaults([
20 | 'query_fields' => ['username', 'password'],
21 | ]);
22 |
23 | $resolver->setRequired(['query_fields']);
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function getRequestOptions()
30 | {
31 | return [
32 | \GuzzleHttp\RequestOptions::QUERY => [
33 | $this->options['query_fields'][0] => $this->options['username'],
34 | $this->options['query_fields'][1] => $this->options['password'],
35 | ],
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/JwtMiddlewareTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class JwtMiddlewareTest extends \PHPUnit\Framework\TestCase
18 | {
19 | /**
20 | * testJwtAuthorizationHeader.
21 | */
22 | public function testJwtAuthorizationHeader()
23 | {
24 | $authMockHandler = new MockHandler([
25 | new Response(
26 | 200,
27 | ['Content-Type' => 'application/json'],
28 | json_encode(['token' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'])
29 | ),
30 | ]);
31 |
32 | $authClient = new Client(['handler' => $authMockHandler]);
33 | $jwtManager = new JwtManager(
34 | $authClient,
35 | (new HttpBasicAuthStrategy(['username' => 'test', 'password' => 'test']))
36 | );
37 |
38 | $mockHandler = new MockHandler([
39 | function (RequestInterface $request) {
40 | $this->assertTrue($request->hasHeader('Authorization'));
41 | $this->assertSame(
42 | 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
43 | $request->getHeader('Authorization')[0]
44 | );
45 |
46 | return new Response(200, [], json_encode(['data' => 'pong']));
47 | },
48 | ]);
49 |
50 | $handler = HandlerStack::create($mockHandler);
51 | $handler->push(new JwtMiddleware($jwtManager));
52 |
53 | $client = new Client(['handler' => $handler]);
54 | $client->get('http://api.example.com/api/ping');
55 | }
56 |
57 | /**
58 | * testJwtAuthorizationHeaderType.
59 | */
60 | public function testJwtAuthorizationHeaderType()
61 | {
62 | $authMockHandler = new MockHandler([
63 | new Response(
64 | 200,
65 | ['Content-Type' => 'application/json'],
66 | json_encode(['token' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'])
67 | ),
68 | ]);
69 |
70 | $authClient = new Client(['handler' => $authMockHandler]);
71 | $jwtManager = new JwtManager(
72 | $authClient,
73 | (new HttpBasicAuthStrategy(['username' => 'test', 'password' => 'test']))
74 | );
75 |
76 | $mockHandler = new MockHandler([
77 | function (RequestInterface $request) {
78 | $this->assertTrue($request->hasHeader('Authorization'));
79 | $this->assertSame(
80 | 'JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
81 | $request->getHeader('Authorization')[0]
82 | );
83 |
84 | return new Response(200, [], json_encode(['data' => 'pong']));
85 | },
86 | ]);
87 |
88 | $handler = HandlerStack::create($mockHandler);
89 | $handler->push(new JwtMiddleware($jwtManager, 'JWT'));
90 |
91 | $client = new Client(['handler' => $handler]);
92 | $client->get('http://api.example.com/api/ping');
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/JwtTokenTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($token->isValid());
14 | }
15 |
16 | public function testTokenShouldNotBeValidIfExpirationIsNow()
17 | {
18 | $token = new JwtToken('foo', new \DateTime('now'));
19 |
20 | $this->assertFalse($token->isValid());
21 | }
22 |
23 | public function testTokenShouldBeValidIfExpirationIsInTheFuture()
24 | {
25 | $token = new JwtToken('foo', new \DateTime('now + 5 minutes'));
26 |
27 | $this->assertTrue($token->isValid());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Manager/JwtManagerTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class JwtManagerTest extends \PHPUnit\Framework\TestCase
18 | {
19 |
20 | /**
21 | * testGetTokenExpiredKeyException
22 | */
23 | public function testGetTokenExpiredKeyException()
24 | {
25 | $mockHandler = new MockHandler([
26 | function (RequestInterface $request) {
27 |
28 | $this->assertTrue($request->hasHeader('timeout'));
29 | $this->assertEquals(
30 | 3,
31 | $request->getHeaderLine('timeout')
32 | );
33 |
34 | $jsonPayload = << 'application/json'],
48 | $jsonPayload
49 | );
50 | },
51 | ]);
52 |
53 | $handler = HandlerStack::create($mockHandler);
54 |
55 | $authClient = new Client([
56 | 'handler' => $handler,
57 | ]);
58 |
59 | $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']);
60 |
61 | $jwtManager = new JwtManager(
62 | $authClient,
63 | $authStrategy,
64 | null,
65 | [
66 | 'token_url' => '/api/token',
67 | 'timeout' => 3,
68 | 'token_key' => 'token',
69 | 'expire_key' => 'expires_in'
70 | ]
71 | );
72 |
73 | $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
74 |
75 | $token = $jwtManager->getJwtToken();
76 | }
77 |
78 | /**
79 | * testGetTokenWithSublevelResponse
80 | */
81 | public function testGetTokenWithSublevelResponse()
82 | {
83 | $mockHandler = new MockHandler([
84 | function (RequestInterface $request) {
85 |
86 | $this->assertTrue($request->hasHeader('timeout'));
87 | $this->assertEquals(
88 | 3,
89 | $request->getHeaderLine('timeout')
90 | );
91 |
92 | $jsonPayload = << 'application/json'],
106 | $jsonPayload
107 | );
108 | },
109 | ]);
110 |
111 | $handler = HandlerStack::create($mockHandler);
112 |
113 | $authClient = new Client([
114 | 'handler' => $handler,
115 | ]);
116 |
117 | $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']);
118 |
119 | $jwtManager = new JwtManager(
120 | $authClient,
121 | $authStrategy,
122 | null,
123 | [
124 | 'token_url' => '/api/token',
125 | 'timeout' => 3,
126 | 'token_key' => 'payload.token',
127 | 'expire_key' => 'expires_in'
128 | ]
129 | );
130 | $token = $jwtManager->getJwtToken();
131 |
132 | $this->assertInstanceOf(JwtToken::class, $token);
133 | $this->assertEquals('1453720507', $token->getToken());
134 | }
135 |
136 | /**
137 | * testGetToken.
138 | */
139 | public function testGetToken()
140 | {
141 | $mockHandler = new MockHandler([
142 | function (RequestInterface $request) {
143 |
144 | $this->assertTrue($request->hasHeader('timeout'));
145 | $this->assertEquals(
146 | 3,
147 | $request->getHeaderLine('timeout')
148 | );
149 |
150 | return new Response(
151 | 200,
152 | ['Content-Type' => 'application/json'],
153 | json_encode(['token' => '1453720507', 'expires_in' => 3600])
154 | );
155 | },
156 | ]);
157 |
158 | $handler = HandlerStack::create($mockHandler);
159 |
160 | $authClient = new Client([
161 | 'handler' => $handler,
162 | ]);
163 |
164 | $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']);
165 |
166 | $jwtManager = new JwtManager(
167 | $authClient,
168 | $authStrategy,
169 | null,
170 | ['token_url' => '/api/token', 'timeout' => 3]
171 | );
172 | $token = $jwtManager->getJwtToken();
173 |
174 | $this->assertInstanceOf(JwtToken::class, $token);
175 | $this->assertEquals('1453720507', $token->getToken());
176 | }
177 |
178 | public function testGetTokenWithTokenKeyOption()
179 | {
180 | $mockHandler = new MockHandler([
181 | function (RequestInterface $request) {
182 |
183 | $this->assertTrue($request->hasHeader('timeout'));
184 | $this->assertEquals(
185 | 3,
186 | $request->getHeaderLine('timeout')
187 | );
188 |
189 | return new Response(
190 | 200,
191 | ['Content-Type' => 'application/json'],
192 | json_encode(['tokenkey' => '1453720507'])
193 | );
194 | },
195 | ]);
196 |
197 | $handler = HandlerStack::create($mockHandler);
198 |
199 | $authClient = new Client([
200 | 'handler' => $handler,
201 | ]);
202 |
203 | $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']);
204 |
205 | $jwtManager = new JwtManager(
206 | $authClient,
207 | $authStrategy,
208 | null,
209 | ['token_url' => '/api/token', 'timeout' => 3, 'token_key' => 'tokenkey']
210 | );
211 | $token = $jwtManager->getJwtToken();
212 |
213 | $this->assertInstanceOf(JwtToken::class, $token);
214 | $this->assertEquals('1453720507', $token->getToken());
215 | }
216 |
217 | public function testGetTokenShouldGetNewTokenIfCachedTokenIsNotValid()
218 | {
219 | $mockHandler = new MockHandler(
220 | [
221 | function (RequestInterface $request) {
222 |
223 | $this->assertTrue($request->hasHeader('timeout'));
224 | $this->assertEquals(
225 | 3,
226 | $request->getHeaderLine('timeout')
227 | );
228 |
229 | return new Response(
230 | 200,
231 | ['Content-Type' => 'application/json'],
232 | json_encode(['token' => '1453720507'])
233 | );
234 | },
235 | function (RequestInterface $request) {
236 |
237 | $this->assertTrue($request->hasHeader('timeout'));
238 | $this->assertEquals(
239 | 3,
240 | $request->getHeaderLine('timeout')
241 | );
242 |
243 | return new Response(
244 | 200,
245 | ['Content-Type' => 'application/json'],
246 | json_encode(['token' => 'foo123'])
247 | );
248 | },
249 | ]
250 | );
251 |
252 | $handler = HandlerStack::create($mockHandler);
253 |
254 | $authClient = new Client([
255 | 'handler' => $handler,
256 | ]);
257 |
258 | $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']);
259 |
260 | $jwtManager = new JwtManager(
261 | $authClient,
262 | $authStrategy,
263 | null,
264 | ['token_url' => '/api/token', 'timeout' => 3]
265 | );
266 | $token = $jwtManager->getJwtToken();
267 |
268 | $this->assertInstanceOf(JwtToken::class, $token);
269 | $this->assertEquals('1453720507', $token->getToken());
270 |
271 | $token = $jwtManager->getJwtToken();
272 |
273 | $this->assertInstanceOf(JwtToken::class, $token);
274 | $this->assertEquals('foo123', $token->getToken());
275 | }
276 |
277 | public function testGetTokenShouldUseTheCachedTokenIfItIsValid()
278 | {
279 | $mockHandler = new MockHandler(
280 | [
281 | function (RequestInterface $request) {
282 |
283 | $this->assertTrue($request->hasHeader('timeout'));
284 | $this->assertEquals(
285 | 3,
286 | $request->getHeaderLine('timeout')
287 | );
288 |
289 | return new Response(
290 | 200,
291 | ['Content-Type' => 'application/json'],
292 | json_encode(['token' => '1453720507', 'expires_in' => 3600])
293 | );
294 | },
295 | function (RequestInterface $request) {
296 |
297 | $this->assertTrue($request->hasHeader('timeout'));
298 | $this->assertEquals(
299 | 3,
300 | $request->getHeaderLine('timeout')
301 | );
302 |
303 | return new Response(
304 | 200,
305 | ['Content-Type' => 'application/json'],
306 | json_encode(['token' => 'foo123'])
307 | );
308 | },
309 | ]
310 | );
311 |
312 | $handler = HandlerStack::create($mockHandler);
313 |
314 | $authClient = new Client([
315 | 'handler' => $handler,
316 | ]);
317 |
318 | $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']);
319 |
320 | $jwtManager = new JwtManager(
321 | $authClient,
322 | $authStrategy,
323 | null,
324 | ['token_url' => '/api/token', 'timeout' => 3]
325 | );
326 | $token = $jwtManager->getJwtToken();
327 |
328 | $this->assertInstanceOf(JwtToken::class, $token);
329 | $this->assertEquals('1453720507', $token->getToken());
330 |
331 | $token = $jwtManager->getJwtToken();
332 |
333 | $this->assertInstanceOf(JwtToken::class, $token);
334 | $this->assertEquals('1453720507', $token->getToken());
335 | }
336 |
337 | public function testGetTokenShouldUseTheCachedTokenIfItIsValidBasedOnExpField()
338 | {
339 | $jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
340 | . '.eyJleHAiOiIzMjUwMzY4MDAwMCJ9'
341 | . '.k4YJmJooaa9B4pAM_U8Pi-4ss6RdKFtj9iQqLIAndVA';
342 |
343 | $mockHandler = new MockHandler(
344 | [
345 | function (RequestInterface $request) use ($jwtToken) {
346 |
347 | $this->assertTrue($request->hasHeader('timeout'));
348 | $this->assertEquals(
349 | 3,
350 | $request->getHeaderLine('timeout')
351 | );
352 |
353 | return new Response(
354 | 200,
355 | ['Content-Type' => 'application/json'],
356 | json_encode(['token' => $jwtToken])
357 | );
358 | },
359 | function (RequestInterface $request) {
360 |
361 | $this->assertTrue($request->hasHeader('timeout'));
362 | $this->assertEquals(
363 | 3,
364 | $request->getHeaderLine('timeout')
365 | );
366 |
367 | return new Response(
368 | 200,
369 | ['Content-Type' => 'application/json'],
370 | json_encode(['token' => uniqid('token', true)])
371 | );
372 | },
373 | ]
374 | );
375 |
376 | $handler = HandlerStack::create($mockHandler);
377 |
378 | $authClient = new Client([
379 | 'handler' => $handler,
380 | ]);
381 |
382 | $authStrategy = new QueryAuthStrategy(['username' => 'admin', 'password' => 'admin']);
383 |
384 | $jwtManager = new JwtManager(
385 | $authClient,
386 | $authStrategy,
387 | null,
388 | ['token_url' => '/api/token', 'timeout' => 3]
389 | );
390 | $token = $jwtManager->getJwtToken();
391 |
392 | $this->assertInstanceOf(JwtToken::class, $token);
393 | $this->assertEquals($jwtToken, $token->getToken());
394 |
395 | $token = $jwtManager->getJwtToken();
396 |
397 | $this->assertInstanceOf(JwtToken::class, $token);
398 | $this->assertEquals($jwtToken, $token->getToken());
399 | }
400 | }
401 |
--------------------------------------------------------------------------------
/tests/Persistence/TokenPersistenceTest.php:
--------------------------------------------------------------------------------
1 | saveToken($token);
25 |
26 | $this->assertFalse($tokenPersistence->hasToken());
27 | $this->assertNull($tokenPersistence->restoreToken());
28 | }
29 |
30 | /**
31 | * testSimpleCacheTokenPersistenceInterface.
32 | * Makes sure we only use the interface methods.
33 | */
34 | public function testSimpleCacheTokenPersistenceInterface()
35 | {
36 | $simpleCache = $this->createMock(CacheInterface::class);
37 | $tokenPersistence = new SimpleCacheTokenPersistence($simpleCache);
38 | $token = new JwtToken('foo', new \DateTime('now'));
39 |
40 | $this->assertNull($tokenPersistence->saveToken($token));
41 | $this->assertNull($tokenPersistence->hasToken());
42 | $this->assertNull($tokenPersistence->restoreToken());
43 | $this->assertNull($tokenPersistence->deleteToken());
44 | }
45 |
46 | /**
47 | * testSimpleCacheTokenPersistence.
48 | */
49 | public function testSimpleCacheTokenPersistence()
50 | {
51 | $simpleCache = new MockCache();
52 | $tokenPersistence = new SimpleCacheTokenPersistence($simpleCache);
53 | $token = new JwtToken('foo', new \DateTime('now'));
54 |
55 | $tokenPersistence->saveToken($token);
56 |
57 | $this->assertTrue($tokenPersistence->hasToken());
58 | $this->assertEquals($tokenPersistence->restoreToken()->getToken(), $token->getToken());
59 |
60 | $tokenPersistence->deleteToken();
61 |
62 | $this->assertFalse($tokenPersistence->hasToken());
63 | $this->assertNull($tokenPersistence->restoreToken());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Strategy/Auth/AuthStrategyTest.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class AuthStrategyTest extends \PHPUnit\Framework\TestCase
14 | {
15 | /**
16 | * testFormAuthStrategy.
17 | */
18 | public function testFormAuthStrategy()
19 | {
20 | $authStrategy = new FormAuthStrategy(
21 | [
22 | 'username' => 'admin',
23 | 'password' => 'admin',
24 | 'form_fields' => ['login', 'password'],
25 | ]
26 | );
27 |
28 | $this->assertTrue(array_key_exists('login', $authStrategy->getRequestOptions()['form_params']));
29 |
30 | $this->assertTrue(array_key_exists('password', $authStrategy->getRequestOptions()['form_params']));
31 |
32 | $this->assertEquals('admin', $authStrategy->getRequestOptions()['form_params']['login']);
33 | $this->assertEquals('admin', $authStrategy->getRequestOptions()['form_params']['password']);
34 | }
35 |
36 | /**
37 | * tesQueryAuthStrategy.
38 | */
39 | public function testQueryAuthStrategy()
40 | {
41 | $authStrategy = new QueryAuthStrategy(
42 | [
43 | 'username' => 'admin',
44 | 'password' => 'admin',
45 | 'query_fields' => ['username', 'password'],
46 | ]
47 | );
48 |
49 | $this->assertTrue(array_key_exists('username', $authStrategy->getRequestOptions()['query']));
50 | $this->assertTrue(array_key_exists('password', $authStrategy->getRequestOptions()['query']));
51 |
52 | $this->assertEquals('admin', $authStrategy->getRequestOptions()['query']['username']);
53 | $this->assertEquals('admin', $authStrategy->getRequestOptions()['query']['password']);
54 | }
55 |
56 | /**
57 | * testHttpBasicAuthStrategy.
58 | */
59 | public function testHttpBasicAuthStrategy()
60 | {
61 | $authStrategy = new HttpBasicAuthStrategy(
62 | [
63 | 'username' => 'admin',
64 | 'password' => 'password',
65 | ]
66 | );
67 |
68 | $this->assertEquals('admin', $authStrategy->getRequestOptions()['auth'][0]);
69 | $this->assertEquals('password', $authStrategy->getRequestOptions()['auth'][1]);
70 | }
71 |
72 | /**
73 | * testJsonAuthStrategy.
74 | */
75 | public function testJsonAuthStrategy()
76 | {
77 | $authStrategy = new JsonAuthStrategy(
78 | [
79 | 'username' => 'admin',
80 | 'password' => 'admin',
81 | 'json_fields' => ['login', 'password'],
82 | ]
83 | );
84 |
85 | $this->assertTrue(array_key_exists('login', $authStrategy->getRequestOptions()['json']));
86 |
87 | $this->assertTrue(array_key_exists('password', $authStrategy->getRequestOptions()['json']));
88 |
89 | $this->assertEquals('admin', $authStrategy->getRequestOptions()['json']['login']);
90 | $this->assertEquals('admin', $authStrategy->getRequestOptions()['json']['password']);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------