Keycloak
has an administration REST API to create realms, clients, users, etc.,
but its documentation can be a bit terse in some places,
making it a bit challenging to connect the dots.
In this notebook I'll try to flesh out the basics using Python and the requests
package.
We'll work against a development Keycloak instance, e.g. running locally in Docker as follows
docker run --rm \
-p 8642:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:21.0.2 \
start-dev
Note that we're remapping port 8080 (to 8642 in this case) to have a lower chance of collision with some other web service that is already running locally.
The Keycloak administration web UI is available at http://localhost:8642/admin, which is handy to follow along while we're trying to do things programmatically.
Ok, let's go¶
import requests
keycloak_root = "http://localhost:8642"
keycloak_admin = "admin"
keycloak_admin_password = "admin"
Get an access token¶
First, we have to authenticate and get an access token for the admin user, which we'll need to do the administration operations.
resp = requests.post(
f"{keycloak_root}/realms/master/protocol/openid-connect/token",
data={
"client_id": "admin-cli",
"username": keycloak_admin,
"password": keycloak_admin_password,
"grant_type": "password"
}
)
resp.raise_for_status()
data = resp.json()
access_token = data["access_token"]
print(f"{access_token[:20]}...{access_token[-20:]}")
print(f"Expires in {data['expires_in']}s")
# Predefine authorization headers for later use.
auth_headers = {
"Authorization": f"Bearer {access_token}",
}
Note that the access token is only valid for a relatively short time (1 minute in this case).
If you execute this notebook flow at your own pace,
you might get HTTPError: 401 Client Error: Unauthorized for url
errors when the access token expired
and have to request new access tokens (e.g. by re-executing the cell above).
Let's verify if we can use the access token (which we already wrapped in a headers dictionary),
by trying to list the available realms (GET /admin/realms
endpoint):
resp = requests.get(
f"{keycloak_root}/admin/realms",
headers=auth_headers,
)
resp.raise_for_status()
[r["realm"] for r in resp.json()]
Create a new realm¶
The Keycloak documentation recommends against using the default realm "master" for your own applications, so we create a new realm first,
with the POST /admin/realms
endpoint:
# Name of our fancy realm.
realm = "keldor"
resp = requests.post(
f"{keycloak_root}/admin/realms",
headers=auth_headers,
json={
"realm": realm,
"enabled": True
}
)
resp.raise_for_status()
Let's see if that worked and list the realms again:
resp = requests.get(
f"{keycloak_root}/admin/realms",
headers=auth_headers,
)
resp.raise_for_status()
[r["realm"] for r in resp.json()]
Create a client¶
To create a client, we use the POST /admin/realms/{realm}/clients
endpoint
and pass it a bunch of desired settings (including but not limited to the settings below):
client_id = "fancy-client"
client_settings = {
"protocol": "openid-connect",
"clientId": client_id,
"enabled": True,
# Public: no client secret. Non-public: "confidential" client with secret.
"publicClient": True,
# Authorization Code Flow
"standardFlowEnabled": True,
# Service accounts: Client Credentials Grant
"serviceAccountsEnabled": False,
# Direct Access: Resource Owner Password Credentials Grant
"directAccessGrantsEnabled": True,
"attributes": {
# Device authorization grant
"oauth2.device.authorization.grant.enabled": True,
}
}
resp = requests.post(
f"{keycloak_root}/admin/realms/{realm}/clients",
json=client_settings,
headers=auth_headers,
)
resp.raise_for_status()
location = resp.headers["Location"]
print(location)
We successfully created a client and received, with a Location
header, a URL to look up its actual settings:
requests.get(
location,
headers=auth_headers,
).json()
Create a user¶
Likewise, we can create a new user as follows.
username = "alice"
password = "wonderland"
user_settings = {
"username": username,
"enabled": True,
"credentials": [{
"type": "password",
"value": password,
"temporary": False,
}]
}
resp = requests.post(
f"{keycloak_root}/admin/realms/{realm}/users",
json=user_settings,
headers=auth_headers,
)
resp.raise_for_status()
location = resp.headers["Location"]
print(location)
Again, using the Location
header after successfully creating the user,
we can inspect the actual user settings:
requests.get(
location,
headers=auth_headers,
).json()
Authenticate the user¶
To put it all together, let's authenticate as the created user, using the created client, in the created realm.
First, get the token endpoint of the realm:
token_endpoint = requests.get(
f"{keycloak_root}/realms/{realm}/.well-known/openid-configuration"
).json()["token_endpoint"]
print(token_endpoint)
Do the token request (here using the password grant for simplicity):
resp = requests.post(
token_endpoint,
data={
"client_id": client_id,
"username": username,
"password": password,
"grant_type": "password",
}
)
resp.raise_for_status()
resp.json()
The end.