Master the Essentials of Identity and Access Management with Keycloak and OAuth2
Sometime back I have written a newsletter on topic
Now, as part of this article we will try understanding the OAuth2.0 from a practical perspective using keycloak.
What is Keycloak ? and Why ?
Keycloak is an open-source identity and access management solution developed by Red Hat. It provides comprehensive authentication and authorization services to secure applications and services.
Although keycloak provides a lot of features to use, but in our scenerio we will comfigure it to generate access and refresh token to play around it.
So in a nutshell, Keycloak will be acting authorization server for us.
Configuring Keycloak for OAuth 2.0 with Authorization Code Grant Type
Before setting our keycloak correctly to generate tokens let’s connect the end to end dots first.

We need to create realm first, In keycloak a realm is equivalent to a tenant, let’s say your consulting organisation is configuring authorisation server for a customer X
So you see here “urban-dior“ is the realm we created for a retail customer “urban-dior“
And we we created a client “ticket-service-app“ as we want to secure backend service ticket-service which will be our resource server.

When client is created, we will also get a client secret, as this needs to be shared with the client app calling authorisation server which indicates that request is being originated from a trusted source.

Now let’s create a user role which will be assigned to a normal user, in order to get basic permission to access resource server.

Now let’s create a user, and visit-user role later needs to be assigned to this user.
This user will belong to “urban-dior“ realm.


Now we are all set to call keycloak apis to
Get Authorization code
Call Access and Refresh token API
Call Access token using refresh token
Get Authorization code
curl --location 'http://localhost:8080/realms/urban-dior/protocol/openid-connect/auth?client_id=ticket-service-app&response_type=code&scope=openid%20profile&redirect_uri=http%3A%2F%2Flocalhost%3A8082%2Fcallback&state=anything'

This above url on browser will redirect you to login page as below.

on entering correct username and password, keycloak will send authorization code to your redirect URL defined during client configuration above.
Call Get Access and Refresh token API
curl --location 'http://localhost:8080/realms/urban-dior/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=ticket-service-app' \
--data-urlencode 'client_secret=NmekTSTUHcYtyaonz3yoxKo4rHGn3h1n' \
--data-urlencode 'code=c0d2aa4c-3381-4955-8b8c-bcbd1abeabef.8ea0f7c9-f029-4e5a-b064-3725ffa6c78e.afa6a44e-b94b-475e-bbab-4182a828ea16' \
--data-urlencode 'redirect_uri=http://localhost:8082/callback' \
--data-urlencode 'scope=read_custom_scope'

