Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.

Commit 99ff945

Browse files
Initial commit
0 parents  commit 99ff945

31 files changed

+1825
-0
lines changed

.gitattributes

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
* text=auto
2+
3+
/assets export-ignore
4+
/tests export-ignore
5+
/.gitattributes export-ignore
6+
/.github export-ignore
7+
/.gitignore export-ignore
8+
/phpunit.xml.dist export-ignore
9+
/.php-cs-fixer.dist.php export-ignore
10+
/CHANGELOG.md export-ignore
11+
/README.md export-ignore

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.log
2+
/build/
3+
/.php_cs
4+
/.php_cs.cache
5+
/.phpunit.result.cache
6+
/vendor/
7+
/composer.lock
8+
.php-cs-fixer.php
9+
.php-cs-fixer.cache
10+
/tests/.kernel/

.php-cs-fixer.dist.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
$finder = PhpCsFixer\Finder::create()
6+
->in(__DIR__)
7+
->append([__FILE__]);
8+
9+
return (new PhpCsFixer\Config())
10+
->setUsingCache(true)
11+
->setRules(
12+
[
13+
'@PSR12' => true,
14+
'@Symfony' => true,
15+
'array_indentation' => true,
16+
'class_definition' => ['multi_line_extends_each_single_line' => true],
17+
'compact_nullable_typehint' => true,
18+
'concat_space' => ['spacing' => 'one'],
19+
'declare_strict_types' => true,
20+
'heredoc_to_nowdoc' => true,
21+
'global_namespace_import' => [
22+
'import_classes' => false,
23+
'import_constants' => false,
24+
'import_functions' => false,
25+
],
26+
'list_syntax' => ['syntax' => 'short'],
27+
'no_null_property_initialization' => true,
28+
'phpdoc_to_comment' => false,
29+
'phpdoc_align' => ['align' => 'left'],
30+
'phpdoc_summary' => false,
31+
'ternary_to_null_coalescing' => true,
32+
]
33+
)
34+
->setRiskyAllowed(true)
35+
->setFinder($finder);

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright (c) 2021 Minh Vuong <vuongxuongminh@gmail.com>
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
13+
all 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
21+
THE SOFTWARE.

composer.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "php-istio/jwt-authentication-bundle",
3+
"description": "Symfony bundle to help authenticate JWT payload from Istio Envoy proxy.",
4+
"type": "symfony-bundle",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Minh Vuong",
9+
"email": "vuongxuongminh@gmail.com"
10+
}
11+
],
12+
"config": {
13+
"sort-packages": true
14+
},
15+
"minimum-stability": "stable",
16+
"require": {
17+
"php": ">=8.0",
18+
"nyholm/psr7": "^1.4",
19+
"php-istio/jwt-payload-extractor": "^1.0",
20+
"symfony/psr-http-message-bridge": "^2.1",
21+
"symfony/security-bundle": "^5.3"
22+
},
23+
"autoload": {
24+
"psr-4": {
25+
"Istio\\Symfony\\JWTAuthentication\\": "src"
26+
}
27+
},
28+
"autoload-dev": {
29+
"psr-4": {
30+
"Istio\\Symfony\\JWTAuthentication\\Tests\\": "tests"
31+
}
32+
},
33+
"require-dev": {
34+
"symfony/browser-kit": "^5.3",
35+
"symfony/console": "^5.3",
36+
"symfony/framework-bundle": "^5.3",
37+
"symfony/phpunit-bridge": "^5.3"
38+
}
39+
}

