Securing Your Applications with Keycloak: A Comprehensive Guide to OAuth2 Integration
Master the Essentials of Identity and Access Management with Keycloak and OAuth2
Sometime back I have written a newsletter on topic
Unlocking the Power of OAuth 2.0: A Comprehensive Guide to Modern Authentication and Authorization
Before going in depths of OAuth 2.0 directly lets first understand the concept of security, authentication and authorization. Security/Protected APIs In terms of Computing we can call APIs as resources, in order to make sure server resource is only being used when from a trusted source, we need to protect them.
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