Authorization code grant (also named "auth_code") is one of the most popular
authentication method on the web. Every oauth2 provider
implements this flow which is the best for web authentication. Facebook, Google, Twitter, Linkedin... all of
them use it (or partially, we will explain why).
But how it works? We will try to explain this method step by step without a diagram paste from the web. An
example with our Keycloak is more convinient.
We will use our bash oauth2 client. It is a simple wrapper which only 3 dependancies :
After cloning, just make the script executable with chmod +x oidc-client.sh
All oauth2 operations are supported, without any context. All parameters are required for each call.
This client has only a redirect URI on 127.0.0.1, client_id is
auth_code_demo
Openid endpoint configuration is
https://app.please-open.it/auth/realms/d0b4a783-114d-4f19-8a1b-425f0abe02ae/.well-known/openid-configuration
You can inspect this endpoint with the script :
./oidc-client.sh \
--operation get_oidc_server_infos \
--openid-endpoint https://app.please-open.it/auth/realms/d0b4a783-114d-4f19-8a1b-425f0abe02ae/.well-known/openid-configuration
Back to the begining : you know oauth2 as a web authentication protocol.
What happens when you click on the "login with" button?
So... there is no communication between the application and the authentication server. This process uses your web browser to transfer informations between apps...
This solution is simple, but not really reliable with a really big limitation :
Your token (aka session identifier) is in an URI. An URI is not a safe place for confidential informations.
The biggest problem is webanalytics. So, send a token in an URI is not a good idea.
OAuth 2.0 Security
Best Current Practice says : In order to avoid these issues, clients SHOULD NOT use the implicit
grant (response type "token") or other response types issuing access
tokens in the authorization response,
End of game...
./oidc-client.sh \
--operation implicit_grant \
--openid-endpoint https://app.please-open.it/auth/realms/d0b4a783-114d-4f19-8a1b-425f0abe02ae/.well-known/openid-configuration \
--client-id auth_code_demo \
--scope email \
--redirect-uri http://127.0.0.1:8080
copy/paste the generated URI in your browser. An authentication form will ask you for your credentials. Note that generated URI is not on 127.0.0.1 but directly on keycloak authentication.
Great now in the browser you have a token with some details. Those details comes from the URI.
Send a token in an URI is a problem. So, authorization code flow says : never send a token in an URI, just a single use authorization code with a short lifetime.
So, in the redirection, an authorization code is sent. This code has to be exchanged for an access_token with a POST request. The answer contains the access_token.
Solved? Partially...
Your app send a POST request to the authentication server with the auth_code. Cross-origin Resource Sharing (CORS) must allows this call. Web origins parameter in Keycloak
./oidc-client.sh \
--operation authorization_code_grant \
--openid-endpoint https://app.please-open.it/auth/realms/d0b4a783-114d-4f19-8a1b-425f0abe02ae/.well-known/openid-configuration \
--client-id auth_code_demo \
--scope email \
--redirect-uri http://127.0.0.1:8080
Copy/paste the generated URI. After login, you have an authorization_code (simply named "code" in the answer). This code can be used only once. It also have a short lifetime.
The second operation is an exchange from this code to obtain a new token. Operation is called "auth_code".
./oidc-client.sh \
--operation auth_code \
--openid-endpoint https://app.please-open.it/auth/realms/d0b4a783-114d-4f19-8a1b-425f0abe02ae/.well-known/openid-configuration \
--client-id auth_code_demo \
--redirect-uri http://127.0.0.1:8080 \
--authorization-code PASTE_AUTH_CODE_HERE
The answer contains the session we need :
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJzYkdqbjk3TXJfa0RGa3ZEV2lnZW51OWYzdndHOXlEYlF1S1RkVUNBN0Y0In0.eyJleHAiOjE1OTM1MDE5MTAsImlhdCI6MTU5MzUwMTYxMCwiYXV0aF90aW1lIjoxNTkzNDk5ODUzLCJqdGkiOiIzMjM4ZDBlYy02MzkzLTRiYjItYmM0Zi0yMzgzYzQxNzkwZjAiLCJpc3MiOiJodHRwczovL2FwcC5wbGVhc2Utb3Blbi5pdC9hdXRoL3JlYWxtcy9kMGI0YTc4My0xMTRkLTRmMTktOGExYi00MjVmMGFiZTAyYWUiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsIndlYiIsImFwaS1iYWNrZW5kIiwiYWNjb3VudCJdLCJzdWIiOiI1NTM5MjZjZS1jZjI1LTQ5ZmEtYTU4Mi1hMzBiMDc4OWM3MzYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhdXRoX2NvZGVfZGVtbyIsInNlc3Npb25fc3RhdGUiOiJkNzk5MGRmNy0xZWJmLTQzNjEtYWMyYS1jMjJiYTM3ZjIyMGMiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly8xMjcuMC4wLjE6ODA4MCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidmlzaW9uIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InJlYWxtLW1hbmFnZW1lbnQiOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJyZWFsbS1hZG1pbiIsImNyZWF0ZS1jbGllbnQiLCJtYW5hZ2UtdXNlcnMiLCJxdWVyeS1yZWFsbXMiLCJ2aWV3LWF1dGhvcml6YXRpb24iLCJxdWVyeS1jbGllbnRzIiwicXVlcnktdXNlcnMiLCJtYW5hZ2UtZXZlbnRzIiwibWFuYWdlLXJlYWxtIiwidmlldy1ldmVudHMiLCJ2aWV3LXVzZXJzIiwidmlldy1jbGllbnRzIiwibWFuYWdlLWF1dGhvcml6YXRpb24iLCJtYW5hZ2UtY2xpZW50cyIsInF1ZXJ5LWdyb3VwcyJdfSwid2ViIjp7InJvbGVzIjpbImRhdGFfbWFuYWdlbWVudCJdfSwiYXBpLWJhY2tlbmQiOnsicm9sZXMiOlsidmlzaW9uIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwicHJlZmVycmVkX3VzZXJuYW1lIjoibWF0aGlldS5wYXNzZW5hdWRAcGxlYXNlLW9wZW4uaXQiLCJlbWFpbCI6Im1hdGhpZXUucGFzc2VuYXVkQHBsZWFzZS1vcGVuLml0In0.SfmCvPa0_qefXrVJ7npErJIUYb57cUFb3JH-5jWyhWDsC7OvN_bYbpJUwjBvxLNUKUjI7CdW73GP8H-g7pxIWDGhdDJavEefZDX9hfBLIKrAQCjZ4SGbtSGCFEeU2mxDhJqKNZiOW9D2ftpnWP_64AMPc8zbHuVIcH90n0-ynUB6UrhqtEbDpdHRfVDfXq-bmmzEolmt3_jseqDcFqul4XFgIVPexLUMDw-qf-4OuHAPgPWXhznKEusV9hmBWxfOJ7362cn3DYn9NseV4pzWaZryHbtWj0CFYQgsg8qxEZqGt8uxqdLacL897jLVh1UVMi2cN2Gq1fKHE-wa0UjJqg",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2YWNjMTE0OC0zZjcwLTQ0YmYtYjc5Mi04ZDljM2ZiMWRkMDQifQ.eyJleHAiOjE1OTM1MDM0MTAsImlhdCI6MTU5MzUwMTYxMCwianRpIjoiM2FjODMzNjItY2ZiYS00OGU3LTk5ZGEtZmEwZGNmZGU2NmJmIiwiaXNzIjoiaHR0cHM6Ly9hcHAucGxlYXNlLW9wZW4uaXQvYXV0aC9yZWFsbXMvZDBiNGE3ODMtMTE0ZC00ZjE5LThhMWItNDI1ZjBhYmUwMmFlIiwiYXVkIjoiaHR0cHM6Ly9hcHAucGxlYXNlLW9wZW4uaXQvYXV0aC9yZWFsbXMvZDBiNGE3ODMtMTE0ZC00ZjE5LThhMWItNDI1ZjBhYmUwMmFlIiwic3ViIjoiNTUzOTI2Y2UtY2YyNS00OWZhLWE1ODItYTMwYjA3ODljNzM2IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImF1dGhfY29kZV9kZW1vIiwic2Vzc2lvbl9zdGF0ZSI6ImQ3OTkwZGY3LTFlYmYtNDM2MS1hYzJhLWMyMmJhMzdmMjIwYyIsInNjb3BlIjoicHJvZmlsZSBlbWFpbCJ9.Q-5bxkWbjD-1FMPv0zbJV6LaXjZer6063pjpYKdg9Nc",
"token_type": "bearer",
"not-before-policy": 0,
"session_state": "d7990df7-1ebf-4361-ac2a-c22ba37f220c",
"scope": "profile email"
}
The specification (https://oauth.net/2/grant-types/authorization-code/) says :
The Authorization Code grant type is used by confidential and public clients to exchange an authorization code for an access token.
If you try the same manipulation with Google's oauth provider, you will have this answer :
{
"error": "invalid_request",
"error_description": "client_secret is missing."
}
The reference documentation from Google excludes public clients from this authorization code exchange operation.
oidc-client.sh supports client-secret option for this operation. So, for an operation with "client_secret" you have to keep this variable secret so forget it in a web application.
Exchange an authorization_code for a token by using a backend operation increases security level.
Allowing implicit grant for public clients (akka web apps) is a pain and an issue (as IETF says)
https://tools.ietf.org/html/draft-ietf-oauth-security-topics-14#section-2.1.2
Never share your client credentials in a webapplication. In this case, you have to use implicit flow. So please be very carreful with web analytics, on a logout do not forget to revoke the token.
Requirement to Google : why not allowing authorization code exchange with a public client ?
Any question? Want more information? Follow us on twitter or you can reach out to us via email.