Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: allow authenticating with a service account (client-id/-secret) #137

Open
mm-igefa opened this issue Dec 13, 2024 · 3 comments

Comments

@mm-igefa
Copy link

Would it be possible to add the option to authenticate with the server via a client-id/-secret instead of admin-credentials?

official docs:
https://www.keycloak.org/docs/latest/server_development/index.html#authenticating-with-a-service-account

@fschmtt
Copy link
Owner

fschmtt commented Dec 14, 2024

Hey, yes surely possible, but not without taking on some effort and probably some necessary refactoring.

I started doing exactly that a while back, but as there was no demand so far, I didn't follow up on it.

I might continue or start over again, but feel free to provide a PR if you like 😄

@giolvani
Copy link

giolvani commented Jan 3, 2025

It would be handy to authenticate with clientId/secret instead of the credentials. I will try to create a PR to handle that.

@TimmayNL
Copy link

Hi,

I found this library and would like to use it, but I also need support for client-id/client-secret authentication (client credentials flow).

I'm willing to work on a PR but would appreciate feedback on the best approach.

Proposed Changes

My first idea is to change the Keycloak constructor signature from

public function __construct(
        private readonly string $baseUrl,
        private readonly string $username,
        private readonly string $password,
        private readonly TokenStorageInterface $tokenStorage = new InMemory(),
        ?ClientInterface $guzzleClient = new GuzzleClient(),
    ) 

to

public function __construct(
        private readonly string $baseUrl,
        private readonly AuthMethodInterface $authMethod,
        private readonly TokenStorageInterface $tokenStorage = new InMemory(),
        ?ClientInterface $guzzleClient = new GuzzleClient(),
    )

and pass the AuthMethodInterface down to the Client.

The interface could look like:

interface AuthMethodInterface
{
    /**
     * Returns form parameters for fetching an access token.
     *
     * @return array<string, string>
     */
    public function getFetchTokenFormParams(): array;

    /**
     * Returns form parameters for refreshing an access token.
     *
     * @return array<string, string>
     */
    public function getRefreshTokenFormParams(): array;
}

And the Client::fetchTokens will than look like:

try {
            $response = $this->httpClient->request(
                'POST',
                $this->keycloak->getBaseUrl() . '/realms/master/protocol/openid-connect/token',
                [
                    'form_params' => $this->authMethod->getRefreshTokenFormParams(),
                ],
            );
        } catch (ClientException $e) {
            $response = $this->httpClient->request(
                'POST',
                $this->keycloak->getBaseUrl() . '/realms/master/protocol/openid-connect/token',
                [
                    'form_params' => $this->authMethod->getFetchTokenFormParams(),
                ],
            );
        }

This would allow adding Password and ClientCredentials authentication classes implementing AuthMethodInterface. The constructor could then be used like this:

$keycloak = new Keycloak(
    $_SERVER['KEYCLOAK_BASE_URL'] ?? 'http://keycloak:8080',
    new \Fschmtt\Keycloak\OAuth\AuthMethod\Password('admin', 'admin'),
);

Alternative Approach

Since modifying the constructor is a big breaking change, I also considered a compromise by adding the AuthMethodInterface as extra argument after the already existing arguments.

public function __construct(
        private readonly string $baseUrl,
        private readonly string $username,
        private readonly string $password,
        private readonly TokenStorageInterface $tokenStorage = new InMemory(),
        ?ClientInterface $guzzleClient = new GuzzleClient(),
        private readonly AuthMethodInterface $authMethod = new Password(),
    )

The AuthMethodInterface would then include parameters for username and password:

interface AuthMethodInterface
{
    /**
     * Returns form parameters for fetching an access token.
     *
     * @return array<string, string>
     */
    public function getFetchTokenFormParams(string $username, string $password): array;

    /**
     * Returns form parameters for refreshing an access token.
     *
     * @return array<string, mixed>
     */
    public function getRefreshTokenFormParams(string $username, string $password): array;
}

When using client credentials, the username and password parameters would accept the client ID and client secret.

Request for Feedback

  • Do you prefer the first approach, even though it introduces a breaking change?
  • Would the alternative approach be more suitable to maintain backward compatibility?
  • Do you have any other suggestions?

Looking forward to your thoughts!

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants