Skip to content

Commit

Permalink
Adding well-known configuration and JWT routes in Laravel
Browse files Browse the repository at this point in the history
When using Laravel with Passport, we have enough knowledge of the environment
to be able to provide 2 useful routes our of the box:

- a discovery endpoint at `/.well-known/openid-configuration`.
- a JWKS endpoint at `/oauth/jwks`.

This greatly eases integrating with Laravel since clients can now
use auto-discovery to integrate with Laravel + PassPort + OpenID.
  • Loading branch information
moufmouf committed Jan 18, 2024
1 parent da5191d commit 3be9e08
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 7 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ In case you want to change the default scopes, add custom claim sets or change t
php artisan vendor:publish --tag=openid
```

### Discovery and JWKS

The Laravel Passport integration also provides:

- a discovery endpoint at `/.well-known/openid-configuration`.
- a JWKS endpoint at `/oauth/jwks`.

Laravel Passport does not provide a `userinfo` endpoint by default. If you provide one, you can add it to the
discovery document by naming the route `openid.userinfo`.

```php
Route::get('/oauth/userinfo', 'YourController@userinfo')->middleware('xxx')->name('openid.userinfo');
```


## Support
Found a bug? Got a feature request? [Create an issue](https://github.com/ronvanderheijden/openid-connect/issues).

Expand Down
46 changes: 46 additions & 0 deletions src/Laravel/DiscoveryController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace OpenIDConnect\Laravel;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

class DiscoveryController
{
public function discovery(Request $request)
{
$response = [
'issuer' => url('/'),
'authorization_endpoint' => route('passport.authorizations.authorize'),
'token_endpoint' => route('passport.token'),
'jwks_uri' => route('openid.jwks'),
'response_types_supported' => [
'code',
'token',
'id_token',
'code token',
'code id_token',
'token id_token',
'code token id_token',
'none',
],
'subject_types_supported' => [
'public',
],
'id_token_signing_alg_values_supported' => [
'RS256',
],
'scopes_supported' => config('openid.passport.tokens_can'),
'token_endpoint_auth_methods_supported' => [
'client_secret_basic',
'client_secret_post',
],
];

if (Route::has('openid.userinfo')) {
$response['userinfo_endpoint'] = route('openid.userinfo');
}

return response()->json($response, 200, [], JSON_PRETTY_PRINT);
}
}
38 changes: 38 additions & 0 deletions src/Laravel/JwksController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace OpenIDConnect\Laravel;

use Illuminate\Config\Repository as Config;
use Laravel\Passport\Passport;

class JwksController
{
public function jwks() {
$publicKey = $this->getPublicKey();

// Source: https://www.tuxed.net/fkooman/blog/json_web_key_set.html
$keyInfo = openssl_pkey_get_details(openssl_pkey_get_public($publicKey));

$jsonData = [
'keys' => [
[
'kty' => 'RSA',
'n' => rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['n'])), '='),
'e' => rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['e'])), '='),
],
],
];

return response()->json($jsonData, 200, [], JSON_PRETTY_PRINT);
}

private function getPublicKey(): string {
$publicKey = str_replace('\\n', "\n", app()->make(Config::class)->get('passport.public_key') ?? '');

if (!$publicKey) {
$publicKey = 'file://'.Passport::keyPath('oauth-public.key');
}

return $publicKey;
}
}
24 changes: 17 additions & 7 deletions src/Laravel/PassportServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,20 @@ public function boot()
__DIR__ . '/config/openid.php' => $this->app->configPath('openid.php'),
], ['openid', 'openid-config']);

$this->loadRoutesFrom(__DIR__.'/routes/web.php');

Passport\Passport::tokensCan(config('openid.passport.tokens_can'));

$this->registerClaimExtractor();
}

public function makeAuthorizationServer(): AuthorizationServer
{
$cryptKey = $this->makeCryptKey('private');

$customClaimSets = config('openid.custom_claim_sets');

$claimSets = array_map(function ($claimSet, $name) {
return new ClaimSet($name, $claimSet);
}, $customClaimSets, array_keys($customClaimSets));

$responseType = new IdTokenResponse(
app(config('openid.repositories.identity')),
new ClaimExtractor(...$claimSets),
app(ClaimExtractor::class),
Configuration::forSymmetricSigner(
app(config('openid.signer')),
InMemory::file($cryptKey->getKeyPath()),
Expand All @@ -67,4 +65,16 @@ public function makeAuthorizationServer(): AuthorizationServer
$responseType,
);
}

public function registerClaimExtractor() {
$this->app->singleton(ClaimExtractor::class, function () {
$customClaimSets = config('openid.custom_claim_sets');

$claimSets = array_map(function ($claimSet, $name) {
return new ClaimSet($name, $claimSet);
}, $customClaimSets, array_keys($customClaimSets));

return new ClaimExtractor(...$claimSets);
});
}
}
3 changes: 3 additions & 0 deletions src/Laravel/routes/web.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
Route::get('/oauth/jwks', \OpenIDConnect\Laravel\JwksController::class."@jwks")->name('openid.jwks');
Route::get('/.well-known/openid-configuration', \OpenIDConnect\Laravel\DiscoveryController::class."@discovery")->name('openid.discovery');

0 comments on commit 3be9e08

Please sign in to comment.