This content originally appeared on Level Up Coding – Medium and was authored by Nazarii Moshenskyi

In the previous part of the series we explored how secure authentication works in Android apps and how to configure Keycloak properly for mobile client. Now it’s time to move theory into practice. In this part I’ll explain the core AppAuth components you need to integrate Keycloak properly.
By the end you’ll have an understanding how to integrate it in your app. If you missed previous articles, you might want to check them first:
- Keycloak for Android: The Foundations of Secure Authentication
- Keycloak for Android: The Configuration
- Keycloak for Android: Building the App (you are here)
Why do we use a library?
As you might remember from my previous articles, to sign in or register via Keycloak you should send authorization request in your browser or Chrome Custom Tab, you should also include redirect uri to be redirected to your app where you can parse the result. The request should look like this:
https://<your-domain>/realms/myrealm/protocol/openid-connect/auth?<params>
But building those requests and handling a lot of different stuff might be overkill. That’s why we can use AppAuth — official OpenID library that does all boilerplate for us. I will try to tell how it’s done in a library and how it works under the hood (you can skip those parts if you just need a reference to SDK).
Setup
First of all, add the dependency to your app/build.gradle.kts:
implementation("net.openid:appauth:0.11.1")Now, to configure redirects we have two options. AppAuth already has a dedicated activity to handle those redirects but as for me adding explicit redirect is too verbose. Basically, you should add this lines to your manifest file:
<activity
android:name="net.openid.appauth.RedirectUriReceiverActivity"
tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="com.moshenskyi.keycloaksample"/>
</intent-filter>
</activity>
Easier and more used approach would be solving the issue via manifestPlaceholders:
android {
...
defaultConfigs {
...
manifestPlaceholders += mapOf("appAuthRedirectScheme" to "com.moshenskyi.keycloaksample")
}
}This redirect should be the same you’ve configured in section 2.2 of previous article.
If you’re interested how those redirects work with Custom Tabs under the hood you might like one of my previous articles — SDK Design 101: Redirect-based flows. In this article I show example from AppAuth architecture.
Fetching Keycloak configuration
You can fetch configuration in multiple ways. Let’s come to it gradually to see how AppAuth helps us.
The link with configuration has such format: https://<your-domain>/.well-known/openid-configuration. You can execute it in your browser and check if you have Keycloak up and running.
That means that we can fetch it manually with any REST client. But AppAuth gives us two options.
- Build it yourself
private val serviceConfig = AuthorizationServiceConfiguration(
"$keycloakBaseUrl/realms/$realm/protocol/openid-connect/auth".toUri(),
"$keycloakBaseUrl/realms/$realm/protocol/openid-connect/token".toUri(),
"$keycloakBaseUrl/realms/$realm/protocol/openid-connect/registration".toUri(),
"$keycloakBaseUrl/realms/$realm/protocol/openid-connect/logout".toUri(),
)
2. Let AppAuth fetch it:
AuthorizationServiceConfiguration.fetchFromIssuer(keycloakBaseUrl.toUri()) { config, exception ->
if (exception != null) Log.e("Keycloak", exception.message, exception)
}It will append .well-known/openid-configuration, make a request and return you a response in callback-based way.
Making your first request
AuthorizationRequest is just a builder that constructs a URL for authorization and creates intent to open it in CustomTabs. I’ll remind you that we don’t use WebView directly because of security considerations (like this, this and that).
There are different predefined types of requests:
- AuthorizationRequest
- EndSessionRequest
- RegistrationRequest (this is not what you think of, will cover it later)
- TokenRequest
Let’s create AuthorizationRequest. It will open login page.
val request = AuthorizationRequest.Builder(
serviceConfig, // fetched on prev step
clientId,
ResponseTypeValues.CODE,
redirectUri.toUri()
)
.setScopes(Scope.OPENID, Scope.EMAIL, Scope.PROFILE)
.setPrompt(Prompt.LOGIN)
.setUiLocales("uk")
.setCodeVerifier(CodeVerifierUtil.generateRandomCodeVerifier())
.build()
We pass service config we’ve fetched on previous step, clientId (you can find it in realm settings), ResponseTypeValues.CODE means we expect identity provider to return authorization code and redirect URI. For scopes you can add whatever you need but as per specs, but Scope.OPENID is mandatory. You can also use custom scopes or the predefined ones (look at AutorizationRequest.Scope class in AppAuth repository).
Other things are optional but to build a secure login flow you should add code_verifier (see Keycloak for Android: The Foundations of Secure Authentication, chapter: Auth Code Flow + PKCE).
In case of MITM attack, even if someone will catch the authorization code, you’re still safe because you use code_verifier and code_challenge. As long as verifier is hashed with SHA-256, attacker won’t decode it, so it can’t send the code challenge you’ve created and pretend it’s you (but you should also use certificate pinning).
All the communication goes through AuthorizationService. It’s special class that can perform any type of request: sign in, registration, logout, token exchange. But I don’t like to call performAuthorizationRequest() because of its inconvenience, so I just get intent and start it myself. It’s easier to handle in multi-layered architecture and most of perform-methods in AuthorizationService use AsyncTask under the hood (proof).
private val authService = AuthorizationService(context)
fun generateSignInRequest(): Intent {
val request = AuthorizationRequest.Builder(
serviceConfig,
clientId,
ResponseTypeValues.CODE,
redirectUri.toUri()
)
.setScopes(Scope.OPENID, Scope.EMAIL, Scope.PROFILE)
.setPrompt(Prompt.LOGIN)
.setUiLocales("uk")
.setCodeVerifier(CodeVerifierUtil.generateRandomCodeVerifier())
.build()
return authService.getAuthorizationRequestIntent(request)
}
And here’s an example of a launcher:
private val authLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
val data = result.data
if (result.resultCode == RESULT_OK && data != null) {
val authResponse = AuthorizationResponse.fromIntent(data)
val authException = AuthorizationException.fromIntent(data)
Log.d("KEYCLOAK", "code = ${authResponse?.authorizationCode}")
viewModel.onCodeReceived(authResponse, authException)
}
}
// somewhere in the code
authLauncher.launch(authIntent)
This intent is launched and after CustomTabs activity closes, it returns a result. In our case authorization code that we will later exchange to token. This is a common pattern in mobile apps, all we need is to register intent-filter and pass redirect-uri to CustomTabs activity. The result is passed to the activity with intent-filter that catches this type of redirect-uri.
Update email and password
AuthorizationRequest has additional builder method setAdditionalParameters ()— it adds form-url-encoded parameters to your request. You can add some of kc_action parameters or your custom ones. kc_action(s) are parameters that let Keycloak understand what action user should perform after redirecting with prebuilt URL. For example, kc_action=UPDATE_EMAIL, kc_action=UPDATE_PASSWORD, etc. Here’s how to use it:
val request = AuthorizationRequest.Builder(
serviceConfig,
clientId,
ResponseTypeValues.CODE,
redirectUri.toUri()
)
.setScopes(Scope.OPENID, Scope.EMAIL, Scope.PROFILE)
.setPrompt(Prompt.LOGIN)
.setUiLocales("uk")
.setAdditionalParameters("kc_action" to "UPDATE_PASSWORD")
.setCodeVerifier(CodeVerifierUtil.generateRandomCodeVerifier())
.build()
You can also add custom parameters that your server devs need.
Those are most common request configurations. You can find more in OIDC specification with full explanation and list of accepted values.
Exchange auth code for token
To exchange auth code for token you can use AuthorizationService:
authService.performTokenRequest(
authResponse.createTokenExchangeRequest()
) { tokenResponse, ex ->
if (ex != null) {
// Handle error
} else if (tokenResponse != null) {
val tokens = OAuthResponse(
accessToken = tokenResponse.accessToken!!,
refreshToken = tokenResponse.refreshToken,
idToken = tokenResponse.idToken,
expiresAt = tokenResponse.accessTokenExpirationTime ?: 0L
)
// Save tokens
} else {
// Handle empty state
}
}
Or you can make a direct API request. As you like. It’s very handy, but remember that it uses AsyncTask under the hood.
How to save tokens
AppAuth has AuthState class. And you can use it if it suits your architecture:
AuthState is designed to be easily persistable as a JSON string, using the storage mechanism of your choice (e.g. SharedPreferences, sqlite, or even just in a file). — from GitHub Readme
But you can also save it in EncryptedSharedPreferences (RIP) or any other secure storage. As JSON string, or separately. The cool thing of this class is that it keeps track of the authorization and token requests and responses.
Registration
In Keycloak world user self-registration and client registration are two different terms. Because “client” is not a user of your app, it’s the app itself. The registration endpoint that you can see in config is client registration. Like this from previous article:

You can check any OIDC config by using this template — https://{oauth-provider-hostname}/.well-known/openid-configuration
The skeleton looks like this: OIDC specification. You can investigate Google’s config and see that some fields are omitted — https://accounts.google.com/.well-known/openid-configuration
For user self-registration it’s a bit trickier because it’s not standardized in OIDC specification. There are some hacks like adding additional parameter kc_action=registration to authorization request that worked on some older versions.
Another option is to hardcode registration endpoint. At the moment of writing this article, user self-registration endpoint is the same as auth but you need to replace /auth with /registrations in their path. I think you can agree with me that fetching OIDC configuration and substituting a part of auth endpoint is a bit weird and unreliable.
But here’s a case with this approach: /registrations endpoint is deprecated in Keycloak 26.1 (proof) in favor of prompt=create (spec) and can be possibly removed in Keycloak 27. You should keep it in mind. Right now, if you are using new version of Keycloak, you can do this in your auth request:
val request = AuthorizationRequest.Builder(
serviceConfig,
clientId,
ResponseTypeValues.CODE,
redirectUri.toUri()
).setPrompt("create")
...
Interestingly, there no predefined prompt parameter in AppAuth library. So, to solve this, I would be very grateful if you’d help me to boost this small pull request 😀
Log out
As you can see, every action is a request, sent to the browser. Same goes with log out. We can use EndSessionRequest to build it:
fun getLogoutRequest(idToken: String?): Intent? {
val request = EndSessionRequest.Builder(serviceConfig)
.setIdTokenHint(idToken)
.setPostLogoutRedirectUri(logoutRedirectUri.toUri())
.build()
return authService.getEndSessionRequestIntent(request)
}
// somewhere in the code
private val logoutLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
viewModel.onLogout() // clear tokens ;)
}
}
val logoutIntent = getLogoutRequest(idToken)
logoutLauncher.launch(logoutIntent)You configured post-logout redirect (the same as login redirect) in previous article when creating a client. For me it looks like this: “com.moshenskyi.keycloaksample://logout”. The scheme is the same as in intent-filter.
Don’t forget to clear saved tokens after logout!
Integrating Keycloak into Android application may look complex at first, but once you break it down into essential pieces, the flow becomes much easier to reason about. AppAuth doesn’t magically hide all the auth mechanics, instead it provides a structured way to build requests and protects you from subtle mistakes that can appear in mobile integrations.
Integrating Keycloak in Android with AppAuth: What You Really Need to Know was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding – Medium and was authored by Nazarii Moshenskyi