
What are Keycloak realm keys?
Keycloak's authentication protocols make use of private and public keys for signing and encrypting, as described in the official documentation. These keys are realm-specific, and by default managed internally in Keycloak. So, when you create a realm using the Keycloak Admin API, kcadm.sh or manually using the Web UI, new keypair(s) get generated automatically. These are called "Managed keys". However, you can also use custom realm keys in Keycloak, which is what this article is about. Using custom realm keys allow you, for example, to use a single keypair in multiple realms.
Internally Keycloak handles realm keys as "components" and stores them the COMPONENT_CONFIGS table in the database. That's why you won't see any "keypair" or "key" methods in the REST API, nor can you find the keys as properties of the realm. While kcadm.sh does have a "get keys" method, keys can only be added, deleted or modifies as components.
The currently used key - managed or custom - is selected based on the priority of all active keys. So, in this context active does not automatically mean that a realm keypair is being used, just that it is has not been explicitly made passive.
Creating custom realm keys
While managed keys are convenient, you can use custom realm keys in Keycloak. The official documentation on how to do this via kcadm and/or the API is missing, so this blog post aims to fill that void. Here we use kcadm.sh, but the process is very similar for raw API calls.
The first step is to create a keypair (borrowed from here):
$ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
The next step is to create a JSON payload, which should look like this:
{
"name" : "rsa-shared",
"providerId" : "rsa",
"providerType" : "org.keycloak.keys.KeyProvider",
"parentId" : "Test",
"config" : {
"privateKey": ["-----BEGIN PRIVATE KEY-----\n<private-key-body>\n-----END PRIVATE KEY-----"],
"certificate" : [],
"active" : [ "true" ],
"priority" : [ "123" ],
"enabled" : [ "true" ],
"algorithm" : [ "RS256" ]
}
}
A couple of notes regarding the payload:
- The name field is the human-readable name of the key
- The parentId should match the "id" property of the realm the keypair should belong to, which is essentially the realm name.
- The priority field determines which key Keycloak actually uses at any given time
- The privateKey as created above contains the whole keypair (private + public).Keycloak automatically extracts the public key from the contents of this parameter. You do not have to, nor should you, define a publicKey field: if you do, you will not be able to modify some of the paraneters under the "config" hash, such as "enabled" or "priority", while modifying "active" does work. This may have to do with the Keycloak config parser choking at "publicKey" and ignoring the values of parameters that come after it.
To apply the payload use
$ /opt/keycloak/bin/kcadm.sh create components -r test -f payload-add-key.json --no-config --server http://localhost:8080/auth --realm master --user admin --password changeme
Getting custom realm key's id
Keycloak uses unique, typically machine-generated identifiers for various resources: this also applies to custom realm keys in Keycloak. According to kcadm.sh documentation you need the key's "Provider ID" to modify or delete it. This is true, but also very confusing: when looking at the keys in JSON you see a property called "providerId", whose value can be "rsa", "rsa-generated" or such. Using that as part of the URL in Admin REST API calls will just fail. Instead, you should use the key's id as part of the URL and ignore its providerId property completely.
To get the ID for a key list all realm keys and check the value of "id" for your custom realm key:
/opt/keycloak/bin/kcadm.sh get keys -r test --no-config --server http://localhost:8080/auth --realm master --user admin --password changeme
Armed with this information you can now update and remove your custom keys.
Modifying custom realm keys
You can modify custom realm keys in Keycloak with kcadm.sh. The first and easier option is to pass the changes as a value to the -s parameter:
/opt/keycloak/bin/kcadm.sh update components/<provider-id> -r test -s 'config.active=["false"]' --no-config --server http://localhost:8080/auth --realm Test --user admin --password changeme
Alternatively you can make the modification(s) using a JSON payload. In fact, when you're working directly with the Admin REST API that's your only option. Note that in that case the payload you use has to include the full representation of the key object. For example:
{ "id":"<provider-id>",
"name":"rsa-shared",
"providerId":"rsa",
"providerType":"org.keycloak.keys.KeyProvider",
"parentId":"Test",
"config": {
"publicKey" : ["-----BEGIN PUBLIC KEY-----\n<public-key-body>\n-----END PUBLIC KEY-----"],
"privateKey": ["-----BEGIN PRIVATE KEY-----\n<private-key-body>\n-----END PRIVATE KEY-----"],
"active":["true"],
"priority":["101"],
"enabled":["true"],
"algorithm":["RS256"]
}
}
Once you've crafter the payload you can update the key object:
/opt/keycloak/bin/kcadm.sh update components/<provider-id> -r test -f update.json --no-config --server http://localhost:8080/auth --realm Test --user admin --password changeme
Removing custom realm keys
To remove a custom realm key from Keycloak delete the component like this:
/opt/keycloak/bin/kcadm.sh delete components/<provider-id> -r test --no-config --server http://localhost:8080/auth --realm master --user admin --password changeme
Ansible module for managing Keycloak realm keys
If you use Ansible for managing Keycloak you can use Puppeteers' keycloak_realm_key module in the puppeteers.keycloak collection. Here's sample usage:
- name: Manage Keycloak realm key
keycloak_realm_key:
name: custom
state: present
parent_id: master
provider_id: "rsa"
auth_keycloak_url: "http://localhost:8080/auth"
auth_username: keycloak
auth_password: keycloak
auth_realm: master
config:
private_key: "{{ private_key }}"
enabled: true
active: true
priority: 120
algorithm: "RS256"
Note that the private key should be string with literal linefeed characters ('\n') instead of actual linefeeds.