Skip to content

Commit 0c7bced

Browse files
committed
implement private jwt login flow by passing the client assertion to the token endpoint
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
1 parent dda608f commit 0c7bced

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

lib/Controller/LoginController.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use OCA\UserOIDC\Event\TokenObtainedEvent;
2222
use OCA\UserOIDC\Helper\HttpClientHelper;
2323
use OCA\UserOIDC\Service\DiscoveryService;
24+
use OCA\UserOIDC\Service\JwkService;
2425
use OCA\UserOIDC\Service\LdapService;
2526
use OCA\UserOIDC\Service\OIDCService;
2627
use OCA\UserOIDC\Service\ProviderService;
@@ -91,6 +92,7 @@ public function __construct(
9192
private ICrypto $crypto,
9293
private TokenService $tokenService,
9394
private OidcService $oidcService,
95+
private JwkService $jwkService,
9496
) {
9597
parent::__construct($request, $config, $l10n);
9698
}
@@ -373,6 +375,7 @@ public function code(string $state = '', string $code = '', string $scope = '',
373375
$oidcSystemConfig = $this->config->getSystemValue('user_oidc', []);
374376
$isPkceSupported = in_array('S256', $discovery['code_challenge_methods_supported'] ?? [], true);
375377
$isPkceEnabled = $isPkceSupported && ($oidcSystemConfig['use_pkce'] ?? true);
378+
$usePrivateKeyJwt = $this->providerService->getSetting($providerId, ProviderService::SETTING_USE_PRIVATE_KEY_JWT, '0') !== '0';
376379

377380
try {
378381
$requestBody = [
@@ -402,15 +405,21 @@ public function code(string $state = '', string $code = '', string $scope = '',
402405
$tokenEndpointAuthMethod = 'client_secret_post';
403406
}
404407

405-
if ($tokenEndpointAuthMethod === 'client_secret_basic') {
408+
// private key JWT auth does not work with client_secret_basic, we don't wanna pass the client secret
409+
if ($tokenEndpointAuthMethod === 'client_secret_basic' && !$usePrivateKeyJwt) {
406410
$headers = [
407411
'Authorization' => 'Basic ' . base64_encode($provider->getClientId() . ':' . $providerClientSecret),
408412
'Content-Type' => 'application/x-www-form-urlencoded',
409413
];
410414
} else {
411415
// Assuming client_secret_post as no other option is supported currently
412416
$requestBody['client_id'] = $provider->getClientId();
413-
$requestBody['client_secret'] = $providerClientSecret;
417+
if ($usePrivateKeyJwt) {
418+
$requestBody['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
419+
$requestBody['client_assertion'] = $this->jwkService->generateClientAssertion($provider, $discovery['issuer'], $code);
420+
} else {
421+
$requestBody['client_secret'] = $providerClientSecret;
422+
}
414423
}
415424

416425
$body = $this->clientService->post(

lib/Service/JwkService.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
require_once __DIR__ . '/../../vendor/autoload.php';
1212

13+
use OCA\UserOIDC\Db\Provider;
1314
use OCA\UserOIDC\Vendor\Firebase\JWT\JWK;
1415
use OCA\UserOIDC\Vendor\Firebase\JWT\JWT;
1516
use OCP\AppFramework\Services\IAppConfig;
@@ -164,6 +165,27 @@ public function createJwt(array $payload, \OpenSSLAsymmetricKey $key, string $ke
164165
return JWT::encode($payload, $key, $alg, $keyId);
165166
}
166167

168+
public function generateClientAssertion(Provider $provider, string $discoveryIssuer, ?string $code = null): string {
169+
$myPemPrivateKey = $this->getMyPemSignatureKey();
170+
$sslPrivateKey = openssl_pkey_get_private($myPemPrivateKey);
171+
$pemPrivateKeyExpiresAt = $this->appConfig->getAppValueInt(self::PEM_SIG_KEY_EXPIRES_AT_SETTINGS_KEY, lazy: true);
172+
173+
$payload = [
174+
'sub' => $provider->getClientId(),
175+
'aud' => $discoveryIssuer,
176+
'iss' => $provider->getClientId(),
177+
'iat' => time(),
178+
'exp' => time() + 60,
179+
'jti' => \bin2hex(\random_bytes(16)),
180+
];
181+
182+
if ($code !== null) {
183+
$payload['code'] = $code;
184+
}
185+
186+
return $this->createJwt($payload, $sslPrivateKey, 'sig_key_' . $pemPrivateKeyExpiresAt, 'ES384');
187+
}
188+
167189
public function debug(): array {
168190
$myPemPrivateKey = $this->getMyPemSignatureKey();
169191
$sslPrivateKey = openssl_pkey_get_private($myPemPrivateKey);

0 commit comments

Comments
 (0)