phpunit.xml.dist

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true">
4+
<php>
5+
<env name="KERNEL_CLASS" value="Istio\Symfony\JWTAuthentication\Tests\TestKernel" />
6+
<env name="SHELL_VERBOSITY" value="-1" />
7+
<env name="SYMFONY_PHPUNIT_REMOVE" value="" />
8+
<ini name="error_reporting" value="-1" />
9+
</php>
10+
11+
<testsuites>
12+
<testsuite name="Bundle Test Suite">
13+
<directory>tests</directory>
14+
</testsuite>
15+
</testsuites>
16+
17+
<coverage processUncoveredFiles="true">
18+
<include>
19+
<directory>.</directory>
20+
</include>
21+
<exclude>
22+
<directory>tests</directory>
23+
<directory>vendor</directory>
24+
<file>.php-cs-fixer.dist.php</file>
25+
</exclude>
26+
</coverage>
27+
</phpunit>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
/*
3+
* (c) Minh Vuong <vuongxuongminh@gmail.com>
4+
*
5+
* This source file is subject to the MIT license that is bundled
6+
* with this source code in the file LICENSE.
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Istio\Symfony\JWTAuthentication\Authenticator;
12+
13+
use Istio\Symfony\JWTAuthentication\User\JWTPayloadAwareUserProviderInterface;
14+
use Nyholm\Psr7\Factory\Psr17Factory;
15+
use Psr\Http\Message\ServerRequestInterface;
16+
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpFoundation\Response;
19+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
20+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
21+
use Symfony\Component\Security\Core\User\UserProviderInterface;
22+
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
23+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
24+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
25+
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
26+
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
27+
28+
final class Authenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
29+
{
30+
public function __construct(
31+
private iterable $userIdentifierClaimMappings,
32+
private UserProviderInterface $userProvider
33+
) {
34+
}
35+
36+
public function supports(Request $request): ?bool
37+
{
38+
$psr7Request = $this->normalizeRequest($request);
39+
40+
foreach ($this->userIdentifierClaimMappings as $mapping) {
41+
/** @var UserIdentifierClaimMapping $mapping */
42+
$payload = $mapping->extractor()->extract($psr7Request);
43+
44+
if (null !== $payload && false !== is_string($payload[$mapping->userIdentifierClaim()] ?? null)) {
45+
$request->attributes->set(
46+
'_user_identifier_and_payload',
47+
[
48+
$mapping->userIdentifierClaim(),
49+
$payload,
50+
]
51+
);
52+
53+
return true;
54+
}
55+
}
56+
57+
return false;
58+
}
59+
60+
public function authenticate(Request $request): PassportInterface
61+
{
62+
[$userIdentifierClaim, $payload] = $request->attributes->get('_user_identifier_and_payload');
63+
$request->attributes->remove('_user_identifier_and_payload');
64+
$userBadge = new UserBadge($payload[$userIdentifierClaim], $this->makeUserLoader($payload));
65+
$passport = new SelfValidatingPassport($userBadge);
66+
$passport->setAttribute('_payload', $payload);
67+
68+
return $passport;
69+
}
70+
71+
private function makeUserLoader(array $payload): callable
72+
{
73+
return function (string $userIdentifier) use ($payload) {
74+
if ($this->userProvider instanceof JWTPayloadAwareUserProviderInterface) {
75+
return $this->userProvider->loadUserByIdentifier($userIdentifier, $payload);
76+
}
77+
78+
return $this->userProvider->loadUserByIdentifier($userIdentifier);
79+
};
80+
}
81+
82+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
83+
{
84+
return null;
85+
}
86+
87+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
88+
{
89+
throw $exception;
90+
}
91+
92+
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
93+
{
94+
/** @var SelfValidatingPassport $passport */
95+
$payload = $passport->getAttribute('_payload');
96+
$token = parent::createAuthenticatedToken($passport, $firewallName);
97+
$token->setAttribute('jwt_payload', $payload);
98+
99+
return $token;
100+
}
101+
102+
public function start(Request $request, AuthenticationException $authException = null)
103+
{
104+
return new Response('Istio JWT in request\'s missing or invalid.', Response::HTTP_UNAUTHORIZED);
105+
}
106+
107+
private function normalizeRequest(Request $request): ServerRequestInterface
108+
{
109+
$psr17Factory = new Psr17Factory();
110+
$psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
111+
112+
return $psrHttpFactory->createRequest($request);
113+
}
114+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/*
3+
* (c) Minh Vuong <vuongxuongminh@gmail.com>
4+
*
5+
* This source file is subject to the MIT license that is bundled
6+
* with this source code in the file LICENSE.
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Istio\Symfony\JWTAuthentication\Authenticator;
12+
13+
use Istio\JWTPayloadExtractor\ExtractorInterface;
14+
15+
final class UserIdentifierClaimMapping
16+
{
17+
public function __construct(private string $userIdentifierClaim, private ExtractorInterface $extractor)
18+
{
19+
}
20+
21+
public function userIdentifierClaim(): string
22+
{
23+
return $this->userIdentifierClaim;
24+
}
25+
26+
public function extractor(): ExtractorInterface
27+
{
28+
return $this->extractor;
29+
}
30+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/*
3+
* (c) Minh Vuong <vuongxuongminh@gmail.com>
4+
*
5+
* This source file is subject to the MIT license that is bundled
6+
* with this source code in the file LICENSE.
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Istio\Symfony\JWTAuthentication\DependencyInjection;
12+
13+
use Symfony\Component\Config\FileLocator;
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Extension\Extension;
16+
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
17+
18+
final class JWTAuthenticationExtension extends Extension
19+
{
20+
public function load(array $configs, ContainerBuilder $container)
21+
{
22+
$loader = new PhpFileLoader($container, new FileLocator(dirname(__DIR__) . '/Resources/config'));
23+
24+
$loader->load('payload_extractor.php');
25+
$loader->load('security.php');
26+
}
27+
}

0 commit comments

Comments
 (0)