├── .phpunit-watcher.yml
├── .styleci.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── config
├── di.php
└── params.php
├── infection.json.dist
├── psalm.xml
├── rector.php
└── src
├── JwtMethod.php
├── KeyFactory
├── FromCertificateFile.php
├── FromKeyFile.php
├── FromPKCS12CertificateFile.php
└── FromSecret.php
├── KeyFactoryInterface.php
├── TokenFactory.php
├── TokenFactoryInterface.php
├── TokenRepository.php
└── TokenRepositoryInterface.php
/.phpunit-watcher.yml:
--------------------------------------------------------------------------------
1 | watch:
2 | directories:
3 | - src
4 | - tests
5 | fileMask: '*.php'
6 | notifications:
7 | passingTests: false
8 | failingTests: false
9 | phpunit:
10 | binaryPath: vendor/bin/phpunit
11 | timeout: 180
12 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: psr12
2 | risky: true
3 |
4 | version: 8.1
5 |
6 | finder:
7 | exclude:
8 | - docs
9 | - vendor
10 |
11 | enabled:
12 | - alpha_ordered_traits
13 | - array_indentation
14 | - array_push
15 | - combine_consecutive_issets
16 | - combine_consecutive_unsets
17 | - combine_nested_dirname
18 | - declare_strict_types
19 | - dir_constant
20 | - fully_qualified_strict_types
21 | - function_to_constant
22 | - hash_to_slash_comment
23 | - is_null
24 | - logical_operators
25 | - magic_constant_casing
26 | - magic_method_casing
27 | - method_separation
28 | - modernize_types_casting
29 | - native_function_casing
30 | - native_function_type_declaration_casing
31 | - no_alias_functions
32 | - no_empty_comment
33 | - no_empty_phpdoc
34 | - no_empty_statement
35 | - no_extra_block_blank_lines
36 | - no_short_bool_cast
37 | - no_superfluous_elseif
38 | - no_unneeded_control_parentheses
39 | - no_unneeded_curly_braces
40 | - no_unneeded_final_method
41 | - no_unset_cast
42 | - no_unused_imports
43 | - no_unused_lambda_imports
44 | - no_useless_else
45 | - no_useless_return
46 | - normalize_index_brace
47 | - php_unit_dedicate_assert
48 | - php_unit_dedicate_assert_internal_type
49 | - php_unit_expectation
50 | - php_unit_mock
51 | - php_unit_mock_short_will_return
52 | - php_unit_namespaced
53 | - php_unit_no_expectation_annotation
54 | - phpdoc_no_empty_return
55 | - phpdoc_no_useless_inheritdoc
56 | - phpdoc_order
57 | - phpdoc_property
58 | - phpdoc_scalar
59 | - phpdoc_singular_inheritdoc
60 | - phpdoc_trim
61 | - phpdoc_trim_consecutive_blank_line_separation
62 | - phpdoc_type_to_var
63 | - phpdoc_types
64 | - phpdoc_types_order
65 | - print_to_echo
66 | - regular_callable_call
67 | - return_assignment
68 | - self_accessor
69 | - self_static_accessor
70 | - set_type_to_cast
71 | - short_array_syntax
72 | - short_list_syntax
73 | - simplified_if_return
74 | - single_quote
75 | - standardize_not_equals
76 | - ternary_to_null_coalescing
77 | - trailing_comma_in_multiline_array
78 | - unalign_double_arrow
79 | - unalign_equals
80 | - empty_loop_body_braces
81 | - integer_literal_case
82 | - union_type_without_spaces
83 |
84 | disabled:
85 | - function_declaration
86 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Yii Auth JWT Change Log
2 |
3 | ## 2.1.1 under development
4 |
5 | - no changes in this release.
6 |
7 | ## 2.1.0 August 20, 2024
8 |
9 | - Chg #82: Replace `web-token/*` with `web-token/jwt-library` and update min version of PHP to 8.1 (@rustamwin)
10 | - Enh #75: Add support for `psr/http-message` version `^2.0` (@vjik)
11 |
12 | ## 2.0.1 September 19, 2023
13 |
14 | - Bug #53: Add missed dependencies `psr/http-message`, `yiisoft/http` and `web-token/jwt-core` (@vjik)
15 |
16 | ## 2.0.0 February 13, 2023
17 |
18 | - Chg #59: Adapt configuration group names to Yii conventions (@vjik)
19 |
20 | ## 1.0.3 July 27, 2021
21 |
22 | - Fix #43: Added exception handling in `TokenRepository::getClaims()` with invalid token string (@strorch)
23 |
24 | ## 1.0.2 April 13, 2021
25 |
26 | - Chg: Adjust config for `yiisoft/factory` changes (@samdark)
27 |
28 | ## 1.0.1 March 23, 2021
29 |
30 | - Chg: Adjust config for new config plugin (@samdark)
31 |
32 | ## 1.0.0 February 11, 2021
33 |
34 | - Initial release.
35 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2008 by Yii Software ()
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of Yii Software nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Yii Auth JWT
6 |
7 |
8 |
9 | [](https://packagist.org/packages/yiisoft/auth-jwt)
10 | [](https://packagist.org/packages/yiisoft/auth-jwt)
11 | [](https://github.com/yiisoft/auth-jwt/actions/workflows/build.yml)
12 | [](https://codecov.io/gh/yiisoft/auth-jwt)
13 | [](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/auth-jwt/master)
14 | [](https://github.com/yiisoft/auth-jwt/actions?query=workflow%3A%22static+analysis%22)
15 | [](https://shepherd.dev/github/yiisoft/auth-jwt)
16 | [](https://shepherd.dev/github/yiisoft/auth-jwt)
17 |
18 | The package provides [JWT authentication](https://tools.ietf.org/html/rfc7519) method for [Yii Auth](https://github.com/yiisoft/auth/).
19 |
20 | ## Requirements
21 |
22 | - PHP 8.1 or higher.
23 |
24 | ## Installation
25 |
26 | The package could be installed with [Composer](https://getcomposer.org):
27 |
28 | ```shell
29 | composer require yiisoft/auth-jwt
30 | ```
31 |
32 | ## General usage
33 |
34 | ### Configuring within Yii
35 |
36 | 1. Set JWT parameters in your `params.php` config file:
37 |
38 | ```php
39 | 'yiisoft/auth-jwt' => [
40 | 'algorithms' => [
41 | // your signature algorithms
42 | ],
43 | 'serializers' => [
44 | // your token serializers
45 | ],
46 | 'key' => [
47 | 'secret' => 'your-secret',
48 | 'file' => 'your-certificate-file',
49 | ],
50 | ],
51 | ```
52 |
53 | 2. Setup definitions, required for `\Yiisoft\Auth\Middleware\Authentication` middleware in a config, for example,
54 | in `config/web/auth.php`:
55 |
56 | ```php
57 | /** @var array $params */
58 |
59 | use Yiisoft\Auth\Jwt\TokenManagerInterface;
60 | use Yiisoft\Auth\Jwt\TokenManager;
61 | use Yiisoft\Auth\AuthenticationMethodInterface;
62 | use Yiisoft\Auth\Jwt\JwtMethod;
63 |
64 | return [
65 | KeyFactoryInterface::class => [
66 | 'class' => FromSecret::class,
67 | '__construct()' => [
68 | $params['yiisoft/auth-jwt']['key']['secret']
69 | ],
70 | ],
71 |
72 | AuthenticationMethodInterface::class => JwtMethod::class,
73 | ];
74 | ```
75 |
76 | > Note: Don't forget to declare your implementations of `\Yiisoft\Auth\IdentityInterface` and `\Yiisoft\Auth\IdentityRepositoryInterface`.
77 |
78 | 3. Use `Yiisoft\Auth\Middleware\Authentication` middleware.
79 | Read more about middlewares in the [middleware guide](https://github.com/yiisoft/docs/blob/master/guide/en/structure/middleware.md).
80 |
81 | ### Configuring independently
82 |
83 | You can configure `Authentication` middleware manually:
84 |
85 | ```php
86 | /** @var \Yiisoft\Auth\IdentityRepositoryInterface $identityRepository */
87 | $identityRepository = getIdentityRepository();
88 |
89 | $tokenRepository = $container->get(\Yiisoft\Auth\Jwt\TokenRepositoryInterface::class);
90 |
91 | $authenticationMethod = new \Yiisoft\Auth\Jwt\JwtMethod($identityRepository, $tokenRepository);
92 |
93 | $middleware = new \Yiisoft\Auth\Middleware\Authentication(
94 | $authenticationMethod,
95 | $responseFactory, // PSR-17 ResponseFactoryInterface.
96 | $failureHandler // Optional, \Yiisoft\Auth\Handler\AuthenticationFailureHandler by default.
97 | );
98 | ```
99 |
100 | ## Documentation
101 |
102 | - [Internals](docs/internals.md)
103 |
104 | If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for
105 | that. You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).
106 |
107 | ## License
108 |
109 | The Yii Auth JWT is free software. It is released under the terms of the BSD License.
110 | Please see [`LICENSE`](./LICENSE.md) for more information.
111 |
112 | Maintained by [Yii Software](https://www.yiiframework.com/).
113 |
114 | ## Support the project
115 |
116 | [](https://opencollective.com/yiisoft)
117 |
118 | ## Follow updates
119 |
120 | [](https://www.yiiframework.com/)
121 | [](https://twitter.com/yiiframework)
122 | [](https://t.me/yii3en)
123 | [](https://www.facebook.com/groups/yiitalk)
124 | [](https://yiiframework.com/go/slack)
125 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yiisoft/auth-jwt",
3 | "type": "library",
4 | "description": "Yii auth JWT method",
5 | "keywords": [
6 | "auth",
7 | "jwt",
8 | "middleware",
9 | "psr-15"
10 | ],
11 | "homepage": "https://www.yiiframework.com/",
12 | "license": "BSD-3-Clause",
13 | "support": {
14 | "issues": "https://github.com/yiisoft/auth-jwt/issues?state=open",
15 | "source": "https://github.com/yiisoft/auth-jwt",
16 | "forum": "https://www.yiiframework.com/forum/",
17 | "wiki": "https://www.yiiframework.com/wiki/",
18 | "irc": "ircs://irc.libera.chat:6697/yii",
19 | "chat": "https://t.me/yii3en"
20 | },
21 | "funding": [
22 | {
23 | "type": "opencollective",
24 | "url": "https://opencollective.com/yiisoft"
25 | },
26 | {
27 | "type": "github",
28 | "url": "https://github.com/sponsors/yiisoft"
29 | }
30 | ],
31 | "require": {
32 | "php": "^8.1",
33 | "psr/http-message": "^1.0|^2.0",
34 | "web-token/jwt-library": "^3.3",
35 | "yiisoft/auth": "^3.0",
36 | "yiisoft/http": "^1.2",
37 | "yiisoft/json": "^1.0"
38 | },
39 | "require-dev": {
40 | "maglnet/composer-require-checker": "^3.8|^4.2",
41 | "nyholm/psr7": "^1.3",
42 | "phpunit/phpunit": "^9.5",
43 | "rector/rector": "^2.0.0",
44 | "roave/infection-static-analysis-plugin": "^1.16",
45 | "spatie/phpunit-watcher": "^1.23",
46 | "vimeo/psalm": "^4.30|^5.21",
47 | "yiisoft/di": "^1.1",
48 | "yiisoft/injector": "^1.1"
49 | },
50 | "autoload": {
51 | "psr-4": {
52 | "Yiisoft\\Auth\\Jwt\\": "src"
53 | }
54 | },
55 | "autoload-dev": {
56 | "psr-4": {
57 | "Yiisoft\\Auth\\Jwt\\Tests\\": "tests"
58 | }
59 | },
60 | "extra": {
61 | "config-plugin-options": {
62 | "source-directory": "config"
63 | },
64 | "config-plugin": {
65 | "di": "di.php",
66 | "params": "params.php"
67 | }
68 | },
69 | "config": {
70 | "sort-packages": true,
71 | "allow-plugins": {
72 | "infection/extension-installer": true,
73 | "composer/package-versions-deprecated": true
74 | }
75 | },
76 | "scripts": {
77 | "test": "phpunit --testdox --no-interaction",
78 | "test-watch": "phpunit-watcher watch"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/config/di.php:
--------------------------------------------------------------------------------
1 | [
20 | 'class' => FromSecret::class,
21 | '__construct()' => [$params['yiisoft/auth-jwt']['key']['secret']],
22 | ],
23 | AlgorithmManager::class => static function (Injector $injector) use ($params) {
24 | $algorithms = array_map(
25 | static fn ($algorithm) => is_string($algorithm) ? $injector->make($algorithm) : $algorithm,
26 | $params['yiisoft/auth-jwt']['algorithms'] ?? []
27 | );
28 | return $injector->make(AlgorithmManager::class, ['algorithms' => $algorithms]);
29 | },
30 | JWSSerializerManager::class => static function (Injector $injector) use ($params) {
31 | $serializers = array_map(
32 | static fn ($serializer) => is_string($serializer) ? $injector->make($serializer) : $serializer,
33 | $params['yiisoft/auth-jwt']['serializers'] ?? []
34 | );
35 | return $injector->make(JWSSerializerManager::class, ['serializers' => $serializers]);
36 | },
37 | TokenFactoryInterface::class => TokenFactory::class,
38 | TokenRepositoryInterface::class => TokenRepository::class,
39 | ];
40 |
--------------------------------------------------------------------------------
/config/params.php:
--------------------------------------------------------------------------------
1 | [
10 | 'algorithms' => [
11 | HS256::class,
12 | ],
13 | 'serializers' => [
14 | CompactSerializer::class,
15 | ],
16 | 'key' => [
17 | 'secret' => '',
18 | 'file' => '',
19 | 'password' => '',
20 | ],
21 | ],
22 | ];
23 |
--------------------------------------------------------------------------------
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "directories": [
4 | "src"
5 | ]
6 | },
7 | "logs": {
8 | "text": "php:\/\/stderr",
9 | "stryker": {
10 | "report": "master"
11 | }
12 | },
13 | "mutators": {
14 | "@default": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | paths([
11 | __DIR__ . '/src',
12 | __DIR__ . '/tests',
13 | ]);
14 |
15 | // register a single rule
16 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
17 |
18 | // define sets of rules
19 | $rectorConfig->sets([
20 | LevelSetList::UP_TO_PHP_81,
21 | ]);
22 | };
23 |
--------------------------------------------------------------------------------
/src/JwtMethod.php:
--------------------------------------------------------------------------------
1 | claimCheckers = $claimCheckers ?? [new ExpirationTimeChecker()];
55 | }
56 |
57 | public function authenticate(ServerRequestInterface $request): ?IdentityInterface
58 | {
59 | $token = $this->getAuthenticationToken($request);
60 | if ($token === null) {
61 | return null;
62 | }
63 |
64 | $claims = $this->tokenRepository->getClaims($token, $name);
65 | if ($claims === null || !isset($claims[$this->identifier])) {
66 | return null;
67 | }
68 |
69 | $this
70 | ->getClaimCheckerManager()
71 | ->check($claims);
72 | return $this->identityRepository->findIdentity((string)$claims[$this->identifier]);
73 | }
74 |
75 | private function getAuthenticationToken(ServerRequestInterface $request): ?string
76 | {
77 | $authHeaders = $request->getHeader($this->headerName);
78 | $authHeader = reset($authHeaders);
79 | if (!empty($authHeader) && preg_match($this->headerTokenPattern, $authHeader, $matches)) {
80 | return $matches[1];
81 | }
82 |
83 | $token = $request->getQueryParams()[$this->queryParameterName] ?? null;
84 | return is_string($token) ? $token : null;
85 | }
86 |
87 | private function getClaimCheckerManager(): ClaimCheckerManager
88 | {
89 | return new ClaimCheckerManager($this->claimCheckers);
90 | }
91 |
92 | public function challenge(ResponseInterface $response): ResponseInterface
93 | {
94 | return $response->withHeader(Header::WWW_AUTHENTICATE, "{$this->headerName} realm=\"{$this->realm}\"");
95 | }
96 |
97 | /**
98 | * @param string $realm The HTTP authentication realm.
99 | *
100 | * @return self
101 | */
102 | public function withRealm(string $realm): self
103 | {
104 | $new = clone $this;
105 | $new->realm = $realm;
106 | return $new;
107 | }
108 |
109 | /**
110 | * @param string $headerName Authorization header name.
111 | *
112 | * @return self
113 | */
114 | public function withHeaderName(string $headerName): self
115 | {
116 | $new = clone $this;
117 | $new->headerName = $headerName;
118 | return $new;
119 | }
120 |
121 | /**
122 | * @param string $headerTokenPattern Regular expression to use for getting a token from authorization header.
123 | * Token value should match first capturing group.
124 | *
125 | * @psalm-param non-empty-string $headerTokenPattern
126 | */
127 | public function withHeaderTokenPattern(string $headerTokenPattern): self
128 | {
129 | $new = clone $this;
130 | $new->headerTokenPattern = $headerTokenPattern;
131 | return $new;
132 | }
133 |
134 | /**
135 | * @param string $queryParameterName Request parameter name to check for a token.
136 | *
137 | * @return self
138 | */
139 | public function withQueryParameterName(string $queryParameterName): self
140 | {
141 | $new = clone $this;
142 | $new->queryParameterName = $queryParameterName;
143 | return $new;
144 | }
145 |
146 | /**
147 | * @param string $identifier Identifier to check claims for.
148 | *
149 | * @return self
150 | */
151 | public function withIdentifier(string $identifier): self
152 | {
153 | $new = clone $this;
154 | $new->identifier = $identifier;
155 | return $new;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/KeyFactory/FromCertificateFile.php:
--------------------------------------------------------------------------------
1 | file, $additionalValues);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/KeyFactory/FromKeyFile.php:
--------------------------------------------------------------------------------
1 | file, $this->password, $additionalValues);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/KeyFactory/FromPKCS12CertificateFile.php:
--------------------------------------------------------------------------------
1 | file, $this->secret, $additionalValues);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/KeyFactory/FromSecret.php:
--------------------------------------------------------------------------------
1 | secret, $additionalValues);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/KeyFactoryInterface.php:
--------------------------------------------------------------------------------
1 | algorithmManager);
34 | $jws = $jwsBuilder->create()->withPayload(Json::encode($payload));
35 | $jwk = $this->keyFactory->create();
36 |
37 | foreach ($this->algorithmManager->list() as $algorithm) {
38 | $jws = $jws->addSignature($jwk, ['alg' => $algorithm]);
39 | }
40 |
41 | return $this->serializerManager->serialize($format, $jws->build(), $signatureIndex);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/TokenFactoryInterface.php:
--------------------------------------------------------------------------------
1 | serializerManager->unserialize($token, $format);
38 | } catch (\InvalidArgumentException) {
39 | return null;
40 | }
41 |
42 | $jwk = $this->keyFactory->create();
43 |
44 | foreach ($this->algorithmManager->list() as $index => $_algorithm) {
45 | /** @var int $index */
46 | if ($this->verifyToken($jws, $jwk, $index)) {
47 | /** @var array|null */
48 | return Json::decode($jws->getPayload() ?? '');
49 | }
50 | }
51 | return null;
52 | }
53 |
54 | private function verifyToken(JWS $jws, JWK $jwk, int $signature = 0): bool
55 | {
56 | return (new JWSVerifier($this->algorithmManager))->verifyWithKey($jws, $jwk, $signature);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/TokenRepositoryInterface.php:
--------------------------------------------------------------------------------
1 |