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.3"
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-connect
Root URL
: let it blank
The next screen must be configured depending on how you want to authenticate:
username/password
with a user of the realmDirect Access Grants
with a service account
-
In Keycloak, during the client setup:
Access Type
:public
orconfidential
Service Accounts Enabled
(whenconfidential
):false
- After saving your client, if you have chosen a
confidential
client, go toCredentials
tab and copy theClient Secret
-
In Keycloak, create a dedicated user (and her credentials):
- Go to
Users
- Click on the
Add user
button - Setup her mandatory information, depending on your realm's configuration
- On the
Credentials
tab, create her a password (toggle offTemporary
)
- Go to
-
In this gem's configuration (see Section
Configuration
):- Setup
username
andpassword
according to your user's configuration - Setup
client_id
with 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
:confidential
Service Accounts Enabled
(whenconfidential
):true
Standard Flow Enabled
:false
Implicit Flow Enabled
:false
Direct Access Grants Enabled
:true
- After saving this client
- open the
Service Account Roles
and add relevantrealm-management.
client's roles. For instance:view-users
if you want to search for users using this gem. - open the
Credentials
tab and copy theClient Secret
- open the
-
In this gem's configuration (see Section
Configuration
):- Set
use_service_account
totrue
- Setup
client_id
with 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 }
end
This 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.get
Returns 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
String
parameter, 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.list
Returns 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").list
Takes 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").delete
Returns 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 client
Returns an array of KeycloakAdmin::GroupRepresentation
.
KeycloakAdmin.realm("a_realm").groups.list
Returns an array of KeycloakAdmin::GroupRepresentation
.
According to the documentation:
- When providing a
String
parameter, 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").members
You 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.list
Takes 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_available
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).list_available
Takes 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.list
In 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).list
Return array of KeycloakAdmin::ClientAuthzPermissionRepresentation
KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'resource').list
Return array of KeycloakAdmin::ClientAuthzPermissionRepresentation
authz_permissions(client.id, 'scope').list.size
Return 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