Ruby client that acts as a client for the Keycloak REST API.
This gem basically acts as an url builder using http-client to get responses and serialize them into representation objects.
Warning: This beta gem is currently used for personal use. Most Keycloak Admin features are not implemented yet.
This gem does not require Rails.
For example, using bundle, add this line to your Gemfile.
gem "keycloak-admin", "1.1.4"To login on Keycloak's Admin API, you first need to setup a client.
Go to your realm administration page and open Clients. Then, click on the Create button.
On the first screen, enter:
Client ID: e.g. my-app-admin-clientClient Protocol: selectopenid-connectRoot URL: let it blank
The next screen must be configured depending on how you want to authenticate:
username/passwordwith a user of the realmDirect Access Grantswith a service account
-
In Keycloak, during the client setup:
Access Type:publicorconfidentialService Accounts Enabled(whenconfidential):false- After saving your client, if you have chosen a
confidentialclient, go toCredentialstab and copy theClient Secret
-
In Keycloak, create a dedicated user (and her credentials):
- Go to
Users - Click on the
Add userbutton - Setup her mandatory information, depending on your realm's configuration
- On the
Credentialstab, create her a password (toggle offTemporary)
- Go to
-
In this gem's configuration (see Section
Configuration):- Setup
usernameandpasswordaccording to your user's configuration - Setup
client_idwith yourClient ID(e.g. my-app-admin-client) - If your client is
confidential, copy its Client Secret toclient_secret
- Setup
Using a service account to use the REST Admin API does not require to create a dedicated user (https://www.keycloak.org/docs/latest/server_admin/#_service_accounts).
-
In Keycloak, during the client setup:
Access Type:confidentialService Accounts Enabled(whenconfidential):trueStandard Flow Enabled:falseImplicit Flow Enabled:falseDirect Access Grants Enabled:true- After saving this client
- open the
Service Account Rolesand add relevantrealm-management.client's roles. For instance:view-usersif you want to search for users using this gem. - open the
Credentialstab and copy theClient Secret
- open the
-
In this gem's configuration (see Section
Configuration):- Set
use_service_accounttotrue - Setup
client_idwith yourClient ID(e.g. my-app-admin-client) - Copy its Client Secret to
client_secret
- Set
To configure this gem, call KeycloakAdmin.configure.
For instance, to configure this gem based on environment variables, write (and load if required) a keycloak_admin.rb:
KeycloakAdmin.configure do |config|
config.use_service_account = false
config.server_url = ENV["KEYCLOAK_SERVER_URL"]
config.server_domain = ENV["KEYCLOAK_SERVER_DOMAIN"]
config.client_id = ENV["KEYCLOAK_ADMIN_CLIENT_ID"]
config.client_realm_name = ENV["KEYCLOAK_REALM_ID"]
config.username = ENV["KEYCLOAK_ADMIN_USER"]
config.password = ENV["KEYCLOAK_ADMIN_PASSWORD"]
config.logger = Rails.logger
# You configure RestClient to your liking – see https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb for available options.
config.rest_client_options = { timeout: 5 }
endThis example is autoloaded in a Rails environment.
All options have a default value. However, all of them can be changed in your initializer file.
| Option | Default Value | Type | Required? | Description | Example |
|---|---|---|---|---|---|
server_url |
nil |
String | Required | The base url where your Keycloak server is located (a URL that starts with http and that ends with /auth). This value can be retrieved in your Keycloak client configuration. |
http://auth:8080/auth |
server_domain |
nil |
String | Required | Public domain that identify your authentication cookies. | Â auth.service.io |
client_realm_name |
"" |
String | Required | Name of the realm that contains the admin client. | master |
client_id |
admin-cli |
String | Required | Client that should be used to access admin capabilities. | api-cli |
client_secret |
nil |
String | Optional | If your client is confidential, this parameter must be specified. |
4e3c481c-f823-4a6a-b8a7-bf8c86e3eac3 |
use_service_account |
true |
Boolean | Required | true if the connection to the client uses a Service Account. false if the connection to the client uses a username/password credential. |
false |
username |
nil |
String | Optional | Username to access the Admin REST API. Recommended if user_service_account is set to false. |
mummy |
password |
nil |
String | Optional | Clear password to access the Admin REST API. Recommended if user_service_account is set to false. |
bobby |
logger |
Logger.new(STDOUT) |
Logger | Optional | The logger used by keycloak-admin |
Rails.logger |
rest_client_options |
{} |
Hash | Optional | Options to pass to RestClient |
{ timeout: 5 }Â |
- Get an access token
- Create/update/get/delete a user
- Get list of users, search for user(s)
- Reset credentials
- Impersonate a user
- Exchange a configurable token
- Get list of clients, or find a client by its id or client_id
- Create, update, and delete clients
- Get list of groups, create/save a group
- Get list of roles, save a role
- Get list of realms, save/update/delete a realm
- Get list of client role mappings for a user/group
- Get list of members of a group
- Get list of groups that have a specific role assigned
- Get list of realm-roles assigned to a group, add a realm-role to a group
- Save client role mappings for a user/group
- Save realm-level role mappings for a user/group
- Add a Group on a User
- Remove a Group from a User
- Get list of Identity Providers
- Create Identity Providers
- Link/Unlink users to federated identity provider brokers
- Execute actions emails
- Send forgot passsword mail
- Client Authorization, create, update, get, delete Resource, Scope, Policy, Permission, Policy Enforcer
Returns an instance of KeycloakAdmin::TokenRepresentation.
KeycloakAdmin.realm("a_realm").token.getReturns an instance of KeycloakAdmin::UserRepresentation or nil when this user does not exist.
user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
KeycloakAdmin.realm("a_realm").users.get(user_id)Returns an array of KeycloakAdmin::UserRepresentation.
According to the documentation:
- When providing a
Stringparameter, this produces an arbitrary search string - When providing a
Hash, you can search for specific field (e.g an email)
KeycloakAdmin.realm("a_realm").users.search("a_username_or_an_email")KeycloakAdmin.realm("a_realm").users.search({ email: "[email protected]" })Returns an array of KeycloakAdmin::UserRepresentation.
KeycloakAdmin.realm("a_realm").users.listReturns the provided user, which must be of type KeycloakAdmin::UserRepresentation.
KeycloakAdmin.realm("a_realm").users.save(user)If you want to update its entire entity. To update some specific attributes, provide an object implementing to_json, such as a Hash.
KeycloakAdmin.realm("a_realm").users.update("05c135c6-5ad8-4e17-b1fa-635fc089fd71", {
email: "[email protected]",
username: "hello",
first_name: "Jean",
last_name: "Dupond"
})Attention point: Since Keycloak 24.0.4, when updating a user, all the writable profile attributes must be passed, otherwise they will be removed. (https://www.keycloak.org/docs/24.0.4/upgrading/)
KeycloakAdmin.realm("a_realm").users.delete(user_id)Returns the created user of type KeycloakAdmin::UserRepresentation.
username = "pioupioux"
email = "[email protected]"
password = "acme0"
email_verified = true
locale = "en"
KeycloakAdmin.realm("a_realm").users.create!(username, email, password, email_verified, locale)user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
new_password = "coco"
KeycloakAdmin.realm("a_realm").users.update_password(user_id, new_password)Returns an instance of KeycloakAdmin::ImpersonationRepresentation.
user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
KeycloakAdmin.realm("a_realm").users.impersonate(user_id)To have enough information to execute an impersonation by yourself, get_redirect_impersonation returns an instance of KeycloakAdmin::ImpersonationRedirectionRepresentation.
user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
KeycloakAdmin.realm("a_realm").users.get_redirect_impersonation(user_id)Requires your Keycloak server to have deployed the Custom REST API configurable-token (https://github.com/looorent/keycloak-configurable-token-api)
Returns an instance of KeycloakAdmin::TokenRepresentation.
user_access_token = "abqsdofnqdsogn"
token_lifespan_in_seconds = 20
KeycloakAdmin.realm("a_realm").configurable_token.exchange_with(user_access_token, token_lifespan_in_seconds)Returns an array of KeycloakAdmin::RealmRepresentation.
KeycloakAdmin.realm("master").listTakes realm of type KeycloakAdmin::RealmRepresentation, or an object implementing to_json, such as a Hash.
KeycloakAdmin.realm(nil).save(realm)If you want to update its entire entity. To update some specific attributes, provide an object implementing to_json, such as a Hash.
KeycloakAdmin.realm("a_realm").update({
smtpServer: { host: 'test_host' }
})KeycloakAdmin.realm("a_realm").deleteReturns an array of KeycloakAdmin::ClientRepresentation or a single KeycloakAdmin::ClientRepresentation
Finding a client by its client_id is a somewhat slow operation, as it requires fetching all clients and then filtering. Keycloak's API does not support fetching a client by its client_id directly.
KeycloakAdmin.realm("a_realm").clients.list
KeycloakAdmin.realm("a_realm").clients.get(id) # id is Keycloak's database id, not the client_id
KeycloakAdmin.realm("a_realm").clients.find_by_client_id(client_id)my_client = KeycloakAdmin.realm("a_realm").clients.get(id)
my_client.name = "My new client name"
my_client.description = "This is a new description"
my_client.redirect_uris << "https://www.example.com/auth/callback"
KeycloakAdmin.realm("a_realm").clients.update(client) # Returns the updated clientReturns an array of KeycloakAdmin::GroupRepresentation.
KeycloakAdmin.realm("a_realm").groups.listReturns an array of KeycloakAdmin::GroupRepresentation.
According to the documentation:
- When providing a
Stringparameter, this produces an arbitrary search string - When providing a
Hash, you can specify other fields (e.g q, max, first)
KeycloakAdmin.realm("a_realm").groups.search("MyGroup")KeycloakAdmin.realm("a_realm").groups.search({query: "MyGroup", exact: true, max: 1})Returns the id of saved group provided, which must be of type KeycloakAdmin::GroupRepresentation.
KeycloakAdmin.realm("a_realm").groups.save(group)Returns the id of created group.
group_name = "test"
group_path = "/top"
group_id = KeycloakAdmin.realm("a_realm").groups.create!(group_name, group_path)Create a new group as the child of an existing group.
parent_id = "7686af34-204c-4515-8122-78d19febbf6e"
group_name = "test"
sub_group_id = KeycloakAdmin.realm("a_realm").groups.create_subgroup!(parent_id, group_name)Returns an array of KeycloakAdmin::UserRepresentation.
KeycloakAdmin.realm("a_realm").group("group_id").membersYou can specify paging with first and max:
KeycloakAdmin.realm("a_realm").group("group_id").members(first:0, max:100)Returns an array of KeycloakAdmin::GroupRepresentation
KeycloakAdmin.realm("a_realm").roles.list_groups("role_name")Returns an array of KeycloakAdmin::RoleRepresentation
KeycloakAdmin.realm("a_realm").groups.get_realm_level_roles("group_id")Returns added KeycloakAdmin::RoleRepresentation
KeycloakAdmin.realm("a_realm").groups.add_realm_level_role_name!("group_id", "role_name")Returns an array of KeycloakAdmin::RoleRepresentation.
KeycloakAdmin.realm("a_realm").roles.listTakes role, which must be of type KeycloakAdmin::RoleRepresentation.
KeycloakAdmin.realm("a_realm").roles.save(role)Returns an array of KeycloakAdmin::RoleRepresentation.
user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
client_id = "1869e876-71b4-4de2-849e-66540db3a098"
KeycloakAdmin.realm("a_realm").user(user_id).client_role_mappings(client_id).list_availableor
group_id = "3a63b5c0-ef8a-47fd-86ed-b5fead18d9b8"
client_id = "1869e876-71b4-4de2-849e-66540db3a098"
KeycloakAdmin.realm("a_realm").group(group_id).client_role_mappings(client_id).list_availableTakes role_list, which must be an array of type KeycloakAdmin::RoleRepresentation.
user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
client_id = "1869e876-71b4-4de2-849e-66540db3a098"
KeycloakAdmin.realm("a_realm").user(user_id).client_role_mappings(client_id).save(role_list)or
group_id = "3a63b5c0-ef8a-47fd-86ed-b5fead18d9b8"
client_id = "1869e876-71b4-4de2-849e-66540db3a098"
KeycloakAdmin.realm("a_realm").group(group_id).client_role_mappings(client_id).save(role_list)Takes role_list, which must be an array of type KeycloakAdmin::RoleRepresentation.
user_id = "95985b21-d884-4bbd-b852-cb8cd365afc2"
KeycloakAdmin.realm("a_realm").user(user_id).role_mapper.save_realm_level(role_list)or
group_id = "3a63b5c0-ef8a-47fd-86ed-b5fead18d9b8"
KeycloakAdmin.realm("a_realm").group(group_id).role_mapper.save_realm_level(role_list)Note: This client requires the realm-management.view-identity-providers role.
Returns an array of KeycloakAdmin::IdentityProviderRepresentation.
KeycloakAdmin.realm("a_realm").identity_providers.listIn order to use authorization, you need to enable the client's authorization_services_enabled attribute.
client_id = "dummy-client"
client = KeycloakAdmin.realm("realm_a").clients.find_by_client_id(client_id)
client.authorization_services_enabled = true
KeycloakAdmin.realm("a_realm").clients.update(client)Returns added KeycloakAdmin::ClientAuthzScopeRepresentation
KeycloakAdmin.realm("a_realm").authz_scopes(client_id).create!("POST_1", "POST 1 scope description", "http://icon.url")Returns array of KeycloakAdmin::ClientAuthzScopeRepresentation
KeycloakAdmin.realm("a_realm").authz_scopes(client.id).search("POST")Returns KeycloakAdmin::ClientAuthzScopeRepresentation
KeycloakAdmin.realm("a_realm").authz_scopes(client.id).get(scope_id)KeycloakAdmin.realm("a_realm").authz_scopes(client.id).delete(scope.id)note: for scopes, use {name: scope.name} to reference the scope object
Returns added KeycloakAdmin::ClientAuthzResourceRepresentation
KeycloakAdmin.realm("realm_id")
.authz_resources(client.id)
.create!(
"Dummy Resource",
"type",
["/resource_1/*", "/resource_1/"],
true,
"display_name",
[ {name: scope_1.name} ],
{"attribute": ["value_1", "value_2"]}
)Returns updated KeycloakAdmin::ClientAuthzResourceRepresentation
note: for scopes, use {name: scope.name} to reference the scope object
KeycloakAdmin.realm("realm_a")
.authz_resources(client.id)
.update(resource.id,
{
"name": "Dummy Resource",
"type": "type",
"owner_managed_access": true,
"display_name": "display_name",
"attributes": {"a":["b","c"]},
"uris": [ "/resource_1/*" , "/resource_1/" ],
"scopes":[
{name: scope_1.name},
{name: scope_2.name}
],
"icon_uri": "https://icon.url"
})Returns array of KeycloakAdmin::ClientAuthzResourceRepresentation
KeycloakAdmin.realm("realm_a").authz_resources(client.id).find_by("Dummy Resource", "", "", "", "")or
KeycloakAdmin.realm("realm_a").authz_resources(client.id).find_by("", "type", "", "", "")Returns KeycloakAdmin::ClientAuthzResourceRepresentation
KeycloakAdmin.realm("realm_a").authz_resources(client.id).get(resource.id)KeycloakAdmin.realm("realm_a").authz_resources(client.id).delete(resource.id)Note: for the moment only role policies are supported.
Returns added KeycloakAdmin::ClientAuthzPolicyRepresentation
KeycloakAdmin.realm("realm_a")
.authz_policies(client.id, 'role')
.create!("Policy 1",
"description",
"role",
"POSITIVE",
"UNANIMOUS",
true,
[{id: realm_role.id, required: true}]
)Returns array of KeycloakAdmin::ClientAuthzPolicyRepresentation
KeycloakAdmin.realm("realm_a").authz_policies(client.id, 'role').find_by("Policy 1", "role") Returns KeycloakAdmin::ClientAuthzPolicyRepresentation
KeycloakAdmin.realm("realm_a").authz_policies(client.id, 'role').get(policy.id) KeycloakAdmin.realm("realm_a").authz_policies(client.id, 'role').delete(policy.id)Returns added KeycloakAdmin::ClientAuthzPermissionRepresentation
KeycloakAdmin.realm("realm_a")
.authz_permissions(client.id, :resource)
.create!("Dummy Resource Permission",
"resource description",
"UNANIMOUS",
"POSITIVE",
[resource.id],
[policy.id],
nil,
""
)Returns added KeycloakAdmin::ClientAuthzPermissionRepresentation
KeycloakAdmin.realm("realm_a")
.authz_permissions(client.id, :scope)
.create!("Dummy Scope Permission",
"scope description",
"UNANIMOUS",
"POSITIVE",
[resource.id],
[policy.id],
[scope_1.id, scope_2.id],
""
) Return array of KeycloakAdmin::ClientAuthzPermissionRepresentation
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "", resource.id).listReturn array of KeycloakAdmin::ClientAuthzPermissionRepresentation
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'resource').listReturn array of KeycloakAdmin::ClientAuthzPermissionRepresentation
authz_permissions(client.id, 'scope').list.sizeReturn array of KeycloakAdmin::ClientAuthzPermissionRepresentation
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(resource_permission.name, nil)or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(resource_permission.name, nil)or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(resource_permission.name, resource.id)or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(scope_permission.name, resource.id)or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(scope_permission.name, resource.id, "POST_1")or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(nil, resource.id)or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(nil, resource.id)or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(nil, resource.id, "POST_1")or
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(scope_permission.name, nil)KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'scope').delete(scope.id) KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'resource').delete(resource_permission.id)From the keycloak-admin-api directory:
$ docker build . -t keycloak-admin:test
$ docker run -v `pwd`:/usr/src/app/ keycloak-admin:test rspec spec