Create and configure Keycloak OAuth 2.0 authorization server

If you want to know how to set up and configure Keycloak as an authorization server (for OAuth 2.0 framework) with Docker this article is for you.

  • protected resource (Java, Spring Boot, backend application) — an application that serves information using REST API, but requires a valid access token for security reasons,
  • client (Angular, frontend application) — a website, which requires user to be logged in order to be able to get access token and query the backend app.

Running Keycloak instance

Before adding any project-specific configuration I’ve configured my OS’s hosts file, so that I’ll have a convenient URL address for testing. The location of this file depends on the OS:

127.0.0.1	keycloak
version: "3.8"
services:
postgres:
image: postgres:13.0-alpine
container_name: postgres
ports:
- 5432:5432
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres:/var/lib/postgresql/data
- ./infra/postgres:/docker-entrypoint-initdb.d
volumes:
postgres:
CREATE USER keycloak WITH ENCRYPTED PASSWORD 'keycloak';
CREATE DATABASE keycloak;
GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;
keycloak:
image: jboss/keycloak:11.0.2
container_name: keycloak
ports:
- 8080:8080
environment:
- KEYCLOAK_USER=admin
- KEYCLOAK_PASSWORD=admin
- DB_VENDOR=postgres
- DB_ADDR=postgres
- DB_DATABASE=keycloak
- DB_USER=keycloak
- DB_PASSWORD=keycloak
depends_on:
- postgres
version: "3.8"
services:

keycloak:
image: jboss/keycloak:11.0.2
container_name: keycloak
ports:
- 8080:8080
environment:
- KEYCLOAK_USER=admin
- KEYCLOAK_PASSWORD=admin
- DB_VENDOR=postgres
- DB_ADDR=postgres
- DB_DATABASE=keycloak
- DB_USER=keycloak
- DB_PASSWORD=keycloak
depends_on:
- postgres
postgres:
image: postgres:13.0-alpine
container_name: postgres
ports:
- 5432:5432
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres:/var/lib/postgresql/data
- ./infra/postgres:/docker-entrypoint-initdb.d
volumes:
postgres:
> docker-compose up -d keycloak
Creating volume "keycloak-security-example_postgres" with default driver
Creating volume "keycloak-security-example_prometheus" with default driver
Pulling postgres (postgres:13.0-alpine)...
13.0-alpine: Pulling from library/postgres
188c0c94c7c5: Pull complete
56f1d1b70e7f: Pull complete
9b4f01476d2b: Pull complete
16419214bc02: Pull complete
4886fc567835: Pull complete
9026d4fbeafa: Pull complete
001c336294eb: Pull complete
8abc6d154e9f: Pull complete
Digest: sha256:d26ddee3648a324a9747b3257236322141920d5f9a82ca703def6bff1cca7067
Status: Downloaded newer image for postgres:13.0-alpine
Pulling keycloak (jboss/keycloak:11.0.2)...
11.0.2: Pulling from jboss/keycloak
0fd3b5213a9b: Pull complete
aebb8c556853: Pull complete
ed3ae09abceb: Pull complete
85e3448ea914: Pull complete
9f0e9c75b3b9: Pull complete
Digest: sha256:8cdd41cb4a0b210ed3f07df5d18306762e1755bb8d6c1ffc5e083c080528783d
Status: Downloaded newer image for jboss/keycloak:11.0.2
Creating postgres ... done
Creating keycloak ... done
> docker ps
CONTAINER ID IMAGE STATUS NAMES
f9a67e3b9756 jboss/keycloak:11.0.2 Up 33 seconds keycloak
5d781b2b8d6f postgres:13.0-alpine Up 34 seconds postgres

Adding realm, client, roles and users

As we have a confirmation that Keycloak is running, let’s login to it. Therefore, in your browser go to http://keycloak:8080, it will lead you to a home page.

  • passowrd: admin

Create a client credentials

