Table of Contents
Overview
Recently I had this use case: I need to integrate Android login with my web app which is using keycloak as IDP. Once the user logs in successfully, the Android app will send an ID token to verify to the backend. Since the user doesn’t have a password (because they haven’t registered for example), it’s not possible to get the token.
STOP!
Before you continue, if you know a better approach, please let me know or share in the comment. This method is risky as far as I know but the deadline can’t wait and I’ve done my search but found no better solution.
Proceed with your understanding of the risk involved.
Okay, let’s continue.
The Flow
The flow is quite simple:
- The user logs in in Android, we get the ID Token returned by Google
- We get this ID Token, and post to the backend to verify the validity
- If the token is valid, we create the user in Keycloak. If the user exists (by email) then creating a user is not needed
- We get the keycloak ID of the user (either by creating a new or from our database if the user exists)
- We use the keycloak admin client to impersonate the newly created user to get the refresh and access token to return to the Android app
- Now the Android app has the token, we can use that call to our backend
I will not do the Android app side in this tutorial, Google already has a good tutorial.
Setup Keycloak
In order to enable this impersonation, you need first to start keycloak with the following options:
-Dkeycloak.profile=preview -Dkeycloak.profile.feature.token_exchange=enabled
If you use docker-compose, so you can pass this option as an env variable:
JAVA_OPTS_APPEND="-Dkeycloak.profile=preview -Dkeycloak.profile.feature.token_exchange=enabled"
Create (or recreate) your keycloak instance before continuing.
Configure your keycloak client
If you have your backend, chances are you already configured a client with client ID and secret. Make sure you enable a service account for this client.
Now, this client needs to have the impersonation right:
Now the configuration with keycloak is done. Back to your Java code.
Getting User Access Token Without password
All you need to do to impersonate the user and get her access token is this:
public String impersonate(String userKeycloakId) throws IOException { var httpClient = HttpClientBuilder.create().build(); var url = serverUrl + "/realms/" + realm + "/protocol/openid-connect/token"; log.info("Impersonate url: {}", url); var reqBuild = RequestBuilder.post() .setUri(url) .addHeader("Content-Type", "application/x-www-form-urlencoded") .addParameter("client_id", clientId) .addParameter("client_secret", clientSecret) // .addParameter("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") .addParameter("subject_token", getKeycloak().tokenManager().getAccessTokenString()) .addParameter("requested_subject", userKeycloakId) .build(); var response = httpClient.execute(reqBuild); if (response.getStatusLine().getStatusCode() == 200) { String text = new BufferedReader( new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8)) .lines() .collect(Collectors.joining("\n")); var token = objectMapper.readValue(text, AccessTokenResponse.class); log.info("Impersonate token: {}", token); } else { // handle error log.info("Impersonate error: {}", response.getStatusLine().getStatusCode()); } return null; } private Keycloak getKeycloak() { return KeycloakBuilder.builder() .serverUrl(serverUrl) .realm(realm) .grantType(OAuth2Constants.CLIENT_CREDENTIALS) .clientId(clientId) .clientSecret(clientSecret).build(); }
Obviously, you need to configure your server URL, realm, client id and secret based on your app.
With this, I was able to impersonate the user.
Conclusion
In this post, I’ve shown you how to impersonate a user and get her token without the user’s password. As I mentioned in the first part of the post, this is risky. Use it at your own risk. Anyways, production can’t wait!
I build softwares that solve problems. I also love writing/documenting things I learn/want to learn.
hi,
thanks for your post, I had a similar scenario so it was helpful. However in your special case: if you are using google as external auth provider (which is fully oidc compatible) couldn’t you just do a token exchange flow such as described here? https://medium.com/@souringhosh/keycloak-token-exchange-usage-with-google-sign-in-cd9127ebc96d
regards
Jon