Skip to content

Commit 9d27204

Browse files
committed
Refactored
1 parent 6e0dc42 commit 9d27204

14 files changed

+326
-203
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ A few things are necessary to make this library work:
1717
When this package is done verifying the token is legit an Event will be fired to be consumed by the target application.
1818
This event should e.g. perform `Auth::login($user)` to fully let Laravel know this package has handled the authorization.
1919

20+
WIP Events - Also explain logout
21+
2022
You can set up an IDP with [`laravel/passport`][2] or set up your own with e.g. a Symfony application in combination with
2123
[`steverhoades/oauth2-openid-connect-server`][3]
2224

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "coddin-web/oidc-client-laravel-wrapper",
33
"description": "A Laravel wrapper of jumbojett's OpenID Connect Client",
44
"type": "library",
5-
"version": "1.2.0",
5+
"version": "1.3.0",
66
"minimum-stability": "stable",
77
"prefer-stable": true,
88
"require": {

config/oidc.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
'profile',
1515
'email',
1616
],
17+
'logout' => [
18+
'redirect_after' => '/',
19+
],
1720
],
1821
'private_key' => [
1922
'base64' => env('OIDC_BASE64_PRIVATE_KEY'),

phpstan-bootstrap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<?php
22

3+
echo 'Bootstrap initiated, bypassing finals...';
34
\DG\BypassFinals::enable();

src/Builder/OpenIDConnectClientBuilder.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ public function __construct(
1818
/**
1919
* @throws ConfigRepositoryException
2020
*/
21-
public function execute(): OpenIDConnectClient
21+
public function execute(bool $public = false): OpenIDConnectClient|OpenIDConnectClientProviderConfigurationPublic
2222
{
2323
$appUrl = $this->configRepository->getAsString('app.url');
2424

25-
$openIDClient = new \Jumbojett\OpenIDConnectClient(
25+
$clientClass = match ($public) {
26+
true => OpenIDConnectClientProviderConfigurationPublic::class,
27+
false => \Jumbojett\OpenIDConnectClient::class,
28+
};
29+
30+
$openIDClient = new $clientClass(
2631
provider_url: $this->configRepository->getAsString('oidc.provider.endpoint'),
2732
client_id: $this->configRepository->getAsString('oidc.client.id'),
2833
client_secret: $this->determineClientSecret(),
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Coddin\OpenIDConnectClient\Builder;
6+
7+
use Jumbojett\OpenIDConnectClient;
8+
use Jumbojett\OpenIDConnectClientException;
9+
10+
/**
11+
* @codeCoverageIgnore
12+
*/
13+
final class OpenIDConnectClientProviderConfigurationPublic extends OpenIDConnectClient
14+
{
15+
/**
16+
* @throws OpenIDConnectClientException
17+
*/
18+
public function getProviderConfigValuePublic(string $param, ?string $default = null): ?string
19+
{
20+
return $this->getProviderConfigValue(param: $param, default: $default);
21+
}
22+
}

src/Helper/DateTimeDiffCalculator.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Coddin\OpenIDConnectClient\Helper;
6+
7+
final class DateTimeDiffCalculator
8+
{
9+
public static function differenceInSeconds(
10+
\DateTimeInterface $start,
11+
\DateTimeInterface $end,
12+
): int {
13+
$diff = $end->diff($start);
14+
15+
$daysInSecs = ((int) $diff->format('%r%a') * 24 * 60 * 60);
16+
$hoursInSecs = ($diff->h * 60 * 60);
17+
$minsInSecs = ($diff->i * 60);
18+
19+
return ($daysInSecs + $hoursInSecs + $minsInSecs + $diff->s);
20+
}
21+
}

src/Http/Middleware/OpenIDConnectAuthenticated.php

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
use Coddin\OpenIDConnectClient\Event\UserAuthorizedEvent;
1010
use Coddin\OpenIDConnectClient\Helper\ConfigRepository;
1111
use Coddin\OpenIDConnectClient\Helper\ConfigRepositoryException;
12+
use Coddin\OpenIDConnectClient\Helper\DateTimeDiffCalculator;
13+
use Coddin\OpenIDConnectClient\Service\Token\Storage\Exception\MissingTokenException;
1214
use Coddin\OpenIDConnectClient\Service\Token\Storage\TokenStorageAdaptor;
1315
use Illuminate\Http\Request;
1416
use Illuminate\Routing\ResponseFactory;
17+
use Illuminate\Support\Facades\Auth;
1518
use Illuminate\Support\Facades\Log;
1619
use Jumbojett\OpenIDConnectClientException;
1720
use Lcobucci\JWT\Token\Plain;
@@ -30,30 +33,42 @@ public function __construct(
3033
}
3134

3235
/**
33-
* @throws HttpException
36+
* @throws ConfigRepositoryException
37+
* @throws MissingTokenException
38+
* @throws OpenIDConnectClientException
3439
*/
3540
public function handle(Request $request, \Closure $next): mixed
3641
{
37-
$token = $this->tokenStorageAdaptor->find();
42+
if (\str_contains($request->getPathInfo(), 'logout')) {
43+
return $next($request);
44+
}
3845

39-
if ($token !== null) {
40-
return $this->handleExistingToken($token, $request, $next);
46+
$accessToken = $this->tokenStorageAdaptor->find(TokenStorageAdaptor::ACCESS_TOKEN_STORAGE_KEY);
47+
48+
if ($accessToken !== null) {
49+
return $this->handleExistingToken($accessToken, $request, $next);
4150
}
4251

4352
try {
4453
$jwtVerifier = $this->jwtVerifierBuilder->execute();
4554
$openIDClient = $this->openIDConnectClientBuilder->execute();
55+
56+
// Dynamically set the redirect URL?
57+
$openIDClient->setRedirectURL($this->configRepository->getAsString('app.url') . $request->getPathInfo());
58+
4659
$openIDClient->authenticate();
4760

48-
/** @var Plain $token */
49-
$token = $jwtVerifier->parser()->parse($openIDClient->getIdToken());
61+
$accessToken = $jwtVerifier->parser()->parse($openIDClient->getAccessToken());
5062
$this->tokenStorageAdaptor->put(
51-
token: $token,
63+
accessToken: $accessToken,
64+
refreshToken: $openIDClient->getRefreshToken(),
5265
);
5366

54-
$userUuid = $token->claims()->get('sub');
55-
$userName = $token->claims()->get('nickname');
56-
$userEmail = $token->claims()->get('email');
67+
/** @var Plain $idToken */
68+
$idToken = $jwtVerifier->parser()->parse($openIDClient->getIdToken());
69+
$userUuid = $idToken->claims()->get('sub');
70+
$userName = $idToken->claims()->get('nickname');
71+
$userEmail = $idToken->claims()->get('email');
5772

5873
UserAuthorizedEvent::dispatch(
5974
$userUuid,
@@ -74,48 +89,54 @@ public function handle(Request $request, \Closure $next): mixed
7489
// Consume the code from the URL and redirect to the intended URL without
7590
// the query parameter still visible.
7691
$oauthCode = $request->get('code');
77-
if (is_string($oauthCode) && strlen($oauthCode) > 900) {
92+
if (is_string($oauthCode) && strlen($oauthCode) > 800) {
7893
return $this->responseFactory->redirectTo($request->getPathInfo());
7994
}
8095

8196
return $next($request);
8297
}
8398

99+
/**
100+
* @throws OpenIDConnectClientException
101+
* @throws MissingTokenException
102+
* @throws ConfigRepositoryException
103+
*/
84104
private function handleExistingToken(
85-
\Lcobucci\JWT\Token $token,
105+
\Lcobucci\JWT\Token $accessToken,
86106
Request $request,
87107
\Closure $next,
88108
): mixed {
89-
if ($token->isExpired(new \DateTimeImmutable())) {
109+
if ($accessToken->isExpired(new \DateTimeImmutable())) {
90110
$this->tokenStorageAdaptor->forget();
111+
Auth::logout();
91112

92113
return $this->responseFactory->redirectTo($request->getPathInfo());
93114
}
94115

95-
try {
96-
$openIDClient = $this->openIDConnectClientBuilder->execute();
97-
$stillActiveResponse = $openIDClient->introspectToken(
98-
token: $token->toString(),
99-
clientSecret: $this->configRepository->getAsString('oidc.client.secret'),
100-
);
101-
} catch (ConfigRepositoryException | OpenIDConnectClientException $e) {
102-
throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR);
103-
}
116+
/** @var Plain $accessToken */
117+
$claims = $accessToken->claims();
118+
/** @var \DateTimeInterface $issuedAt */
119+
$issuedAt = $claims->get('iat');
120+
/** @var \DateTimeInterface $expiresAt */
121+
$expiresAt = $claims->get('exp');
104122

105-
if (!\is_object($stillActiveResponse)) {
106-
$this->tokenStorageAdaptor->forget();
123+
$validityInSeconds = DateTimeDiffCalculator::differenceInSeconds($issuedAt, $expiresAt);
124+
$secondsLeft = DateTimeDiffCalculator::differenceInSeconds(new \DateTimeImmutable(), $expiresAt);
125+
$percentageLeft = (($secondsLeft / $validityInSeconds) * 100);
107126

108-
return $this->responseFactory->redirectTo($request->getPathInfo());
109-
}
110-
111-
// This is a shortcoming of the library returning an unstructured object.
112-
/* @phpstan-ignore-next-line */
113-
if ($stillActiveResponse->active === true) {
114-
return $next($request);
115-
} else {
116-
$this->tokenStorageAdaptor->forget();
127+
if ($percentageLeft <= 25) {
128+
$refreshToken = $this->tokenStorageAdaptor->get(TokenStorageAdaptor::REFRESH_TOKEN_STORAGE_KEY);
129+
$openIDClient = $this->openIDConnectClientBuilder->execute();
130+
$openIDClient->refreshToken($refreshToken->toString());
117131

118-
return $this->responseFactory->redirectTo($request->getPathInfo());
132+
$jwtVerifier = $this->jwtVerifierBuilder->execute();
133+
$newAccessToken = $jwtVerifier->parser()->parse($openIDClient->getAccessToken());
134+
$this->tokenStorageAdaptor->put(
135+
accessToken: $newAccessToken,
136+
refreshToken: $openIDClient->getRefreshToken(),
137+
);
119138
}
139+
140+
return $next($request);
120141
}
121142
}

src/Service/Token/Storage/IlluminateSessionAdaptorToken.php

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,23 @@ public function __construct(
1515
) {
1616
}
1717

18-
public function find(): ?Token
18+
public function find(string $type): ?Token
1919
{
20-
$token = $this->sessionStore->get($this->getStorageKey());
20+
if ($type !== $this->getAccessTokenStorageKey() && $type !== $this->getRefreshTokenStorageKey()) {
21+
return null;
22+
}
23+
24+
$token = $this->sessionStore->get($this->getAccessTokenStorageKey());
2125
if (!$token instanceof Token) {
2226
return null;
2327
}
2428

2529
return $token;
2630
}
2731

28-
public function get(): Token
32+
public function get(string $type): Token
2933
{
30-
$token = $this->find();
34+
$token = $this->find($type);
3135

3236
if ($token === null) {
3337
throw MissingTokenException::make();
@@ -36,18 +40,28 @@ public function get(): Token
3640
return $token;
3741
}
3842

39-
public function put(Token $token): void
43+
public function put(Token $accessToken, ?string $refreshToken = null): void
4044
{
41-
$this->sessionStore->put($this->getStorageKey(), $token);
45+
$this->sessionStore->put($this->getAccessTokenStorageKey(), $accessToken);
46+
if ($refreshToken !== null) {
47+
$this->sessionStore->put($this->getRefreshTokenStorageKey(), $refreshToken);
48+
}
4249
}
4350

4451
public function forget(): void
4552
{
46-
$this->sessionStore->forget($this->getStorageKey());
53+
$this->sessionStore->forget($this->getAccessTokenStorageKey());
54+
$this->sessionStore->forget($this->getRefreshTokenStorageKey());
55+
$this->sessionStore->save();
56+
}
57+
58+
public function getAccessTokenStorageKey(): string
59+
{
60+
return self::ACCESS_TOKEN_STORAGE_KEY;
4761
}
4862

49-
public function getStorageKey(): string
63+
public function getRefreshTokenStorageKey(): string
5064
{
51-
return 'oidc_id_token';
65+
return self::REFRESH_TOKEN_STORAGE_KEY;
5266
}
5367
}

src/Service/Token/Storage/TokenStorageAdaptor.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@
99

1010
interface TokenStorageAdaptor
1111
{
12-
public function find(): ?Token;
12+
public const ACCESS_TOKEN_STORAGE_KEY = 'oidc_id_access_token';
13+
public const REFRESH_TOKEN_STORAGE_KEY = 'oidc_id_refresh_token';
14+
15+
public function find(string $type): ?Token;
1316

1417
/**
1518
* @throws MissingTokenException
1619
*/
17-
public function get(): Token;
20+
public function get(string $type): Token;
1821

19-
public function put(Token $token): void;
22+
public function put(Token $accessToken, ?string $refreshToken = null): void;
2023

2124
public function forget(): void;
2225

23-
public function getStorageKey(): string;
26+
public function getAccessTokenStorageKey(): string;
27+
28+
public function getRefreshTokenStorageKey(): string;
2429
}

tests/Unit/Builder/OpenIDConnectClientBuilderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function authorization_code_flow(): void
8080
configRepository: $this->configRepository,
8181
);
8282
/** @noinspection PhpUnhandledExceptionInspection */
83-
$openIdConnectClient = $openIdConnectBuilder->execute();
83+
$openIdConnectClient = $openIdConnectBuilder->execute(true);
8484

8585
self::assertInstanceOf(OpenIDConnectClient::class, $openIdConnectClient);
8686
// Obviously this is actually testing the Client, which is beyond our scope,

0 commit comments

Comments
 (0)