I have a script that automatically creates a local Kubernetes cluster (for development purposes), installing a bunch of applications including Keycloak on it. The next step that I need to automate is creating some Keycloak resources. I need to:
- create a realm
- create two clients
- create two users
- assign a specific composite role to each user
- add the users to a specific group
I have managed to create a realm through the API, but creating a client or user is causing errors.
This is my code:
##################### Create variables ########################
LB_ADDRESS="192.168.49.3.nip.io"
KEYCLOAK_URL="https://auth.${LB_ADDRESS}/auth"
REALM_NAME="account-1"
ADMIN_USERNAME="admin"
ADMIN_PASSWORD="aic"
project_id_1="7677"
client_1_id="ais-${project_id_1}"
client_1_name="project${project_id_1}.${LB_ADDRESS}"
user_1_name="myuser"
user_1_password="mypassword"
group_name="$client_1_name"
#################### Create helper functions ####################
function get_access_token() {
curl --insecure -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=password&client_id=admin-cli&username=${ADMIN_USERNAME}&password=${ADMIN_PASSWORD}" | jq -r '.access_token'
}
function create_realm() {
curl --insecure -s -X POST "${KEYCLOAK_URL}/admin/realms" -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d "{
\"realm\": \"$1\",
\"enabled\": true
}"
}
function create_client() {
client_id="$1"
client_name="$2"
curl --insecure -X POST "${KEYCLOAK_URL}/admin/realms/$REALM_NAME/clients" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clientId": "'"$client_id"'",
"name": "'"$client_name"'",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"publicClient": false,
"attributes": {
"validRedirectUris": ["*"],
"webOrigins": ["*"]
}
}'
}
function create_user() {
username="$1"
password="$2"
curl --insecure -X POST "${KEYCLOAK_URL}/admin/realms/$REALM_NAME/users/" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "'"$username"'",
"emailVerified": true,
"serviceAccountsEnabled": true,
"credentials": [{
"type": "password",
"value": "'"$password"'",
"temporary": false
}]
}'
}
function assign_role_to_user() {
client="$1"
username="$2"
role="$3"
curl --insecure -X POST "${KEYCLOAK_URL}/admin/realms/$REALM_NAME/users/${username}/role-mappings/clients/${client}" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '[{
"id": "'"$role"'",
"composite": true
}]'
}
function add_user_to_group() {
group="$1"
username="$2"
curl --insecure -X PUT "${KEYCLOAK_URL}/admin/realms/$REALM_NAME/users/${username}/groups/${group}" \
-H "Authorization: Bearer $ACCESS_TOKEN"
}
################### Access Keycloak API #######################
ACCESS_TOKEN="$(get_access_token)"
create_realm $REALM_NAME
create_client "$client_1_id" "$client_1_name"
create_user "$user_1_name" "$user_1_password"
assign_role_to_user "$client_1_id" "$user_1_name" "my-composite-role"
add_user_to_group "$group_name" "$user_1_name"
The access token is retrieved successfully, the realm is created successfully, but then it fails:
create_client "$client_1_id" "$client_1_name"
results in:
{"error":"HTTP 401 Unauthorized"}
What am I doing wrong?
Edit: Do I maybe have to create an access token for the newly created realm before creating resources inside of it? But that seems impossible because the newly created realm doesn't have an admin account by default and in order to create one, I would need an access token again.
Btw I'm using Keycloak v21.1.1: https://quay.io/repository/keycloak/keycloak?tab=tags&tag=21.1.1
You said:
As a workaround, you might consider using first the master realm's admin account to perform operations in the newly created realm. The master realm's admin account has sufficient privileges to manage other realms.
Then, use the access token obtained from the master realm (with the
admin-cli
client), to perform operations in the new realm: it should carry the necessary permissions.If needed, after setting up the new realm, you can create an admin user specific to that realm. That can be done using the master realm token. Once this new admin user is set up, you can obtain tokens specific to the new realm for subsequent operations.
The
get_master_realm_access_token
function retrieves an access token from the master realm. That token is used for all subsequent API calls.The script creates a new realm and then proceeds to create a client and a user within that realm using the master realm token. It does not create a separate admin user for the new realm, which simplifies the process and uses the master realm's admin privileges to manage the new realm.
The functions
create_realm
,create_client
, etc., are modified to accept an access token as their first argument. That token is the one obtained from the master realm and is used to authorize operations in the new realm.As per dreamcrash's answer, the JSON structures for creating clients and users have been corrected.
If the master realm and admin user was already used, but, as commented, the access token is too short-lived, causing the
HTTP 401 Unauthorized
error, you could, instead of using a single access token for all operations, use a refresh token to obtain a new access token when needed. That approach makes sure you always have a valid token for your operations.If the script takes a long time between obtaining the token and using it, try to minimize this gap. You can fetch the token just before it is needed for each operation. Before making an API call, check if the token is still valid. If it is not, obtain a new one before proceeding.
As a less secure but more convenient approach, you could increase the lifespan of the access token in Keycloak's settings. But... that is generally not recommended for production environments due to security concerns but might be acceptable in a development setup.
The
refresh_access_token
function is used to obtain a new access token using the refresh token whenever necessary. That should help in situations where the access token expires too quickly for the operations to complete successfully.