Quick Look at Authentication with Keycloak

Keycloak is an open source identity and access management application. Put it simply, it can be used to add authentication to applications and secure services.
I've just started taking a look at it, so I will write about how I tested OpenID Connect's (OIDC) Authorization Code Flow using Keycloak as the authorization server. This is a very common flow we see when applications allow us to login with our Google or Facebook credentials.
Explaining OpenID Connect is not the goal of this article, so I will leave an extremely succinct and well explained article about the subject:
https://www.simpleorientedarchitecture.com/openid-connect-in-a-nutshell/
Running Keycloak
I've run Keycloak on Docker on my local machine. Make sure Docker is installed and start Keycloak with the following command (please adapt it to your needs):
$ docker run -d --name=keycloak --restart unless-stopped -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:15.0.2
Just access http://localhost:8080 to be presented with the welcome page:

Just click on Administration Console and you can login with username admin and password admin as set in the Docker command. In this console, we can add users and register applications to be secured by Keycloak.
Realms
A realm represents the top level grouping mechanism in Keycloak. It is a space where we can create isolated groups of users and applications. By default, we have the master realm that contains the admin user we used to login to the console. But, we should not use it for our own applications and only use it to create other realms.
So, let's create our realm by hovering over the Master menu and clicking Add realm :

Type demo in the Name field and click Create .
Users
Let's create a user in the newly created realm. Click Users in the sidebar and then Add user in the top-right corner of the page.

And create a user as shown in the image above. In the user details, select the Credentials tab to set its password. We can disable the Temporary option to make it permanent since we are just testing the functionality. If this is enabled, the user needs to set a new password after logging in.
Clients
Now, we're going to create a new client. Clients are the applications that will use Keycloak as their authentication system. Click Clients in the sidebar and Create in the top-right corner of the page.

And add a client called test_client_app as shown above. I've set an arbitrary Root URL since I just want to simulate the login flow without actually having an application for that.

We want to make sure the Standard Flow Enabled option is turned on to make sure we can use the Authorization Code Flow. Let's also enable the Consent Required option, so the end-user needs to explicitly consent to sharing his/her information to the client application. Also, set Access Type as confidential , so our client application needs to provide a secret when interacting with the OIDC endpoints.
Testing the Authorization Code Flow
We can finally test the Authorization Code Flow. Here, we are simulating the user we created clicking in the login button in the test_client_app. A client app will redirect the user to a link like below, the authorization endpoint, so the person can login to Keycloak. For our test, let's open this URL in the browser.
http://localhost:8080/auth/realms/demo/protocol/openid-connect/auth?client_id=test_client_app&response_type=code&state=a_random_state_to_validate_the_callback
Then we can type our credentials to sign in:

The consent screen will be displayed as we are used to when using Google or Facebook to login to other services.

Clicking Yes will redirect us to an error page. This is expected since Keycloak redirect us to the Root URL we configured previously and we don't have an actual client application to test. The URL of the page we are redirect to will be something like below:
http://localhost:9090/?state=a_random_state_to_validate_the_callback&session_state=f526307d-8bf0-4706-b308-2309ddc1c95a&code=25f7896f-12ef-499b-a8eb-85364e068379.f526307d-8bf0-4706-b308-2309ddc1c95a.b0f608c3-ccd2-4923-9ade-f785c6237cc3
state is the random value we set when accessing the login page. Our client application can check it now to make sure it matches the initial request. The most significant parameter here is the code parameter we can exchange by an access token.
Access Token
We can exchange the code received previously by an access token. We do that by making a request to the token endpoint. To check the available endpoints in our realm, we can open the following URL in the browser. This is the identity provider metadata endpoint.
http://localhost:8080/auth/realms/demo/.well-known/openid-configuration
So, to call the token endpoint, let's run the following request in a terminal:
curl -X POST 'http://localhost:8080/auth/realms/demo/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code' \
-d 'client_id=test_client_app' \
-d 'client_secret=9797d273-a3e2-4269-85b6-6deec57bdc40' \
-d 'code=25f7896f-12ef-499b-a8eb-85364e068379.f526307d-8bf0-4706-b308-2309ddc1c95a.b0f608c3-ccd2-4923-9ade-f785c6237cc3' \
-d 'redirect_uri=http://localhost:9090/callback'
The client_secret can be found in the Credentials tab in the client details page as below, if the Access Type was set to confidential as described in the initial steps.

The response will be a JSON and contain the access_token :
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItUHVFWHk4N09ucko5OFpzZjRXRlEyY1VhTmFlQ2JtTXFxSGY5TnpRVEdNIn0.eyJleHAiOjE2Mzg0NjAzMzIsImlhdCI6MTYzODQ2MDAzMiwiYXV0aF90aW1lIjoxNjM4NDYwMDA0LCJqdGkiOiIyMmY5MDg5Mi01MDE1LTRiMmItYjkxNS03YWEyYTlkZGRhMmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJjMjk2N2JmNy0zYmFmLTQzYzQtOGFlYS1kZmVlZDAxODUyZWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0X2NsaWVudF9hcHAiLCJzZXNzaW9uX3N0YXRlIjoiODQzYWM5NWUtOTE5YS00ZmVhLTlkM2QtODhiMzY0MjRlZmI3IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjkwOTAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1kZW1vIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI4NDNhYzk1ZS05MTlhLTRmZWEtOWQzZC04OGIzNjQyNGVmYjciLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJUZXN0IFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0X3VzZXIiLCJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciJ9.g_v6DA9foM1TBUUV46wclDbkSYrFWJ1S6VWXuA1lj6AY1T4SIFcGrGDibCdT1N1s01HsU1qDwJohLcQpBmojIbgeLHuIK_K2AeDC99lE0yVFvxxoH-rfeAyHDJf0Vqqk_GWQU0hl3VYhDEb-ZBstkFwv0Vuu1C9nw6a_w1POoEACMppz-0WROeXhQa5_D8yqGHSC70jZ0vUVFCL7RiVeXDaV9J7kyoSr4bQYhJ1bHU9kQwq-yo0eSLxCnmQmYFa3yEDJKSCTups7A1FrXTuNTG8XUT1pfa_YVh51g4_u_HEsahwl8mSSs_Y56TLI5LNOBKj-tH56aDxCoLx8XMWYJQ",
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyYmUwYjMxYS0wZTUwLTQzMWEtYjZiNi1jMmJkZDFhZDk0MTgifQ.eyJleHAiOjE2Mzg0NjE4MzIsImlhdCI6MTYzODQ2MDAzMiwianRpIjoiOGJlYjA5NDktODRkYi00NzI1LTlmN2MtMjc0YzU5NWFmYzkxIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2RlbW8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsInN1YiI6ImMyOTY3YmY3LTNiYWYtNDNjNC04YWVhLWRmZWVkMDE4NTJlYiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJ0ZXN0X2NsaWVudF9hcHAiLCJzZXNzaW9uX3N0YXRlIjoiODQzYWM5NWUtOTE5YS00ZmVhLTlkM2QtODhiMzY0MjRlZmI3Iiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiODQzYWM5NWUtOTE5YS00ZmVhLTlkM2QtODhiMzY0MjRlZmI3In0.sDfZRV6snD8wGkSZ1h-Asn2FFXDS0DPAXKUss4WgBos",
  "token_type": "Bearer",
  "not-before-policy": 0,
  "session_state": "843ac95e-919a-4fea-9d3d-88b36424efb7",
  "scope": "email profile"
}
Userinfo
Now that we have an access token, we can use it to make a request to the userinfo endpoint:
curl 'http://localhost:8080/auth/realms/demo/protocol/openid-connect/userinfo' \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItUHVFWHk4N09ucko5OFpzZjRXRlEyY1VhTmFlQ2JtTXFxSGY5TnpRVEdNIn0.eyJleHAiOjE2Mzg0NjAzMzIsImlhdCI6MTYzODQ2MDAzMiwiYXV0aF90aW1lIjoxNjM4NDYwMDA0LCJqdGkiOiIyMmY5MDg5Mi01MDE1LTRiMmItYjkxNS03YWEyYTlkZGRhMmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJjMjk2N2JmNy0zYmFmLTQzYzQtOGFlYS1kZmVlZDAxODUyZWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0X2NsaWVudF9hcHAiLCJzZXNzaW9uX3N0YXRlIjoiODQzYWM5NWUtOTE5YS00ZmVhLTlkM2QtODhiMzY0MjRlZmI3IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjkwOTAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1kZW1vIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI4NDNhYzk1ZS05MTlhLTRmZWEtOWQzZC04OGIzNjQyNGVmYjciLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJUZXN0IFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0X3VzZXIiLCJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciJ9.g_v6DA9foM1TBUUV46wclDbkSYrFWJ1S6VWXuA1lj6AY1T4SIFcGrGDibCdT1N1s01HsU1qDwJohLcQpBmojIbgeLHuIK_K2AeDC99lE0yVFvxxoH-rfeAyHDJf0Vqqk_GWQU0hl3VYhDEb-ZBstkFwv0Vuu1C9nw6a_w1POoEACMppz-0WROeXhQa5_D8yqGHSC70jZ0vUVFCL7RiVeXDaV9J7kyoSr4bQYhJ1bHU9kQwq-yo0eSLxCnmQmYFa3yEDJKSCTups7A1FrXTuNTG8XUT1pfa_YVh51g4_u_HEsahwl8mSSs_Y56TLI5LNOBKj-tH56aDxCoLx8XMWYJQ'
This returns details about the user as expected so, in a real application, we could proceed to registering or logging in the user.
{
  "sub": "c2967bf7-3baf-43c4-8aea-dfeed01852eb",
  "email_verified": false,
  "name": "Test User",
  "preferred_username": "test_user",
  "given_name": "Test",
  "family_name": "User"
}
This was a very short test of Keycloak. It's extremely fully-featured and flexible, so I plan to learn more about it since it seems to be a very good option to have in our toolkit.
References
  • https://www.keycloak.org/
  • https://www.keycloak.org/getting-started/getting-started-docker
  • https://www.keycloak.org/docs/latest/getting_started/index.html
  • https://www.keycloak.org/docs/latest/securing_apps/index.html
  • https://www.keycloak.org/docs/latest/securing_apps/index.html#other-openid-connect-libraries
  • https://www.appsdeveloperblog.com/keycloak-authorization-code-grant-example/
  • 좋은 웹페이지 즐겨찾기