Skip to main content

Integrating IDaaS OIDC with a mobile app using AppAuth

Introduction

This document describes how to add OIDC authentication to a mobile application using IDaaS and the open-source project AppAuth.

Installation

AppAuth dependencies are available on MavenCentral.

implementation 'net.openid:appauth:<version>'

Getting started - Endpoints configuration

The configuration can be fetched from discovery url, You can also create the authorization service config by explicitly adding the authorize, token, and endsession endpoints, below is an example using discovery url.

import net.openid.appauth.AuthorizationServiceConfiguration;

private AuthorizationServiceConfiguration config = null;

AuthorizationServiceConfiguration.fetchFromUrl(
Uri.parse("https://yourIdaasAccount.com/api/oidc/.well-known/openid-configuration"),
this::handleConfigurationCallBack, // fetchFromUrl will return all necessary configurations (clientId, endpoints)
// as AuthorizationServiceConfiguration for the callback to persist.
DefaultConnectionBuilder.INSTANCE // your http connection builder
});

private void handleConfigurationCallBack(
AuthorizationServiceConfiguration config) {
this.config = config;
}

You can also explicitly define the configuration in a JSON file auth_config.json.

{
"client_id": "932c9c22-16d3-46a3-9b6f-9b019003c444",
"redirect_uri": "net.openid.appauthdemo:/oauth2redirect",
"end_session_redirect_uri": "net.openid.appauthdemo:/oauth2redirect",
"authorization_scope": "openid email profile",
"discovery_uri": "",
"authorization_endpoint_uri": "",
"token_endpoint_uri": "",
"registration_endpoint_uri": "",
"user_info_endpoint_uri": "",
"https_required": true
}

For better clarification, the following documentation defines the endpoints in each section.

Logging in

Step 1

Constructing Authorization Request by using its Builder, In Android AppAuth, PKCE is implemented by default if the server supports it (IOS AppAuth has to be manually passed in the parameters). You don’t need to worry about manually managing the codeVerifier, codeChallenge, or codeChallengeMethod

import net.openid.appauth.AuthorizationRequest;

AuthorizationServiceConfiguration serviceConfig = new AuthorizationServiceConfiguration(
Uri.parse("https://your-authorization-server.com/auth"), // authorization endpoint
Uri.parse("https://your-authorization-server.com/token") // token endpoint
);

private static final String CLIENT_ID = "155bc68f-f162-4a4b-bd44-fc4782cd3f0f";
private static final String REDIRECT_URI = "net.openid.appauthdemo:/oauth2redirect"; // this is a pointer back to your application


AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
serviceConfig,
CLIENT_ID,
ResponseTypeValues.CODE,
Uri.parse(REDIRECT_URI)
)
.setScopes("openid", "profile", "email") // Add the required scopes
.build();

Step 2

Sending Authorization Request by calling
getAuthorizationRequestIntent to generate an intent (authIntent) that contains the authorization request details,
startActivityForResult to go to IDaaS web page for authentication,
onActivityResult to receive and process the authentication result.

import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationException;

// If the request code matches 100, then process the result as an authorization response, which can be success or failure
private static final int RC_AUTH = 100;

private void doAuthorization() {
AuthorizationService authService = new AuthorizationService(this);
Intent authIntent = authService.getAuthorizationRequestIntent(authRequest);
startActivityForResult(authIntent, RC_AUTH);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RC_AUTH) {
AuthorizationResponse resp = AuthorizationResponse.fromIntent(data);
AuthorizationException ex = AuthorizationException.fromIntent(data);
// ... process the response or exception ...
} else {
// ...
}
}

Step 3

Exchanging authorization code for access token by calling the token request performTokenRequest().
We use AuthorizationService to perform the token exchange using the authorization code obtained from the previous step AuthorizationResponse. The AuthState object is used to store and manage tokens (e.g., access tokens, refresh tokens) and their lifecycle. We also use AuthStateManager to manage and track AuthState at different stages of the authentication flow.

AuthorizationService authService = new AuthorizationService(this);
authService.performTokenRequest(
resp.createTokenExchangeRequest(), // `resp` is the AuthorizationResponse from the previous step, which contains token endpoint information
new AuthorizationService.TokenResponseCallback() {
@Override
public void onTokenRequestCompleted(TokenResponse response, AuthorizationException ex) {
if (response != null) {
// Token exchange succeeded, update the AuthState
authState.update(response, ex);

// Fetch the access token
String accessToken = authState.getAccessToken();
} else {
// Token exchange failed, handle the error
}
}
});

Logging out

Build the EndSessionRequest

Start by constructing an EndSessionRequest to log out. Since the id_token is required by most OpenID providers to complete the logout, first check if it exists in AuthState. If the id_token is missing, skip the logout process.

import net.openid.appauth.AuthState;
import net.openid.appauth.EndSessionRequest;
import android.net.Uri;

// Retrieve the current AuthState
AuthState authState = stateManager.getCurrent();

// Ensure ID token exists before attempting logout
if (authState.getIdToken() != null) {
// Define a post-logout redirect URI specific to the mobile app, using a custom scheme or localhost
Uri postLogoutRedirectUri = Uri.parse("net.openid.appauthdemo:/oauth2redirect");

EndSessionRequest endSessionRequest = new EndSessionRequest.Builder(
authState.getAuthorizationServiceConfiguration(), // serviceConfig included here, very similar with building AuthorizationRequest
authState.getIdToken() // Pass the ID token
)
.setPostLogoutRedirectUri(postLogoutRedirectUri) // Redirect URI back to the app
.build();
} else {
// Log or handle missing ID token: skip logout
}

Perform the end session request

Once the EndSessionRequest is ready, use performEndSessionRequest() on AuthorizationService. This also requires a conditional check for the presence of the id_token.

import net.openid.appauth.AuthorizationService;
import android.app.PendingIntent;
import android.content.Intent;

// Ensure ID token exists before performing the end session request
if (authState.getIdToken() != null) {
AuthorizationService authService = new AuthorizationService(this);

authService.performEndSessionRequest(
endSessionRequest,
PendingIntent.getActivity(this, 0, new Intent(this, MyAuthCompleteActivity.class), PendingIntent.FLAG_UPDATE_CURRENT),
PendingIntent.getActivity(this, 0, new Intent(this, MyAuthCanceledActivity.class), PendingIntent.FLAG_UPDATE_CURRENT)
);
}

Using access tokens

Using the working flow above, the access token you get is the OIDC Access Token, which can be used to access userInfo endpoint.
In the AuthorizationRequest, if you pass in the audience value, you will get an OAuth Access Token, that can be used to access protected resource.

Using OIDC Access Tokens to access User Information

performActionWithFreshTokens can be used to call endpoints using access token by taking the AuthorizationService and an API call using access token as parameters.
AppAuth does not provide a specific wrapper for userInfo requests, so you must create an HTTP connection manually.

import net.openid.appauth.connectivity.DefaultConnectionBuilder;
import net.openid.appauth.AuthorizationService;

Uri userInfoEndpoint = Uri.parse("https://YOUR_DOMAIN.auth0.com/userinfo");

private AuthorizationService service = new AuthorizationService(this);

authState.performActionWithFreshTokens(service, (accessToken, endpointUri) -> {
// Manually set up the UserInfo request
HttpURLConnection conn = DefaultConnectionBuilder.INSTANCE.openConnection(userInfoEndpoint);
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
conn.setInstanceFollowRedirects(false);

// Read the response from the user info endpoint
String response = Okio.buffer(Okio.source(conn.getInputStream()))
.readString(Charset.forName("UTF-8"));
// Handle the user info response here
});

Using OAuth access tokens to access protected resources

Follow this page to configure API Authorization with resource audience in IDaaS.
In AppAuth, audience value is passed in as additionalParams, set the audience value by setAdditionalParameters, audience value is the url you want to protect. The OAuth Access token can be fetched from OIDAuthState.lastTokenResponse?.accessToken. If you also require a refresh token to be included in the response with the access token, include the offline_access scope.

// build the authorization request by passing in all mandatory attributes.
AuthorizationRequest.Builder builder = new AuthorizationRequest.Builder(
serviceConfiguration,
clientId,
ResponseTypeValues.CODE,
redirectUri

// Add the audience parameter.
Map<String, String> additionalParams = new HashMap<>();
additionalParams.put("audience", "https://protected.com");

AuthorizationRequest request = builder
.setAdditionalParameters(additionalParams) // Add audience here
.build();

When constructing the AuthorizationRequest, you also need to ensure the offline_access scope is included, as this typically requests the refresh token.

AuthorizationRequest request = builder
.setScopes("openid", "profile", "email", "offline_access") // Add the scope for refresh token.
.build();

For sending the authorizationRequest and getting the token, please see the logging in section.