The response you will receive something like below
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItSV9jdzdZUF9CUVE2TXoxbDVOOVlEWFVtTjdCQTY4emdLMm50MFJVSjJzIn0.eyJleHAiOjE3MTkyMjE2ODYsImlhdCI6MTcxOTIyMTM4NiwiYXV0aF90aW1lIjoxNzE5MjIwMDgzLCJqdGkiOiJjZWVjZjY5Zi0yYWM2LTQ0NzktOTZlZS1lMGJhYmM3ZDI4ZmYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3VyYmFuLWRpb3IiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiZjZmNWJhZmItNzRkOS00ZGRjLTgwZDctZmFjMTI0ZmM0MjZiIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGlja2V0LXNlcnZpY2UtYXBwIiwic2lkIjoiOGVhMGY3YzktZjAyOS00ZTVhLWIwNjQtMzcyNWZmYTZjNzhlIiwiYWNyIjoiMCIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODIiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtdXJiYW4tZGlvciIsIm9mZmxpbmVfYWNjZXNzIiwidmlzaXQtdXNlciIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IlRlc3QgVXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIiwiZ2l2ZW5fbmFtZSI6IlRlc3QiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJlbWFpbCI6InRlc3R1c2VyQGFueXRoaW5nLmNvbSJ9.Q1tUfOPO36f2r9sWDFFR4y0D32ndP3IaxHY8CM2eVxyfAlB50-y01bo6p3vEv54w9Xv0GxHK_n2kq4uAaotk8BPLqzC1XedBMgX1BQtecntEUIpJNgsG62WoKg0sAJkpO2ciQzIcc67epLOZfR-9QZxaFGkWUeGm06XxeZ993Y2H7S2-BDOJ47Hdy5VxVox_QDlMRAlbyaWm-b0BRA3TYVtMje1MVl2vzevTm5TIKioxNf9iWv5NWOgakM8RD9Duf53sBl_fi9OzHNjcyBMoLMFzXCOhJTtJHRLyEjA0VmjOZPu3z2u2qs6m3fMm4aEyCvxEIc3MdwDMsnRw4dfEsg",
"expires_in": 299,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxNzNkNDNhYi1lYjNlLTQ3MzItYmNjNC1hYzhlOWY4NDEyYzcifQ.eyJleHAiOjE3MTkyMjMxODcsImlhdCI6MTcxOTIyMTM4NywianRpIjoiZjQzYmFkNDMtZmU4OC00M2VkLWFjNjEtM2VjZDE5MzI0YTEyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy91cmJhbi1kaW9yIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy91cmJhbi1kaW9yIiwic3ViIjoiZjZmNWJhZmItNzRkOS00ZGRjLTgwZDctZmFjMTI0ZmM0MjZiIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InRpY2tldC1zZXJ2aWNlLWFwcCIsInNpZCI6IjhlYTBmN2M5LWYwMjktNGU1YS1iMDY0LTM3MjVmZmE2Yzc4ZSIsInNjb3BlIjoib3BlbmlkIGFjciBwcm9maWxlIGJhc2ljIHdlYi1vcmlnaW5zIHJvbGVzIGVtYWlsIn0.gLJRI9u4Uw7ql9ix67cKCiC6PMFtmZTxx6HqF1eZr1520b2xAi3glmHrfQZ31aJL0KaND6L7DJh8dcFvNIkQSg",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItSV9jdzdZUF9CUVE2TXoxbDVOOVlEWFVtTjdCQTY4emdLMm50MFJVSjJzIn0.eyJleHAiOjE3MTkyMjE2ODYsImlhdCI6MTcxOTIyMTM4NywiYXV0aF90aW1lIjoxNzE5MjIwMDgzLCJqdGkiOiJkZDEyZWRhNy1jODE5LTQyYmYtOTNkMC03MDI3YzlmNWIxOGYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3VyYmFuLWRpb3IiLCJhdWQiOiJ0aWNrZXQtc2VydmljZS1hcHAiLCJzdWIiOiJmNmY1YmFmYi03NGQ5LTRkZGMtODBkNy1mYWMxMjRmYzQyNmIiLCJ0eXAiOiJJRCIsImF6cCI6InRpY2tldC1zZXJ2aWNlLWFwcCIsInNpZCI6IjhlYTBmN2M5LWYwMjktNGU1YS1iMDY0LTM3MjVmZmE2Yzc4ZSIsImF0X2hhc2giOiJCX0d4UVpsd0daOGRpdWVOYnZIcWhBIiwiYWNyIjoiMCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IlRlc3QgVXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIiwiZ2l2ZW5fbmFtZSI6IlRlc3QiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJlbWFpbCI6InRlc3R1c2VyQGFueXRoaW5nLmNvbSJ9.QMY8zxH9tSObdosSdTdpAoaHKsvEsDThhMeDZ8QQDiZmDHYG9QphoCogFBplOfXFK0Zn8xRaxBBVZSFY6wm7JwDRHT_3d9QVTPqVwe97VFONFTyGzSTn2Eipp2K57YZoOO_A6O0QAlktK75N8vn8_wbOGAek55iGC9tYl3tcw9XTt5qg-HMiWYngUI4dyXU7FC1Gr8J6V_22VRnxx-UirULOc4iydHhkaC46PM3JUfopwrsY4FlqramNvZKs2Fw5eIN9tQETjEFWWlPQu47lL0q6GtceSCO-cHnsecD4U4IGLZ0aWzGFC-1QUz5i1cJaLCadXQX6eETdxG-4fovZiQ",
"not-before-policy": 0,
"session_state": "8ea0f7c9-f029-4e5a-b064-3725ffa6c78e",
"scope": "openid profile email"
}
Now, if you really want to look at this token internals, what it is actually carrying you can go to jwt.io and deserialize this token.
{
"exp": 1719221686,
"iat": 1719221386,
"auth_time": 1719220083,
"jti": "ceecf69f-2ac6-4479-96ee-e0babc7d28ff",
"iss": "http://localhost:8080/realms/urban-dior",
"aud": "account",
"sub": "f6f5bafb-74d9-4ddc-80d7-fac124fc426b",
"typ": "Bearer",
"azp": "ticket-service-app",
"sid": "8ea0f7c9-f029-4e5a-b064-3725ffa6c78e",
"acr": "0",
"allowed-origins": [
"http://localhost:8082"
],
"realm_access": {
"roles": [
"default-roles-urban-dior",
"offline_access",
"visit-user",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid profile email",
"email_verified": false,
"name": "Test User",
"preferred_username": "testuser",
"given_name": "Test",
"family_name": "User",
"email": "testuser@anything.com"
}
so now you see in the roles, section it has visit-user role. accordingly your client app can limit users based on these information.
If you really like my content you can subscribe me below.
Youtube Channel – https://www.youtube.com/channel/UCpF3Y8AxzgYZnI8Zcf_G_fg
You can follow me on linkedin here – https://www.linkedin.com/in/suchait-gaurav-944479109/
Github Repo – https://github.com/suchait007