Konubinix' opinionated web of thoughts

Trying Authenticating to Keycloak Using Google as Identity Provider

Fleeting

Trying 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:

  1. 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
  2. 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.

https://datatracker.ietf.org/doc/html/rfc6749#section-4.2

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:

  1. 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
    
  2. 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.

  1. 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.

  2. 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
    ...
    
  3. at that moment, the nested flow is over. keycloak has my identity token and can create a user of me.

  4. 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

https://datatracker.ietf.org/doc/html/rfc9068

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).

Notes linking here