Skip to main content

How to have a programmatic access to Keycloak's admin API? An Elixir usecase

·707 words·4 mins
elixir keycloak
Author
Fungal
Borned to be fungus, forced to be mushroom. she/her
Table of Contents

For a project I’m currently doing I need to access Keycloak’s admin API from backend for doing management. Since Elixir is my main langage and the one for my project, we will be using Elixir and a few libraries.

  • Keycloak version: ~23.0.0

What we need to know first
#

Keycloak is heavily based on OAuth2, understanding it will help understanding what’s going on. I have an article (>100 lines) in draft that explains OAuth2, maybe I’ll release it one day.

We (me) want to manage Keycloak, if that’s the whole keycloak we have to use the master realm. However if you only need to manage what’s inside a single realm, you can replace this realm by yours. The user we will be using have to be in the right realm, and have the according privileges for our actions.

When you login to the admin web frontend, you use the security-admin-console client. But when you wish having a programmatic access, you can’t use this client, hence the admin-cli client. Without going too deep into the details (I don’t know those to be honest), the main difference is the former only have ‘Authorization Code Flow’ (AuthCode) while the later has ‘Resource Owner Password Credentials Grant’ (Password). What does this mean? Basically with admin-cli you can provide credentials to get your OAuth2 token, that’s what we want for a programmatic access.

Now, how to do it?
#

Let’s start creating a project (you can use your existing one), you will need a supervision tree:

$ mix new --sup keyadm
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/keyadm.ex
* creating lib/keyadm/application.ex
* creating test
* creating test/test_helper.exs
* creating test/keyadm_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd keyadm
    mix test

Run "mix help" for more commands.

Yeah I didn’t thought too much for the name, went with a simple keyadm (Keycloak + Admin). And install:

Obtaining the OAuth2 token
#

Sadly, the OAuth2 library documentation is very light (I want to improve it a little), it quicly lets you on your own figuring out how to use it. While the AuthCode is covered, you have to dig in sources to understand how to use Password. Here is the magic gist that will unlock you the token (more precisely, a struct containing the token, you could use with the OAuth2 library do perfrom authenticated requests):

username = "admin"
password = "hunter2"
base_url = "https://auth.example.com"
realm = "master"

client =
  [
    authorize_url: "/protocol/openid-connect/auth",
    client_id: "admin-cli",
    params: %{
      "username" => username,
      "password" => password
    },
    serializers: %{"application/json" => Jason},
    site: "#{base_url}/realms/#{realm}",
    strategy: OAuth2.Strategy.Password,
    token_url: "/protocol/openid-connect/token"
  ]
  |> OAuth2.Client.new()
  |> OAuth2.Client.get_token!()

Deciphering it:

  • authorize_url: the relative path for auth
  • client_id: the OAuth2 client used, as explained earlier
  • params: HTTP parametres, containing our credentials
  • serializers: mandatory to parse Keycloak’s response, with the associated mime
  • site: the Keycloak’s domain with the realm, used as a base for relative URL
  • strategy: we are doing a Password strategy to auth
  • token_url: the relative part for obtaining the OAuth2 token

I put variables in the head if you need to change to your need. Once executed it will give you a OAuth2.Client struct containg your Access Token, you will have to give to HTTP functions for them to work. Yay!

It used to work, now it’s broken
#

Elixir does no magic in your back, and OAuth2 library neither (the immutability helps to this). However an Access Token by default lives for only 1 minute, it quickly expires! A Refresh Token is provided along the Access Token to get a new one, a lighter procedure than doing the whole initial Access Token retrieve.

Assuming your Refresh Token contained in your OAuth2.Client isn’t expired, you can renew it using the following gist to update the client variable in-place:

client = OAuth2.Client.refresh_token!(client)

How to automatically refresh the Access Token?
#

I assume you wish to maintain an available access to your Keycloak API. The easiest is wrapping the given gists in a GenServer, documentation even gives an example on how to schedule work. The rest is given as an exercise at reader discretion :3