Keycloak gem was developed to integrate applications and services into Red Hat's Keycloak system for user control, authentication, authorization, and session management.
Its development was based on version 3.2 of Keycloak, whose documentation can be found here.
Publication of gem: https://rubygems.org/gems/keycloak
Exemple: https://github.com/imagov/example-gem-keycloak
Add this line in your application's gemfile:
gem 'keycloak'
Install the gem by running:
$ bundle install
Or install it yourself:
$ gem install keycloak
To add the configuration file:
$ rails generate initializer
Since you already have a Keycloak environment configured and the gem already installed, the next step is to define how the application will authenticate. Keycloak works with key authentication protocols, such as OpenID Connect, Oauth 2.0 and SAML 2.0, integrating system access through Single-Sign On, and can also provide access to LDAP or Active Directory users.
When you register a realm and also a Client in your Keycloak environment, you can download the Client installation file into the root folder of the application so that gem gets the information it needs to interact with Keycloak. To download this, simply access your Client's registry, click the Installation tab, select Keycloak OIDC JSON in the Format option field and click Download. If your application does not only work with a specific client (application server for APIs, for example), then you can tell what is the realm that gem will interact in the keycloak.rb
configuration file.
Gem has a main module called Keycloak. Within this module there are three other modules: Client, Admin and Internal.
The Keycloak module has some attributes and its definitions are fundamental for the perfect functioning of the gem in the application.
Keycloak.realm
If your application does not only work with a specific client (application server for APIs, for example), then you can tell the realm name that gem will interact in that attribute. When installed, gem creates the keycloak.rb
file in config / initializers
. This attribute can be found and defined in this file.
Keycloak.auth_server_url
For the same scenario as the above attribute, you can tell the url of the realm that the gem will interact in that attribute. When installed, gem creates the keycloak.rb
file in config / initializers
. This attribute can be found and defined in this file.
Keycloak.proxy
If the environment where your application will be used requires the use of proxy for the consumption of the Keycloak APIs, then define it in this attribute. When it is installed, gem creates the keycloak.rb
file in config/initializers
. This attribute can be found and defined in this file.
Keycloak.generate_request_exception
This attribute is to define whether the HTTP exceptions generated in the returns of the requests made to Keycloak will or will not burst in the application. If set to false
, then the exception will not be blown and the HTTP response will be returned to the application to do its own processing. The default value of this attribute is true
. When it is installed, gem creates the keycloak.rb
file in config/initializers
. This attribute can be found and defined in this file.
Keycloak.keycloak_controller
It is recommended that your application has a controller that centralizes the session actions that Keycloak will manage, such as login action, logout, session update, password reset, among others. Define in this attribute what is the name of the controller that will play this role. If your controller name is SessionController
, then the value of this attribute should be session
only. When it is installed, gem creates the keycloak.rb
file in config/initializers
. This attribute can be found and defined in this file.
Keycloak.proc_cookie_token
This attribute is an anonymous method (lambda). The same must be implemented in the application so that the gem has access to the authentication token which, in turn, must be stored in the cookie. When performing the keycloak authentication through gem, the system must store the token returned in the browser cookie, such as:
cookies.permanent[:keycloak_token] = Keycloak::Client.get_token(params[:user_login], params[:user_password])
The application can retrieve the token in the cookie by implementing the Keycloak.proc_cookie_token
method as follows:
Keycloak.proc_cookie_token = -> do
cookies.permanent[:keycloak_token]
end
This way, every time gem needs to use the token information to consume a Keycloak service, it will invoke this lambda method.
Keycloak.proc_external_attributes
Keycloak gives the possibility that new attributes are mapped to the user registry. However, when these attributes are application specific, it is recommended that you manage them yourself. To do this, the best solution is to create these attributes in the application - example: create a table in the database of the application itself containing the columns representing each of the attributes, also inserting in this table a unique key column, same as the User Id created in Keycloak, indicating that the one belonging to that Id has those attributes.
In order for gem to have access to these attributes, set the Keycloak.proc_external_attributes
attribute to a lambda method by obtaining the attributes of the logged-in user. Example:
Keycloak.proc_external_attributes = -> do
attribute = UsuariosAtributo.find_or_create_by(user_keycloak_id: Keycloak::Client.get_attribute('sub'))
if attribute.status.nil?
attribute.status = false
attribute.save
end
attribute
end
Note: The Keycloak.proc_cookie_token
and Keycloak.proc_external_attributes
attributes can be defined in the initialize
of the controller ApplicationController
.
The Keycloak::Client
module has the methods that represent the endpoint services. These services are fundamental for creating and updating tokens, logging in and logout, and also for obtaining the synthetic information of a logged in user. What enables gem to make use of all these services is the previously mentioned client installation file.
We will detail each of these methods:
Keycloak::Client.get_token(user, password)
If you choose to authenticate users using the screen of your own application, then use this method. Simply invoke it in the method of login in the controller
defined with the session controller of your application, passing as parameter the user and the password informed by the user. If the authentication is valid, then a JSON containing the access_token
and the refresh_token
is returned.
Keycloak::Client.url_login_redirect(redirect_uri, response_type = 'code')
To authenticate the users of your application using a template configured in Keycloak, redirect the request to the url returned in this method. Pass as a parameter the url that the user will have access in case of successful authentication (redirect_uri
) and also the type of response (response_type
), which if not informed, gem will assume the code
value. If the authentication is successful, then a code
will be returned that will enable you to request a token from Keycloak.
Keycloak::Client.get_token_by_code(code, redirect_uri)
When using the Keycloak::Client.url_login_redirect
method to get a code
, pass it as a parameter in this method so that Keycloak returns a token, thus logging the user in the application. The second parameter (redirect_uri
) must be passed so that when a token is made available, Keycloak redirects to the url informed.
Keycloak::Client.get_token_by_refresh_token(refresh_token = '')
When the user is already logged in and your application internally tracks the token expiration time provided by Keycloak, then this method can be used to renew that token if it is still valid. To do this, simply pass the refresh_token
as a parameter. If you do not inform refresh_token
, gem will use the refresh_token
stored in the cookie.
Keycloak::Client.get_token_introspection(token = '')
This method returns the information from the token
session passed as parameter. Among the information returned, the most important is the active
field, since it informs whether the token session passed in the parameter is active or not. This will help your application control whether the logged-in user session has expired or not. If no token is passed as a parameter, gem will use the last access_token
stored in the application's cookie.
Keycloak::Client.get_token_by_client_credentials(client_id = '', secret = '')
There are some Keycloak services like password reset, user registration in the initial screen of the application or even authentication following the standard OAuth 2.0, that the authentication of a user becomes unnecessary. Therefore, we can obtain a token using the credentials of its own application (Client) registered in Keycloak. To obtain this token, pass the client_id
- informed by the person who registered your application in Keycloak - and the secret
of your application generated by Keycloak - to generate a secret
, the Access Type of your Client must be configured as confidential
. If you do not pass any of these parameters, gem will use the credentials contained in the installation file mentioned above.
Keycloak::Client.logout(redirect_uri = '', refresh_token = '')
When used before the expiration of the logged on user's session, this method terminates the session. If the redirect_uri
parameter is fed, then Keycloak will redirect your application to the url informed after logout. The second parameter is refresh_token
, obtained at the time of authentication or session update. If the latter is not informed, then the gem will use the refresh_token
of the cookie
Keycloak::Client.get_userinfo(access_token = '')
This method returns synthetic information from the user represented by the access_token
passed as a parameter, such as sub
- which is the authenticated user id -, preferred_username
- which is the authenticated user name - and email
- which is the user's email address. If the access_token
parameter is not informed, then the gem will get this information in the cookie.
Keycloak::Client.url_user_account
Returns the url for access to the realm user registry of the installation file (keycloak.json
). To access the screen, Keycloak will require user authentication. After connected, and if has permission, the user will have access to his own personal information and could even change them.
Keycloak::Client.has_role?(user_role, access_token = '')
The has_role?
method decodes the JWT access_token
and verifies that the user who owns the token has the role informed in the user_role
parameter. If access_token
is not informed then gem will use the access_token
of the cookie.
Keycloak::Client.user_signed_in?(access_token = '')
This method checks whether the access_token
passed in the parameter is still active. To check whether the user is active or not, the gem invokes the get_token_introspection
method internally. If access_token
is not informed then gem will use the access_token
of the cookie.
Keycloak::Client.get_attribute(attribute_name, access_token = '')
This method decodes the JWT access_token
and returns the value of the name attribute passed in the attribute_name
parameter. This attribute can be a mapper - registered in the Mappers section of the Realm Client registry. If access_token
is not informed then gem will use the access_token
of the cookie.
Keycloak::Client.token
Returns the last authenticated token stored in the cookie. When the Keycloak.proc_cookie_token
method is implemented in the application and a user authenticates the application, this method returns the token of that user.
Keycloak::Client.external_attributes
When the Keycloak.proc_external_attributes
method is implemented, the external_attributes
method returns it. The purpose of this method is to return the application-specific attributes not mapped in Keycloak.
The Keycloak :: Admin
module provides methods that represent the REST APIs do Keycloak. In order to use these APIs, an active access_token
is required, that is, authentication must occur before using the methods for a valid token to be used as a credential. If access_token
is not informed then gem will use the access_token
of the cookie. The authenticated user must have the role
of the respective service invoked - roles of the realm-management
client, which represents the management of the realm.
The list of methods is shown below. The {realm}
route parameter of all APIs will be obtained from the keycloak.json
installation file:
# GET /admin/realms/{realm}/users
Keycloak::Admin.get_users(query_parameters = nil, access_token = nil)
get_users
returns a list of users, filtered according to the parameters hash passed in query_parameters
.
# POST /admin/realms/{realm}/users
Keycloak::Admin.create_user(user_representation, access_token = nil)
create_user
creates a new user in Keycloak. The user_representation
parameter must be a hash according to Keycloak UserRepresentation. The return of this method will be true
for success.
# GET /admin/realms/{realm}/users/count
Keycloak::Admin.count_users(access_token = nil)
count_users
returns the number of users in the realm.
# GET /admin/realms/{realm}/users/{id}
Keycloak::Admin.get_user(id, access_token = nil)
get_user
returns the user representation identified by the id
parameter - which is the ID created by Keycloak when creating a new user.
# PUT /admin/realms/{realm}/users/{id}
Keycloak::Admin.update_user(id, user_representation, access_token = nil)
update_user
updates the user registry identified by id
- which is the ID created by Keycloak when creating a new user. In the user_representation
parameter should be a hash with the fields that will be changed, respecting the UserRepresentation of Keycloak. The return of this method will be true
for success.
# DELETE /admin/realms/{realm}/users/{id}
Keycloak::Admin.delete_user(id, access_token = nil)
delete_user
excludes the user ID identified by the id
- which is the ID created by Keycloak when creating a new user. The return of this method will be true
for success.
# DELETE /admin/realms/{realm}/users/{id}/consents/{client}
Keycloak::Admin.revoke_consent_user(id, client_id = nil, access_token = nil)
revoke_consent_user
revokes the tokens of a user identified by id
- which is the ID created by Keycloak when creating a new user - on the client identified by the client_id
parameter.
# PUT /admin/realms/{realm}/users/{id}/execute-actions-email
Keycloak::Admin.update_account_email(id, actions, redirect_uri = '', client_id = nil, access_token = nil)
update_account_email
sends an account update email to the user represented by the id
parameter. The email contains a link that the user can click to execute a set of actions represented by the actions
parameter - which awaits an array
of actions defined by Keycloak. An example value that can be passed to the actions
parameter is ['UPDATE_PASSWORD']
, which indicates that the action that the user must take when clicking the link in the email is to change their password. In the redirect_uri
parameter, if necessary, a url must be passed so that, at the end of sending the e-mail, the application is redirected. The client_id
parameter should be informed if the Client responsible for the actions to be performed is not the same as the keycloak.json
installation file.
# GET /admin/realms/{realm}/users/{id}/role-mappings
Keycloak::Admin.get_role_mappings(id, access_token = nil)
get_role_mappings
returns all Role Mappings in the realm assigned to the user identified by the id
parameter, regardless of the Client.
# GET /admin/realms/{realm}/clients
Keycloak::Admin.get_clients(query_parameters = nil, access_token = nil)
get_clients
returns a list of ClientRepresentation pertaining to the realm. The query_parameters
parameter expects a hash with clientId
attributes - if you want the list to be filtered by client_id
- and viewableOnly
- to filter whether the Keycloak Administration Clients will be returned in the list.
# GET /admin/realms/{realm}/clients/{id}/roles
Keycloak::Admin.get_all_roles_client(id, access_token = nil)
get_all_roles_client
returns a RoleRepresentation list with all client roles identified by the id
parameter - this parameter must be passed in the ID of the Clint and not client_id
.
# GET /admin/realms/{realm}/clients/{id}/roles/{role-name}
Keycloak::Admin.get_roles_client_by_name(id, role_name, access_token = nil)
get_roles_client_by_name
returns the RoleRepresentation of the role identified by the parameter role_name
- which is the name of the role.
# POST /admin/realms/{realm}/users/{id}/role-mappings/clients/{client}
Keycloak::Admin.add_client_level_roles_to_user(id, client, role_representation, access_token = nil)
add_client_level_roles_to_user
inserts a role from the Client (represented by the client
parameter) to the user represented by the id
parameter. The role_representation
parameter should receive an array
of RoleRepresentation that will be entered into the user. On success, the return will be true
.
# DELETE /admin/realms/{realm}/users/{id}/role-mappings/clients/{client}
Keycloak::Admin.delete_client_level_roles_from_user(id, client, role_representation, access_token = nil)
delete_client_level_roles_from_user
deletes a Client-Role (representado pelo parâmetro client
) of the user represented by the id
parameter. The role_representation
parameter should receive an array
of RoleRepresentation that will be removed on the user. On success, the return will be true
.
# GET /admin/realms/{realm}/users/{id}/role-mappings/clients/{client}
Keycloak::Admin.get_client_level_role_for_user_and_app(id, client, access_token = nil)
get_client_level_role_for_user_and_app
return a list of RoleRepresentation of client Client-Roles, represented by client
parameter linked to the user represented by the id
parameter.
Keycloak::Admin.update_effective_user_roles(id, client_id, roles_names, access_token = nil)
update_effective_user_roles
is not on the Keycloak Admin APIs list. This method binds to the user represented by the id
parameter all the roles passed in an array
in the roles_names
parameter. The roles passed in the roles_names
parameter must belong to the Client represented by the client_id
parameter. If the user has the link with a role that is not in the roles_names
parameter, this link will be removed because the purpose of this method is for the user to effectively assume the roles passed in this parameter. On success, the return will be true
.
PUT /admin/realms/{realm}/users/{id}/reset-password
Keycloak::Admin.reset_password(id, credential_representation, access_token = nil)
reset_password
change the user password represented by id
parameter. The new password is represented by credential_representation
parameter, which is a set of information formatted under the CredentialRepresentation section of the Keycloak API manual.
GET /admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}/composite
Keycloak::Admin.get_effective_client_level_role_composite_user(id, client, access_token = nil)
get_effective_client_level_role_composite_user
return a list (array) of RoleRepresentation of a Group represented by id
parameter attached to a Client represented by client
parameter.
If there is any service in the manual Keycloak Admin REST API that has not been implemented in this gem, there is a possibility of being invoked using the Generics Methods of the Keycloak::Admin
model. The Generics Methods allow you to request any of the APIs, either GET
, POST
, PUT
or DELETE
, passing the request parameters as hashes
in the parameters query_parameters
and body_parameter
of the Generics Methods.
The following are the Generics Methods:
Keycloak::Admin.generic_get(service, query_parameters = nil, access_token = nil)
generic_get
permite que você faça requisições de serviços GET
do Keycloak. A parte da URI que identifica o serviço deve ser passada no parâmetro service
, já com os parâmetros de rota (como o {client}
, por exemplo) devidamente substituídos. No parâmetro query_parameters
você poderá passar um hash
contendo os Queries Parameters da requisição.
Exemplo:
Keycloak::Admin.generic_get("users/", {email: '[email protected]'}, "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU...")
Keycloak::Admin.generic_post(service, query_parameters, body_parameter, access_token = nil)
generic_post
permite que você faça requisições de serviços POST
do Keycloak. A parte da URI que identifica o serviço deve ser passada no parâmetro service
, já com os parâmetros de rota (como o {client}
, por exemplo) devidamente substituídos. No parâmetro query_parameters
você poderá passar um hash
contendo os Query Parameters da requisição. No parâmetro body_parameter
você poderá passar um hash
contendo os Body Parameters da requisição.
Exemplo:
Keycloak::Admin.generic_post("users/", nil, { username: "admin", email: "[email protected]", enabled: true }, "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU...")
Keycloak::Admin.generic_put(service, query_parameters, body_parameter, access_token = nil)
generic_put
permite que você faça requisições de serviços PUT
do Keycloak. A parte da URI que identifica o serviço deve ser passada no parâmetro service
, já com os parâmetros de rota (como o {client}
, por exemplo) devidamente substituídos. No parâmetro query_parameters
você poderá passar um hash
contendo os Query Parameters da requisição. No parâmetro body_parameter
você poderá passar um hash
contendo os Body Parameters da requisição.
Keycloak::Admin.generic_delete(service, query_parameters = nil, body_parameter = nil, access_token = nil)
generic_delete
permite que você faça requisições de serviços DELETE
do Keycloak. A parte da URI que identifica o serviço deve ser passada no parâmetro service
, já com os parâmetros de rota (como o {client}
, por exemplo) devidamente substituídos. No parâmetro query_parameters
você poderá passar um hash
contendo os Query Parameters da requisição. No parâmetro body_parameter
você poderá passar um hash
contendo os Body Parameters da requisição.
O módulo Keycloak::internal
disponibiliza métodos criados para facilitar a interação entre a aplicação e o Keycloak. Partindo das informações encontradas no arquivo de instalação keycloak.json
, todos os métodos invocados serão autenticados automaticamente, utilizando as credências da aplicação (grant_type = client_credentials
), dependendo assim dos roles atribuídos a mesma para que o retorno da requisição seja autorizado.
Keycloak::Internal.get_users(query_parameters = nil)
get_users
invoca o método Keycloak::Admin.get_users
que, por sua vez, retorna uma lista de usuários, filtrada de acordo com o hash de parâmetros passado em query_parameters
.
Keycloak::Internal.change_password(user_id, redirect_uri = '')
change_password
invocará a API PUT /admin/realms/{realm}/users/{id}/execute-actions-email
do Keycloak requisitando a action UPDATE_PASSWORD
. Isso fará com que o Keycloak dispare um e-mail para o usuário representado pelo parâmetro user_id
. O parâmetro redirect_uri
é opcional. Se não for preenchido, então não haverá nenhum link para clicar após a ação de reset de senha ter sido concluída.
Keycloak::Internal.get_user_info(user_login, whole_word = false)
get_user_info
, baseado no parâmetro user_login
, que poderá recepcionar o username
ou o email
do usuário, retornará uma lista (array) de UserRepresentation no caso em que o parâmetro whole_word
for false
, ou retornará um UserRepresentation quando o parâmetro whole_word
for true
. O parâmetro whole_word
indica se o método deverá considerar usuários que tenham no username
ou email
parte da expressão passada no parâmetro user_login
- para os casos de whole_word = false
-, ou que tenha exatamente a expressão passada nesse parâmetro - para os casos de whole_word = true
.
Keycloak::Internal.forgot_password(user_login, redirect_uri = '')
forgot_password
invocará o método Keycloak::Internal.change_password
após invocar o método Keycloak::Internal.get_user_info
- passando no parâmetro user_login
do método descrito o parâmetro user_login
deste tópico e passando true
no parâmetro whole_word
-. A utilização deste método é indicado para os casos de aplicações permitam o reset da senha dos usuários sem que o mesmo esteja logado.
Keycloak::Internal.exists_name_or_email(value, user_id = '')
exists_name_or_email
verifica se no reino já existe algum usuário com username
ou o email
passado no parâmetro value
. O parâmetro user_id
serve para passar o ID
de um usuário nos casos em que deseja-se alterar o username
ou o email
do mesmo, para que assim sejam considerados na verificação do username
e do email
usuários diferentes do usuário com o ID
informado em user_id
.
Keycloak::Internal.get_logged_user_info
get_logged_user_info
retorna o UserRepresentation do usuário logado na aplicação.
# GET /admin/realms/{realm}/users
Keycloak::Internal.logged_federation_user?
logged_federation_user?
incova o método Keycloak::Internal.get_logged_user_info
e verifica se o mesmo é um Federation User (um usuário do AD por exemplo).
# GET /admin/realms/{realm}/users
Keycloak::Internal.create_starter_user(username, password, email, client_roles_names, proc = nil)
create_starter_user
é indicado para aplicações que permitam a criação de novos usuários sem que um usuário esteja logado ou até mesmo para criar novos usuários a partir do rake db:seed
. Nos parâmetros username
, password
e email
devem ser passados o nome do usuário, a senha do usuário, e o e-mail do usuário, respectivamente. No parâmetro client_roles_names
deve ser passado uma lista (array) com o nome dos roles
do Client que serão atribuídos ao usuário. O parâmetro proc
trata-se de um método lambda que disponibilizará como parâmetro a UserRepresentation do usuário criado para que sejam definidas ações por parte da aplicação. Este método terá como retorno o mesmo retorno do método do parâmetro proc
se o mesmo for definido, caso contrário retornará a UserRepresentation do usuário criado.
Keycloak::Internal.get_client_roles
get_client_roles
retornará uma lista (array) de RoleRepresentation do Client indicado pelo arquivo de instalação keycloak.json
.
Keycloak::Internal.get_client_user_roles(user_id)
get_client_user_roles
invocará o método Keycloak::Admin.get_effective_client_level_role_composite_user
considerando o Client indicado pelo arquivo de instalação keycloak.json
e o usuário representado pelo parâmetro user_id
.
Keycloak::Internal.has_role?(user_id, user_role)
has_role?
informará se o usuário representado pelo parâmetro user_id
possui o role com o nome representado pelo parâmetro user_role
.