Because we’re building an authentication & authorization based on OAuth 2.0 and OpenID we’ll have two different applications — protected resource that is a backend application that servers some data and a client application, a frontend that will want to access data from protected resource. To do that frontend application will need to have an access token. And to obtain that a client needs to be registered (has client id and client secret) on the authorization server side, Keycloak in our case.

  • scope: openid
  • client_id: test_client
  • client_secret: 8ac27a39-fa84-46b9-8c30-b485056e0cea
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJXYmdnRkNBNzJ4UG5KYWNUZTRHMzdEN1NDWFpJOW8wQVZMTWd0d0tfamhRIn0.eyJleHAiOjE2MTM2Mjc1NzYsImlhdCI6MTYxMzYyNzI3NiwianRpIjoiZjkzODVlMDMtYzRjOC00NzY0LWIyMDgtYzBhYWFiMjIyODEyIiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvYXV0aC9yZWFsbXMvdGVzdCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIwODhjNTdiMy1mNGJlLTQwOTQtOGQzNC00Y2UyNWQ1OGUzY2IiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0X2NsaWVudCIsInNlc3Npb25fc3RhdGUiOiJlMTYxMTNjZi0wZGI5LTRlZDMtOGJjMC0yNWRmNDY4MDU2NjkiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJ0ZXN0X2NsaWVudCI6eyJyb2xlcyI6WyJ1bWFfcHJvdGVjdGlvbiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SWQiOiJ0ZXN0X2NsaWVudCIsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LXRlc3RfY2xpZW50IiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4xOC4wLjEifQ.ihCQGFWRdr1NR7el7zsnnXZFwonOpFgEE_MHBlYSq07s2JhjsLJauvQ9erTM5YV_gmY-Q-QpmpRI-qq4miF9hem8qxXzxBkei7cXyYYQeiz44MwkusUW75VChfsYljgRNUSJM5G4_O636xWQNc2ET5v8508CPpgVIvl4QFKYQui3J2BADH8AyDihgaOcF5hfrutuEpH6AINvBmUebUKOLG3ZyST81Q3GjmSZmBaL7RK29uTm94i1HVqfXgRLIuf5zxLucq_gU4KM8c3mB5d8ZfJOMl8nOnYDbEbiGVEP_coz9x3iF2Lf3Fp8K2zez59w4yvGTy39Whns-KhtX02yAQ",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjZGQ0ZTczOC0zMDU1LTQ5YjAtOTM2Mi00NjAyNTZiNTdkNTQifQ.eyJleHAiOjE2MTM2MjkwNzYsImlhdCI6MTYxMzYyNzI3NiwianRpIjoiMDhmZjgyYjktNTZhZS00MjQ0LTg4MWEtYjY2MmU5NDU2YWFiIiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvYXV0aC9yZWFsbXMvdGVzdCIsImF1ZCI6Imh0dHA6Ly9rZXljbG9hazo4MDgwL2F1dGgvcmVhbG1zL3Rlc3QiLCJzdWIiOiIwODhjNTdiMy1mNGJlLTQwOTQtOGQzNC00Y2UyNWQ1OGUzY2IiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoidGVzdF9jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiZTE2MTEzY2YtMGRiOS00ZWQzLThiYzAtMjVkZjQ2ODA1NjY5Iiwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSJ9._aclvBR9ij5B6Zq1EO7g_5RoZA6pK6SqvMlj1Sb5ero",
"token_type": "bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJXYmdnRkNBNzJ4UG5KYWNUZTRHMzdEN1NDWFpJOW8wQVZMTWd0d0tfamhRIn0.eyJleHAiOjE2MTM2Mjc1NzYsImlhdCI6MTYxMzYyNzI3NiwiYXV0aF90aW1lIjowLCJqdGkiOiI4ODNiMzM2YS01YTU3LTQzOTItODY2YS1kOGU5ZDI4ZDQwNTAiLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODA4MC9hdXRoL3JlYWxtcy90ZXN0IiwiYXVkIjoidGVzdF9jbGllbnQiLCJzdWIiOiIwODhjNTdiMy1mNGJlLTQwOTQtOGQzNC00Y2UyNWQ1OGUzY2IiLCJ0eXAiOiJJRCIsImF6cCI6InRlc3RfY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImUxNjExM2NmLTBkYjktNGVkMy04YmMwLTI1ZGY0NjgwNTY2OSIsImF0X2hhc2giOiJxaDJmQ25ISXBMYWV3LVRPYTZOWGdRIiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SWQiOiJ0ZXN0X2NsaWVudCIsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LXRlc3RfY2xpZW50IiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4xOC4wLjEifQ.TzbQRhk_z0YqfUnDi2T4iYHtw0flKTWh4MNcsAX3gqDV3GbxS2I0h45Z56ZurtPHf1idFKLo44mK-vopR3pt0WSzZx4U9_kQWPOK8BvoUIlft4lGNzgEU_wVIKG3B7RBCq82DVfC6b6g8yGI9D8xXkPCApwqxPoGVs1eTizCWubuRjddQzZTZ5Ida4vvPLXhRwVMNYMEb7h_4Oy8GC2zGNulBCUMpUDmLkqfSaOzKHwn8gOBJABIdmAqB5GbVDtfwpPdjk4exAohU8Gd6YcMSo2H94ZGJtEWZyZw_hOUbF1g5t9rxNnWCDuRkzZBFmVOeqzUZ3n-YQjnYJrpyQG8tg",
"not-before-policy": 0,
"session_state": "e16113cf-0db9-4ed3-8bc0-25df46805669",
"scope": "openid email profile"
}

Create a user and roles

Once we registered a client in Keycloak, we need to create users (resource owners) and give them different roles to differentiate what actions on protected resource they can perform.

  • scope: openid
  • client_id: test_client
  • client_secret: 8ac27a39-fa84-46b9-8c30-b485056e0cea
  • username: luke
  • password: password

Single-click set up

It’s awesome that we’ve got authorization up and running, but here is the problem — how to avoid these manual steps in future? Nothing more simple, we can export realm that we have just created!

keycloak:
image: jboss/keycloak:11.0.2
container_name: keycloak
ports:
- 8080:8080
volumes:
- ./:/temp
depends_on:
- postgres
> docker exec -it keycloak /opt/jboss/keycloak/bin/standalone.sh
-Djboss.socket.binding.port-offset=100
-Dkeycloak.migration.action=export
-Dkeycloak.migration.provider=singleFile
-Dkeycloak.migration.realmName=test
-Dkeycloak.migration.usersExportStrategy=REALM_FILE
-Dkeycloak.migration.file=/temp/realm-test.json
keycloak:
image: jboss/keycloak:11.0.2
container_name: keycloak
ports:
- 8080:8080
environment:
- KEYCLOAK_IMPORT=/tmp/realm-test.json
volumes:
- ./infra/keycloak/realm-test.json:/tmp/realm-test.json
command: ["-Dkeycloak.profile.feature.upload_scripts=enabled"]
depends_on:
- postgres
  • Create and configure Keycloak OAuth 2.0 authorization server (this one)
  • Implementing OAuth 2.0 access token validation with Spring Security (soon)
  • Step-by-step guide how integrate Keycloak with Angular application (soon)

Java Software Developer, DevOps newbie, constant learner, podcast enthusiast.