We're trying to integrate (me & my colleagues) Oauth2 authentication in the communication of some of my REST applications. As an authentication provider and manager, We would like to use Keycloak. We read some documentation and We think We understood how it works. Now, We would like to know please from you, if the flow authentication We designed respects the principles of the Oauth2 protocol. The flow auth We decided to use involves the generation of tokens and their validation is:
graphic representation of auth-flow
Let's proceed with the description of an example of data interchange between two applications (application A and application B) that use Oauth2 to authenticate each other. The image above shows how the A application queries Keycloak to request a token; subsequently, application A sends the request to application B by entering the token it received from Keycloak; at this point, application B, after receiving the token, queries Keycloak and asks it to validate the token received; finally, application B will receive an answer regarding the validity of the token and, based on this reply, it will be able to decide whether to accept the request received from application A or to refuse it.
Let's see the technical details of the interchange:
1 - Application A, asks Keycloak, through authentication, to generate a new token.
An example of an HTTP request:
POST /auth/realms/OMS/protocol/openid-connect/token HTTP/1.1
Host: local-keycloak.it:8080
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
client_id=oms-test&client_secret=039a6b94-44a7-4dae-b8a4-e7b673eee8e5&grant_type=client_credentials&scope=openid
The body of the HTTP message sent to the server is essentially one giant query string -- name/value pairs are separated by the ampersand (&), and names are separated from values by the equals symbol (=). In fact, the content-type of the body is indicated as "application/x-www-form-urlencoded".
2 - Keycloak, if the application A authentication was successful, generate the token and forward it to it.
An example of an HTTP response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiJhNmEzZmQ3ZS00NDdhLTQzNTMtOWM2Yi03ZjFhN2QwZDAxYTEiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsImNsaWVudElkIjoib21zLXRlc3QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfbmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsImNsaWVudEFkZHJlc3MiOiIxMjcuMC4wLjEiLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdEBwbGFjZWhvbGRlci5vcmcifQ.RkQ178gLfWoA1-F7w5e4q7FXzhLhigAOPrUN1QrX3oz7PxFuqhm_GopWcX0hHNgX0uFNtSGC1iWn04H5VzcevcDK42w5gV5TWo9WJ5CJp-NRjYdsEST_PhI6KlHsXgik53qF_kCeKwB-_eal1rVdlEY7WO1kv1p8cih-bEA9NNBdA5C6_iA4IF6Jfrdp8lJ_DeRtnbXqsc1dgYdJbYru_BGiYTkolLXxIqfTOTENH64to3EAEVMQ21c_zQtmRxVOaD_fvNOZMqOmWeKk02Z6rfq2m77M6edv1LvlGAnVmx7-zRG6a6eL-t6rZiOwr3eohJ67U77ndzJKrl5J5Wuiwg",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2NzhjY2M0NC03ZTY3LTRmODAtOTk5NC1hOTA0NmI3NGY2YTgifQ.eyJqdGkiOiJiYTU3NzZjYi03Zjg1LTRhNTAtOGM5Ni1kYWQ3OTRlZGRjZWIiLCJleHAiOjE1NTc5OTY4NTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL09NUyIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvbXMtdGVzdCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6ImVhNWUxYzQyLWI5MzUtNGRhMC05ZDY2LWEwMjlmZGY3NzdiMiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSJ9.BkXWMLmuf1c0OBUeg2P2262LLvTmhXg46y4-rrvebNE",
"token_type": "bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiIyYjdjZGI2Ny1kYjM3LTQ5MTQtYWNiYi0xNmU5MDA4YzQ4N2IiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoib21zLXRlc3QiLCJzdWIiOiI2MGU4YjY1Yy02NTk2LTQzMmEtYTY2OC0xMzk5YzE2N2QzODYiLCJ0eXAiOiJJRCIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImNsaWVudEhvc3QiOiIxMjcuMC4wLjEiLCJjbGllbnRJZCI6Im9tcy10ZXN0IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX25hbWUiOiJzZXJ2aWNlLWFjY291bnQtb21zLXRlc3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtb21zLXRlc3QiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC4xIiwiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtb21zLXRlc3RAcGxhY2Vob2xkZXIub3JnIn0.C0YkK-B4LnKH3NBCxHjuAkVZKVikh3FaUPIUpToCVFKkgefZRF7JS2yddC4ejxn4_B4y56TBMdVSXg5dEk-ghkz_f1VOR1whRY0HAC6Z5izEJBOHesASWoxJE43QJHXoDYzNWJK1S4JQ6W_BF5KobVHrXL2fmb-ypLBJCc8EAMTYEC-fpxT_T3NkDbsAjmnoCTl1YmRiDkV0sqUKerx5irIZJ3S297Z0Ub4Ahal8ObX7t3JbpJ-SBEvRvNo0PriZdk7C1DZQEhc77v2qnpeyqkwcRkAhZ0uXb5QF32J6dxhKh8-gZHYCauMdzeNmkh-962RnWXqyhGOYipLarnmzjg",
"not-before-policy": 0,
"session_state": "ea5e1c42-b935-4da0-9d66-a029fdf777b2",
"scope": "openid email profile"
}
As we can see, we have been given an access token to use as an authentication and authorization token between our platforms. The access token has a faster expiration than the refresh token, with this it is possible to regenerate an access token.
3 - Application A sends a request to Application B using the token (access_token) received.
An example of an HTTP request:
POST /omsesb/order/placeOrder HTTP/1.1
Host: application-b.it:8081
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiJhNmEzZmQ3ZS00NDdhLTQzNTMtOWM2Yi03ZjFhN2QwZDAxYTEiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsImNsaWVudElkIjoib21zLXRlc3QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfbmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsImNsaWVudEFkZHJlc3MiOiIxMjcuMC4wLjEiLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdEBwbGFjZWhvbGRlci5vcmcifQ.RkQ178gLfWoA1-F7w5e4q7FXzhLhigAOPrUN1QrX3oz7PxFuqhm_GopWcX0hHNgX0uFNtSGC1iWn04H5VzcevcDK42w5gV5TWo9WJ5CJp-NRjYdsEST_PhI6KlHsXgik53qF_kCeKwB-_eal1rVdlEY7WO1kv1p8cih-bEA9NNBdA5C6_iA4IF6Jfrdp8lJ_DeRtnbXqsc1dgYdJbYru_BGiYTkolLXxIqfTOTENH64to3EAEVMQ21c_zQtmRxVOaD_fvNOZMqOmWeKk02Z6rfq2m77M6edv1LvlGAnVmx7-zRG6a6eL-t6rZiOwr3eohJ67U77ndzJKrl5J5Wuiwg
Client-Id: oms-test
cache-control: no-cache
{
"orders":
{
"order": {
...
}
}
}
As we can see, the "Authorization" header contains the access token that application A received from Keycloak. We have added another "Client-Id" header which will contain the user ID with which application A has requested the generation of the token from Keycloak.
In this example, application A is asking application B to enter a new order but must first authenticate itself and must, therefore, be authorized.
4 - Application B asks Keycloak, with a request, to validate the access token it received from the application A.
An example of an HTTP request:
POST /auth/realms/OMS/protocol/openid-connect/token/introspect HTTP/1.1
Host: local-keycloak.it:8080
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
client_id=account&client_secret=d67da47e-387a-4930-a89a-eda0296c4896&token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvU2ZDYjlzSTV6eTNha1BLU2hSMFVYeXJqNjltUFhEcFdjdWM1SG1mUlFvIn0.eyJqdGkiOiJhNmEzZmQ3ZS00NDdhLTQzNTMtOWM2Yi03ZjFhN2QwZDAxYTEiLCJleHAiOjE1NTc5OTUzNTUsIm5iZiI6MCwiaWF0IjoxNTU3OTk1MDU1LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvT01TIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjYwZThiNjVjLTY1OTYtNDMyYS1hNjY4LTEzOTljMTY3ZDM4NiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9tcy10ZXN0IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZWE1ZTFjNDItYjkzNS00ZGEwLTlkNjYtYTAyOWZkZjc3N2IyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsImNsaWVudElkIjoib21zLXRlc3QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfbmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdCIsImNsaWVudEFkZHJlc3MiOiIxMjcuMC4wLjEiLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC1vbXMtdGVzdEBwbGFjZWhvbGRlci5vcmcifQ.RkQ178gLfWoA1-F7w5e4q7FXzhLhigAOPrUN1QrX3oz7PxFuqhm_GopWcX0hHNgX0uFNtSGC1iWn04H5VzcevcDK42w5gV5TWo9WJ5CJp-NRjYdsEST_PhI6KlHsXgik53qF_kCeKwB-_eal1rVdlEY7WO1kv1p8cih-bEA9NNBdA5C6_iA4IF6Jfrdp8lJ_DeRtnbXqsc1dgYdJbYru_BGiYTkolLXxIqfTOTENH64to3EAEVMQ21c_zQtmRxVOaD_fvNOZMqOmWeKk02Z6rfq2m77M6edv1LvlGAnVmx7-zRG6a6eL-t6rZiOwr3eohJ67U77ndzJKrl5J5Wuiwg
5 - Keycloak, at this point, responds by giving the outcome of the validation as a response. The result could be positive, or negative.
An example of an HTTP response containing a positive result of validation:
{
"jti": "26b6794d-3d83-446a-a106-10dfb14793c3",
"exp": 1557997077,
"nbf": 0,
"iat": 1557996777,
"iss": "http://local-keycloak:8080/auth/realms/OMS",
"aud": "account",
"sub": "60e8b65c-6596-432a-a668-1399c167d386",
"typ": "Bearer",
"azp": "oms-test",
"auth_time": 0,
"session_state": "88a63302-b148-42e4-81d0-5cb81c446903",
"preferred_username": "service-account-oms-test",
"email": "[email protected]",
"email_verified": false,
"acr": "1",
"allowed-origins": [
"http://local-keycloak:8080"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid email profile",
"clientHost": "application-a.it",
"clientId": "oms-test",
"user_name": "service-account-oms-test",
"clientAddress": "80.10.10.1",
"client_id": "oms-test",
"username": "service-account-oms-test",
"active": true
}
As we can see, the validation was successful (active = true): Keycloak recognized the token (not yet expired) and returned to application B a set of information by which it is able to decide whether to accept, or not, application A's request.
Through this information, application B is able to recognize the user who requested the token previously, through the id of the client, IP, address and other information.
In this case, to avoid a possible man-in-the-middle attack, the B application can match the data received from the application A with those received by the Auth Provider: it can verify that the client-id and the IPs match.
An example of an HTTP response containing a negative result of validation:
{
"active": false
}
The result is negative when the token does not exist, the token has expired or the application that is requesting token validation uses a user that does not have the necessary powers to request validation.
6 - On the basis of the result received from Keycloak, application B communicates to application A if its request has been accepted or not.
So, in your opinion, could this authentication flow be correct and therefore respect the principles of Oauth2?
I have not received any response from Stack users but, looking at the flow with my colleagues and studying Oauth2, we realized that the flow we implemented is efficient and respects the rules and characteristics of the Oauth2. Furthermore, by introducing the final control based on the IP and the client's username, we can assure the fact that the client that requested the token is the same one that then presented it as an authorization.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With