Trying Authenticating to Keycloak Using Google as Identity Provider
FleetingTrying authenticating to keycloak using google sign-in following this tutorial
https://keycloakthemes.com/blog/how-to-setup-sign-in-with-google-using-keycloak.
First, a small precision: this is not doing SSO at all, because it does not replace the policy handling of keycloak.
The tutorial is great, but I need to get a little bit into the details to challenge my understanding.
I will try with a dev keycloak run like this:
docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:15.1.0
Then I can connect to it using the following url
http://localhost:8080/auth/admin/master/console/#/realms/master.
Then, creating a test realm with a test client allowing the standard and
implicit flow of OAuth 2.0 with the valid redirection to
http://localhost:8081
.
I will use implicit grant not to bother with the intermediate code.
It is important to realize here that there are two flows going on:
- I trigger an open id connect flow with keycloak and the client id test, here
the three OAuth actors (there are four actors, but the resource server is not
involved in the part of the flow I’m interested about) are:
- resource owner: me
- client: my browser
- authorization server: keycloak
- keycloak will perform a nested flow with
- resource owner: still me
- client: keycloak
- authorization server: google
In the second (nested) flow, keycloak will try to get an identity token from google about me in order to create my keycloak account.
According to the RFC:
Authorization Request
The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the “application/x-www-form-urlencoded” format, per Appendix B:
- response_type
- REQUIRED. Value MUST be set to “token”.
- client_id
- REQUIRED. The client identifier as described in Section 2.2.
- redirect_uri
- OPTIONAL. As described in Section 3.1.2.
- scope
- OPTIONAL. The scope of the access request as described by Section 3.3
- state
- RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in Section 10.12.
Then, I can open the following url in my browser
http://localhost:8080/auth/realms/test/protocol/openid-connect/auth?response_type=token&client_id=test&redirect_url=http://localhost:8081
It shows me the login screen of keycloak
When I try using the method using google as Identity Provider, it:
-
opens the endpoint of keycloak to deal with the google broker
http://localhost:8080/auth/realms/test/broker/google/login? client_id=test &tab_id=sG4RPI75z5I &session_code=H3TIlkvrMkPNSw7nWZ0l25mhZXvyhN0uhtaqI11W-VI
-
that immediately redirects me to the google sign in screen
https://accounts.google.com/o/oauth2/v2/auth? scope=openid+profile+email &state=IsqEILwW6QB0NBOo4DmCd_J5Y8eOd7r44onHyTYC3Ew.MeU8vINP53c.test &response_type=code &client_id=MYCLIENTID &redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fauth%2Frealms%2Ftest%2Fbroker%2Fgoogle%2Fendpoint &nonce=5fa1WUvjcVPaH0wjXzAJ9Q
Note the openid scope, indicating they are talking in OpenID Connect protocol. Also, this is the beginning of the nested flow, using another client id.
Here, keycloak uses the standard flow, the server will respond with the code to get the access token and not directly with the access token.
This shows the sign in screen to get access to the application.
-
I’m redirected to the redirect url of the nested flow
http://localhost:8080/auth/realms/test/broker/google/endpoint? state=IsqEILwW6QB0NBOo4DmCd_J5Y8eOd7r44onHyTYC3Ew.MeU8vINP53c.test &code=4%2F0AX4XfWjptKJdbVwWl5P3DmEdkGRxXTsq8m0AGFazVq6O3_aZRhpNX6RIKXP1pWLc6YIw3g &scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email &authuser=0 &hd=SOMEHOSTEDDOMAIN &prompt=consent
Note that it added a few of its own scopes and gave an authorization code. Also note that the state parameter is the same, meaning I did not fall into a cross-site request forgery.
-
here, something happens under the hood, keycloak uses the code to asks google about the ID token. This does not use my browser, so I cannot see the communication.
By using tcpdump, I can indeed see some traffik between the docker container running keycloak and some server at google (with oauth2.googleapis.com.).
12:03:21.060963 IP 172.17.0.8.35055 > myrouter: 44109+ A? oauth2.googleapis.com. (39) 12:03:21.064306 IP myrouter > 172.17.0.8.35055: 44109 1/0/0 A 142.250.75.234 (55) 12:03:21.064502 IP 172.17.0.8.37792 > par10s41-in-f10.1e100.net.https: Flags [S], seq 4218726088, win 64240, options [mss 1460,sackOK,TS val 963198246 ecr 0,nop,wscale 7], length 0 12:03:21.074882 IP par21s05-in-f138.1e100.net.https > 172.17.0.8.45674: Flags [R], seq 3137516732, win 0, length 0 12:03:21.078601 IP par10s41-in-f10.1e100.net.https > 172.17.0.8.37792: Flags [S.], seq 2497217181, ack 4218726089, win 65535, options [mss 1430,sackOK,TS val 417428570 ecr 963198246,nop,wscale 8], length 0 ...
-
at that moment, the nested flow is over. keycloak has my identity token and can create a user of me.
-
in the end, keycloak redirects me to my initial redirect url, with a fresh id token and access token relative to my flow (the one with client_id=test)
http://localhost:8081/# session_state=7577f2d7-6988-45e5-a450-ce749ab32d72 &access_token= eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJQTl9ybnEtbVNobGpRX3dzdU1rSW5GTmRoTEltNDExLXp2MW9IaVh3WFZNIn0 . eyJleHAiOjE2NDAxMDU2ODYsImlhdCI6MTY0MDEwNDc4NiwiYXV0aF90aW1lIjoxNjQwMTA0Nzg2LCJqdGkiOiIwZDY0MjM5OC01ZjNmLTQzZGItYTU1MS0wMmI0ZjdhNGFkOWIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvdGVzdCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJkYTE0NDE1OC1lNzE4LTRjODgtYmNhYy1kZDExN2U1ZmZlZGUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6Ijc1NzdmMmQ3LTY5ODgtNDVlNS1hNDUwLWNlNzQ5YWIzMmQ3MiIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy10ZXN0Iiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6Ijc1NzdmMmQ3LTY5ODgtNDVlNS1hNDUwLWNlNzQ5YWIzMmQ3MiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IktvbnViaW5peCBLb251YmluaXgiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJrb251YmluaXhAZ21haWwuY29tIiwiZ2l2ZW5fbmFtZSI6IktvbnViaW5peCIsImZhbWlseV9uYW1lIjoiS29udWJpbml4IiwiZW1haWwiOiJrb251YmluaXhAZ21haWwuY29tIn0 . PkA2Je5BYgLeOdByWGchvVILr9EROnjLQ_NJSxzWN2r0QT7aOTUX4d4jI-7U2_NHTk8VLgvXWtEskEMLxMYhNXHILiuBXKj4PjaPU0zo_AX06NZMKNxtM_yRkoaA4u2w0dOkgYcWNX38sbinMYlO2uILTV6EWUHXByRYzA8EJiWD41A6yNZvxEc0_GUNnCszvki1MhYEcIvfSvJVrkHJ5G9xa84gJoIQaw2EX9dNMp4pxCJka5uyUYS1sEMtJKSbxU65
I can take a look at my new access token.
echo "${access_token}"| cut -f2 -d. | base64 -d|jq -M
{
"exp": 1640105686,
"iat": 1640104786,
"auth_time": 1640104786,
"jti": "0d642398-5f3f-43db-a551-02b4f7a4ad9b",
"iss": "http://localhost:8080/auth/realms/test",
"aud": "account",
"sub": "da144158-e718-4c88-bcac-dd117e5ffede",
"typ": "Bearer",
"azp": "test",
"session_state": "7577f2d7-6988-45e5-a450-ce749ab32d72",
"acr": "1",
"realm_access": {
"roles": [
"default-roles-test",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "profile email",
"sid": "7577f2d7-6988-45e5-a450-ce749ab32d72",
"email_verified": false,
"name": "Konubinix Konubinix",
"preferred_username": "konubinix@gmail.com",
"given_name": "Konubinix",
"family_name": "Konubinix",
"email": "konubinix@gmail.com"
}
keycloak provide many user related information in the access token by default.
This access token contains my username, family name and email: that’s strange.
I assume that this is linked to what is said in the RFC 9068
often include resource owner attributes directly in access tokens so that resource servers can consume them directly for authorization or other purposes without any further round trips to introspection
Yet, I have a hard time imagining why my family name matters here. Actually, I tend to think that there is a confusion with ID Token. Actually, discussing the topic with colleagues, I realize that some client dev often expect the access token to be JWS Compact Serialization of a JWT and that read user information in it, while this is exactly the purpose of the id token (ID Tokens vs Access Tokens).