javakeycloakkeycloak-admin-client

Using the java keycloak-admin-client, how do I turn on Client authentication and Authorization?


I'm trying to programatically create a Keycloak realm, with associated client on Keycloak 24.0.0 using the library: org.keycloak:keycloak-admin-client:24.0.0.

I want to turn on Client authentication and Authorization for the client.

I'm creating a client, and then subsequently updating it to have authorizationServicesEnabled(true); like so:

ClientRepresentation clientRepresentation = getClientRepresentation(realm);
ClientResource clientResource = getClientResource(realm, clientRepresentation);
clientRepresentation.setAuthorizationServicesEnabled(true);
clientRepresentation.setStandardFlowEnabled(true);
clientRepresentation.setDirectAccessGrantsEnabled(true);
clientRepresentation.setPublicClient(true);
clientRepresentation.setEnabled(true);
clientRepresentation.setServiceAccountsEnabled(true);
clientRepresentation.setAlwaysDisplayInConsole(true);
clientRepresentation.setAttributes(Map.of(
    "oauth2.device.authorization.grant.enabled", "false",
    "oidc.ciba.grant.enabled", "false",
    "login_theme", "base",
    "display.on.consent.screen", "false",
    "backchannel.logout.url", "",
    "backchannel.logout.session.required", "true",
    "backchannel.logout.revoke.offline.tokens", "false"
));
clientRepresentation.setRedirectUris(List.of(publicUrl + "/*"));
clientResource.update(clientRepresentation);

Here is the PUT body that was sent when I stepped through the process in debug:

{
    "id": "de890c6a-7695-4cce-b512-87989059860c",
    "clientId": "my-client",
    "name": "my-client",
    "description": null,
    "rootUrl": null,
    "adminUrl": null,
    "baseUrl": null,
    "surrogateAuthRequired": false,
    "enabled": true,
    "alwaysDisplayInConsole": true,
    "clientAuthenticatorType": "client-secret",
    "secret": null,
    "registrationAccessToken": null,
    "defaultRoles": null,
    "redirectUris": [
        "http://localhost:8080/*"
    ],
    "webOrigins": [],
    "notBefore": 0,
    "bearerOnly": false,
    "consentRequired": false,
    "standardFlowEnabled": true,
    "implicitFlowEnabled": false,
    "directAccessGrantsEnabled": true,
    "serviceAccountsEnabled": true,
    "authorizationServicesEnabled": true,
    "directGrantsOnly": null,
    "publicClient": true,
    "frontchannelLogout": false,
    "protocol": "openid-connect",
    "attributes": {
        "oidc.ciba.grant.enabled": "false",
        "client.secret.creation.time": "1709725705",
        "backchannel.logout.session.required": "true",
        "login_theme": "base",
        "display.on.consent.screen": "false",
        "oauth2.device.authorization.grant.enabled": "false",
        "backchannel.logout.revoke.offline.tokens": "false"
    },
    "authenticationFlowBindingOverrides": {},
    "fullScopeAllowed": true,
    "nodeReRegistrationTimeout": -1,
    "registeredNodes": null,
    "protocolMappers": [
        {
            "id": "a939828f-f524-4d06-aacf-490ea0e2e105",
            "name": "Client IP Address",
            "protocol": "openid-connect",
            "protocolMapper": "oidc-usersessionmodel-note-mapper",
            "consentRequired": false,
            "consentText": null,
            "config": {
                "user.session.note": "clientAddress",
                "introspection.token.claim": "true",
                "id.token.claim": "true",
                "access.token.claim": "true",
                "claim.name": "clientAddress",
                "jsonType.label": "String"
            }
        },
        {
            "id": "b62609d1-330f-445e-ab73-edf78b16f396",
            "name": "Client ID",
            "protocol": "openid-connect",
            "protocolMapper": "oidc-usersessionmodel-note-mapper",
            "consentRequired": false,
            "consentText": null,
            "config": {
                "user.session.note": "client_id",
                "introspection.token.claim": "true",
                "id.token.claim": "true",
                "access.token.claim": "true",
                "claim.name": "client_id",
                "jsonType.label": "String"
            }
        },
        {
            "id": "1d78e050-38eb-4da5-950e-fe5112471ddf",
            "name": "Client Host",
            "protocol": "openid-connect",
            "protocolMapper": "oidc-usersessionmodel-note-mapper",
            "consentRequired": false,
            "consentText": null,
            "config": {
                "user.session.note": "clientHost",
                "introspection.token.claim": "true",
                "id.token.claim": "true",
                "access.token.claim": "true",
                "claim.name": "clientHost",
                "jsonType.label": "String"
            }
        }
    ],
    "clientTemplate": null,
    "useTemplateConfig": null,
    "useTemplateScope": null,
    "useTemplateMappers": null,
    "defaultClientScopes": [
        "web-origins",
        "acr",
        "roles",
        "profile",
        "email"
    ],
    "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
    ],
    "authorizationSettings": null,
    "access": {
        "view": true,
        "configure": true,
        "manage": true
    },
    "origin": null
}

I can see that "authorizationServicesEnabled": true is there, but in the Admin console, it's still not switched on under the Capability Config part of the Client Settings tab:

Screenshot of Client Capability Config

When you turn on those two switches in the Keycloak admin console, it shows a Credentials tab with a Client Id and Secret Client Authenticator, and a Client secret. I think I need to create these programatically and associate them with the ClientRepresentation, but I'm not convinced, as I created a client in the Admin console with the same settings and examined the request payload going to the api and it was the same as above.

So my question is:

How do I replicate what happens when you turn these two switches on in the UI programatically?

Any/all help appreciated.


Solution

  • After examining the Admin Console UI code I could see that the client needs to not be a public client for it to work as expected.

    Removing the line:

    clientRepresentation.setPublicClient(true);
    

    made it all work as expected.