Skip to content

Commit 69e0c77

Browse files
Initial login functionality (#3)
* Initial login functionality * Update php versions in CI * Support for running tests with Laravel 8 * phpstan and psalm ignore on wrong typehint of open id connect client
1 parent c292166 commit 69e0c77

19 files changed

+992
-2
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
php: [ 8.0, 8.1, 8.2 ]
16+
php: [ 8.1, 8.2 ]
1717
laravel: [ 8.*, 9.*, 10.* ]
1818
stability: [ prefer-stable ]
1919
include:

composer.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@
2121
}
2222
},
2323
"require": {
24-
"php": ">=8.0"
24+
"php": ">=8.1",
25+
"jumbojett/openid-connect-php": "dev-master",
26+
"guzzlehttp/guzzle": "^7.5",
27+
"web-token/jwt-encryption": "^3.1",
28+
"web-token/jwt-key-mgmt": "^3.1",
29+
"web-token/jwt-encryption-algorithm-aescbc": "^3.1",
30+
"web-token/jwt-encryption-algorithm-rsa": "^3.1"
2531
},
2632
"require-dev": {
2733
"orchestra/testbench": "^6.0|^7.0|^8.0",

config/oidc.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return [
6+
/**
7+
* The issuer URL of the OpenID Connect provider.
8+
*/
9+
'issuer' => env('OIDC_ISSUER', ''),
10+
11+
/**
12+
* The client ID of the OpenID Connect provider.
13+
*/
14+
'client_id' => env('OIDC_CLIENT_ID', ''),
15+
16+
/**
17+
* If needed, the client secret of the OpenID Connect provider.
18+
*/
19+
'client_secret' => env('OIDC_CLIENT_SECRET', ''),
20+
21+
/**
22+
* Only needed when response of user info endpoint is encrypted.
23+
* This is the path to the JWE decryption key.
24+
*/
25+
'decryption_key_path' => env('OIDC_DECRYPTION_KEY_PATH', ''),
26+
27+
/**
28+
* By default, the openid scope is requested. If you need additional scopes, you can specify them here.
29+
*/
30+
'additional_scopes' => explode(',', env('OIDC_ADDITIONAL_SCOPES', '')),
31+
32+
/**
33+
* Code Challenge Method used for PKCE.
34+
*/
35+
'code_challenge_method' => env('OIDC_CODE_CHALLENGE_METHOD', 'S256'),
36+
37+
/**
38+
* TTL of the OpenID configuration cache in seconds.
39+
*/
40+
'configuration_cache_ttl' => env('OIDC_CONFIGURATION_CACHE_TTL', 60 * 60 * 24),
41+
42+
/**
43+
* Route configuration
44+
*/
45+
'route_configuration' => [
46+
'login_route' => env('OIDC_LOGIN_ROUTE', '/oidc/login'),
47+
'middleware' => [
48+
'web'
49+
],
50+
'prefix' => '',
51+
]
52+
];

routes/oidc.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Support\Facades\Route;
6+
use MinVWS\OpenIDConnectLaravel\Http\Controllers\LoginController;
7+
8+
Route::get(config('oidc.route_configuration.login_route'), LoginController::class)->name('oidc.login');

src/Http/Controllers/Controller.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\Http\Controllers;
6+
7+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
8+
use Illuminate\Foundation\Bus\DispatchesJobs;
9+
use Illuminate\Foundation\Validation\ValidatesRequests;
10+
use Illuminate\Routing\Controller as BaseController;
11+
12+
class Controller extends BaseController
13+
{
14+
use AuthorizesRequests;
15+
use DispatchesJobs;
16+
use ValidatesRequests;
17+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\Http\Controllers;
6+
7+
use Exception;
8+
use Illuminate\Contracts\Support\Responsable;
9+
use Jumbojett\OpenIDConnectClientException;
10+
use MinVWS\OpenIDConnectLaravel\Http\Responses\LoginResponseInterface;
11+
use MinVWS\OpenIDConnectLaravel\OpenIDConnectClient;
12+
use MinVWS\OpenIDConnectLaravel\Services\OpenIDConnectExceptionHandlerInterface;
13+
14+
class LoginController extends Controller
15+
{
16+
public function __construct(
17+
protected OpenIDConnectClient $client,
18+
protected OpenIDConnectExceptionHandlerInterface $exceptionHandler,
19+
) {
20+
}
21+
22+
public function __invoke(): Responsable
23+
{
24+
// This redirects to the client and handles the redirect back
25+
try {
26+
$this->client->authenticate();
27+
} catch (OpenIDConnectClientException $e) {
28+
return $this->exceptionHandler->handleExceptionWhileAuthenticate($e);
29+
}
30+
31+
// After the redirect back, we can get the user information
32+
try {
33+
$userInfo = $this->client->requestUserInfo();
34+
if (!is_object($userInfo)) {
35+
throw new OpenIDConnectClientException('Received user info is not an object');
36+
}
37+
} catch (OpenIDConnectClientException $e) {
38+
return $this->exceptionHandler->handleExceptionWhileRequestUserInfo($e);
39+
} catch (Exception $e) {
40+
return $this->exceptionHandler->handleException($e);
41+
}
42+
43+
// Return the user information in a response
44+
return app(LoginResponseInterface::class, ['userInfo' => $userInfo]);
45+
}
46+
}

src/Http/Responses/LoginResponse.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\Http\Responses;
6+
7+
use Illuminate\Contracts\Support\Responsable;
8+
use Illuminate\Http\JsonResponse;
9+
use Illuminate\Http\Request;
10+
use Symfony\Component\HttpFoundation\Response;
11+
12+
class LoginResponse implements Responsable
13+
{
14+
public function __construct(
15+
protected object $userInfo
16+
) {
17+
}
18+
19+
/**
20+
* Create an HTTP response that represents the object.
21+
*
22+
* @param Request $request
23+
* @return Response
24+
*/
25+
public function toResponse($request): Response
26+
{
27+
return new JsonResponse([
28+
'userInfo' => $this->userInfo,
29+
]);
30+
}
31+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\Http\Responses;
6+
7+
use Illuminate\Contracts\Support\Responsable;
8+
9+
interface LoginResponseInterface extends Responsable
10+
{
11+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\OpenIDConfiguration;
6+
7+
/**
8+
* Class OpenIDConfiguration
9+
* Based on the OpenID Provider Metadata specification.
10+
* @link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
11+
*/
12+
class OpenIDConfiguration
13+
{
14+
/**
15+
* @param string $version
16+
* @param string[] $tokenEndpointAuthMethodsSupported
17+
* @param bool $claimsParameterSupported
18+
* @param bool $requestParameterSupported
19+
* @param bool $requestUriParameterSupported
20+
* @param bool $requireRequestUriRegistration
21+
* @param string[] $grantTypesSupported
22+
* @param bool $frontchannelLogoutSupported
23+
* @param bool $frontchannelLogoutSessionSupported
24+
* @param bool $backchannelLogoutSupported
25+
* @param bool $backchannelLogoutSessionSupported
26+
* @param string $issuer
27+
* @param string $authorizationEndpoint
28+
* @param string $jwksUri
29+
* @param string $tokenEndpoint
30+
* @param string[] $scopesSupported
31+
* @param string[] $responseTypesSupported
32+
* @param string[] $responseModesSupported
33+
* @param string[] $subjectTypesSupported
34+
* @param string[] $idTokenSigningAlgValuesSupported
35+
* @param string $userinfoEndpoint
36+
* @param string[] $codeChallengeMethodsSupported
37+
*/
38+
public function __construct(
39+
public string $version = '',
40+
public array $tokenEndpointAuthMethodsSupported = [],
41+
public bool $claimsParameterSupported = false,
42+
public bool $requestParameterSupported = false,
43+
public bool $requestUriParameterSupported = false,
44+
public bool $requireRequestUriRegistration = false,
45+
public array $grantTypesSupported = [],
46+
public bool $frontchannelLogoutSupported = false,
47+
public bool $frontchannelLogoutSessionSupported = false,
48+
public bool $backchannelLogoutSupported = false,
49+
public bool $backchannelLogoutSessionSupported = false,
50+
public string $issuer = '',
51+
public string $authorizationEndpoint = '',
52+
public string $jwksUri = '',
53+
public string $tokenEndpoint = '',
54+
public array $scopesSupported = [],
55+
public array $responseTypesSupported = [],
56+
public array $responseModesSupported = [],
57+
public array $subjectTypesSupported = [],
58+
public array $idTokenSigningAlgValuesSupported = [],
59+
public string $userinfoEndpoint = '',
60+
public array $codeChallengeMethodsSupported = [],
61+
) {
62+
}
63+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\OpenIDConfiguration;
6+
7+
use Illuminate\Contracts\Cache\Repository;
8+
use Illuminate\Support\Facades\Http;
9+
10+
class OpenIDConfigurationLoader
11+
{
12+
public function __construct(
13+
protected string $issuer,
14+
protected ?Repository $cacheStore = null,
15+
protected int $cacheTtl = 3600,
16+
) {
17+
}
18+
19+
/**
20+
* @throws OpenIDConfigurationLoaderException
21+
*/
22+
public function getConfiguration(): OpenIDConfiguration
23+
{
24+
if (!$this->cacheStore) {
25+
return $this->getConfigurationFromIssuer();
26+
}
27+
28+
return $this->cacheStore->remember('openid-configuration', $this->cacheTtl, function () {
29+
return $this->getConfigurationFromIssuer();
30+
});
31+
}
32+
33+
/**
34+
* @throws OpenIDConfigurationLoaderException
35+
*/
36+
protected function getConfigurationFromIssuer(): OpenIDConfiguration
37+
{
38+
$url = $this->getOpenIDConfigurationUrl();
39+
40+
$response = Http::get($url);
41+
if (!$response->successful()) {
42+
throw new OpenIDConfigurationLoaderException(
43+
message: 'Could not load OpenID configuration from issuer',
44+
context: [
45+
'issuer' => $this->issuer,
46+
'url' => $url,
47+
'response_status_code' => $response->status(),
48+
'response' => $response,
49+
],
50+
);
51+
}
52+
53+
if (!is_array($response->json())) {
54+
throw new OpenIDConfigurationLoaderException(
55+
message: 'Response body of OpenID configuration is not JSON',
56+
context: [
57+
'issuer' => $this->issuer,
58+
'url' => $url,
59+
'response_status_code' => $response->status(),
60+
'response' => $response,
61+
'response_body' => $response->body(),
62+
],
63+
);
64+
}
65+
66+
return new OpenIDConfiguration(
67+
version: $response->json('version', ''),
68+
tokenEndpointAuthMethodsSupported: $response->json('token_endpoint_auth_methods_supported', []),
69+
claimsParameterSupported: $response->json('claims_parameter_supported', false),
70+
requestParameterSupported: $response->json('request_parameter_supported', false),
71+
requestUriParameterSupported: $response->json('request_uri_parameter_supported', false),
72+
requireRequestUriRegistration: $response->json('require_request_uri_registration', false),
73+
grantTypesSupported: $response->json('grant_types_supported', []),
74+
frontchannelLogoutSupported: $response->json('frontchannel_logout_supported', false),
75+
frontchannelLogoutSessionSupported: $response->json('frontchannel_logout_session_supported', false),
76+
backchannelLogoutSupported: $response->json('backchannel_logout_supported', false),
77+
backchannelLogoutSessionSupported: $response->json('backchannel_logout_session_supported', false),
78+
issuer: $response->json('issuer', ''),
79+
authorizationEndpoint: $response->json('authorization_endpoint', ''),
80+
jwksUri: $response->json('jwks_uri', ''),
81+
tokenEndpoint: $response->json('token_endpoint', ''),
82+
scopesSupported: $response->json('scopes_supported', []),
83+
responseTypesSupported: $response->json('response_types_supported', []),
84+
responseModesSupported: $response->json('response_modes_supported', []),
85+
subjectTypesSupported: $response->json('subject_types_supported', []),
86+
idTokenSigningAlgValuesSupported: $response->json('id_token_signing_alg_values_supported', []),
87+
userinfoEndpoint: $response->json('userinfo_endpoint', ''),
88+
codeChallengeMethodsSupported: $response->json('code_challenge_methods_supported', [])
89+
);
90+
}
91+
92+
protected function getOpenIDConfigurationUrl(): string
93+
{
94+
return $this->issuer . '/.well-known/openid-configuration';
95+
}
96+
}

0 commit comments

Comments
 (0)