I've been working on custom Keycloak and Shibboleth (OIDC) providers that use signed JSON web tokens to authenticate the client instead of a secret. It is based on the specifications in this RFC: https://datatracker.ietf.org/doc/html/rfc7523#section-2.2.
Just like #653, I implemented a new auth option provider that allows you to bring your own JWT generator. Your provider probably already comes with a JWT library that you can easily plug in. This prevents any extra or unnecessary dependencies, and keeps the option provider itself very basic:
class JwtAuthOptionProvider extends PostAuthOptionProvider
{
/**
* @var callable():string
*/
private $jwtGenerator;
/**
* @param callable():string $jwtGenerator
*/
public function __construct(callable $jwtGenerator)
{
$this->jwtGenerator = $jwtGenerator;
}
#[\Override]
public function getAccessTokenOptions($method, array $params)
{
$params['client_assertion'] = ($this->jwtGenerator)();
$params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
// The AbstractProvider adds client_id and client_secret to params if they are set.
// When using a JWT, the client_secret should not be sent and the client id is already part of the JWT (iss, sub).
unset($params['client_id'], $params['client_secret']);
return parent::getAccessTokenOptions($method, $params);
}
}
I'm using a custom Keycloak provider based on https://github.com/stevenmaguire/oauth2-keycloak, because it does not yet support JWTs for client authentication. I plan on upstreaming to the specific provider but I wanted to check here first for the auth option provider part. There might be other providers, that I'm not yet aware of, that could make us of it too.
I still have some testing to do with my custom providers but if there's any interest in the auth option provider part I would be happy to submit a pull request later on!
The existing Keycloak provider already depends on firebase/php-jwt so this is what the generator looks like using that same library:
use Firebase\JWT\JWT;
readonly class PrivateKeyJwtGenerator
{
public const string ALG = 'RS256';
public const string KID = 'rsa-sign';
public function __construct(
private string $clientId,
private string $clientJwtKey,
private string $tokenUrl,
) {
}
public function generate(): string
{
$timestamp = time();
$payload = [
'iss' => $this->clientId,
'sub' => $this->clientId,
'aud' => $this->tokenUrl,
'jti' => bin2hex(random_bytes(16)),
'iat' => $timestamp,
'nbf' => $timestamp - 60,
'exp' => $timestamp + 60,
];
return JWT::encode($payload, $this->clientJwtKey, self::ALG, self::KID);
}
public function __invoke(): string
{
return $this->generate();
}
}
class CustomProvider extends AbstractProvider
{
/**
* The RS256 (openssl) private key used to sign JWTs.
*/
public ?string $clientJwtKey = null;
public function __construct(array $options = [], array $collaborators = [])
{
parent::__construct($options, $collaborators);
if (isset($this->clientJwtKey)) {
$jwtGenerator = new PrivateKeyJwtGenerator(
$this->clientId,
$this->clientJwtKey,
$this->getBaseAccessTokenUrl([]),
);
$this->setOptionProvider(new JwtAuthOptionProvider($jwtGenerator));
}
}
}
I've been working on custom Keycloak and Shibboleth (OIDC) providers that use signed JSON web tokens to authenticate the client instead of a secret. It is based on the specifications in this RFC: https://datatracker.ietf.org/doc/html/rfc7523#section-2.2.
Just like #653, I implemented a new auth option provider that allows you to bring your own JWT generator. Your provider probably already comes with a JWT library that you can easily plug in. This prevents any extra or unnecessary dependencies, and keeps the option provider itself very basic:
I'm using a custom Keycloak provider based on https://github.com/stevenmaguire/oauth2-keycloak, because it does not yet support JWTs for client authentication. I plan on upstreaming to the specific provider but I wanted to check here first for the auth option provider part. There might be other providers, that I'm not yet aware of, that could make us of it too.
I still have some testing to do with my custom providers but if there's any interest in the auth option provider part I would be happy to submit a pull request later on!
The existing Keycloak provider already depends on firebase/php-jwt so this is what the generator looks like using that same library: