> For AI agents: [llms.txt](https://docs.trustedauth.com/llms.txt) provides a Markdown index of this documentation.

# Protect a Mobile App

This document describes how to add OIDC authentication to a mobile application using IDaaS and the open-source project [AppAuth](https://appauth.io/).

## Installation

**Android**

AppAuth dependencies are available on [Maven Central](https://central.sonatype.com/artifact/net.openid/appauth).

**Gradle**

```kotlin
dependencies {
    implementation("net.openid:appauth:<version>")
}
```

**Maven**

```xml
<dependency>
    <groupId>net.openid</groupId>
    <artifactId>appauth</artifactId>
    <version>0.11.1</version>
</dependency>
```

**iOS**

With [CocoaPods](https://guides.cocoapods.org/using/getting-started.html), add this to your `podfile`:

```bash
pod 'AppAuth'
```

With [Swift Package Manager](https://www.swift.org/documentation/package-manager/), add this to your `Package.swift`:

```swift
dependencies: [
    .package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.3.0"))
]
```

## Getting started - Endpoints configuration

**Android**

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.

```java
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`.

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

**iOS**

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 the discovery URL.

```swift
let issuer = URL(string: "https://yourIdaasAccount.com/api/oidc")!

// Discover configuration endpoints
OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in
  guard let config = configuration else {
    print("Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown error")")
    return
  }

  // Use the config for further authorization requests
  print("Configuration retrieved successfully: \(config)")
  // Perform the auth request or other actions using the configuration...
}
```

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

## Logging in

**Android**

### 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`

```java
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.

```java
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.

```java
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
          }
      }
  });
```

**iOS**

`OIDExternalUserAgentSession` is a protocol in the AppAuth library that manages the session between the app and the external user agent (the browser), allowing the app to initiate OAuth and handle the final redirect response.

```Swift
class AppDelegate: UIResponder, UIApplicationDelegate {
  // property of the app's AppDelegate
  var currentAuthorizationFlow: OIDExternalUserAgentSession?
}
```

When building authentication request in IOS, use PKCE (Proof Key for Code Exchange) to securely perform the authorization flow. The following shows how to construct an authorization request using PKCE, including a `codeChallenge` and `codeChallengeMethod`.

```Swift
let clientID = "155bc68f-f162-4a4b-bd44-fc4782cd3f0f";
let redirectURI = "net.openid.appauthdemo:/oauth2redirect"; // this is a pointer back to your application

// Build PKCE parameters
let codeVerifier = OIDAuthorizationRequest.generateCodeVerifier()
let codeChallenge = OIDAuthorizationRequest.codeChallenge(forVerifier: codeVerifier)
let codeChallengeMethod = OIDOAuthorizationRequestCodeChallengeMethodS256

// Build authorization request
let request = OIDAuthorizationRequest(configuration: configuration,
                                    clientId: clientID,
                                    scopes: ["openid", "profile", "email"],
                                    redirectURL: redirectURI,
                                    responseType: OIDResponseTypeCode,
                                    codeChallenge: codeChallenge,
                                    codeChallengeMethod: codeChallengeMethod,
                                    )
```

For iOS implementation, AppAuth provides a convenience method to do the token exchange : by using the `OIDAuthState.authState()`, the token exchange can be performed in one step:

```Swift
import AppAuth
import UIKit

//AppDelegate stores the current authorization flow session so it can be resumed when the user returns to the app after authentication.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//authorizing + exchange token
appDelegate.currentAuthorizationFlow =
    OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
  if let authState = authState {
    self.setAuthState(authState)
    let accessToken = authState.lastTokenResponse?.accessToken;
  } else {
    self.setAuthState(nil)
  }
}
```

## Logging out

**Android**

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

```java
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`.

```java
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)
    );
}
```

**iOS**

Build the `OIDEndSessionRequest`.

```swift
let authState = stateManager.getCurrentAuthState()

let endSessionRedirectUri = URL(string: "your.app.scheme:/logout-callback")!

guard let configuration = authState.authorizationServiceConfiguration,
      let idToken = authState.lastTokenResponse?.idToken else {
    // Handle missing configuration or idToken
    return
}

let endSessionRequest = OIDEndSessionRequest(
    configuration: configuration,
    idTokenHint: idToken,
    postLogoutRedirectURL: endSessionRedirectUri,
    additionalParameters: nil
)
```

Use `OIDAuthorizationService.perform` to send the end session request and handle the completion or cancellation of the logout process.

```swift
let appDelegate = UIApplication.shared.delegate as! AppDelegate

OIDAuthorizationService.perform(endSessionRequest) { endSessionResponse, error in
    if let response = endSessionResponse {
        // Transition to the completion view controller (logout complete)
        let logoutCompleteVC = MyAuthCompleteViewController()
        appDelegate.window?.rootViewController = logoutCompleteVC
    } else if let error = error {
        // Handle the cancellation or error case
        let logoutCanceledVC = MyAuthCanceledViewController()
        appDelegate.window?.rootViewController = logoutCanceledVC
    }
}
```

In the code above,`OIDEndSessionRequest` is built using the `currentOIDAuthState`, which contains the authorization
service configuration and the idToken. The request is sent with `OIDAuthorizationService.perform`, and you can handle
the transition to a "complete" or "cancelled" view based on the response.

## Using access tokens

Using the working flow above, the access token you get is the [OIDC Access Token](https://supertokens.com/blog/oauth-vs-oidc#:~:text=The%20primary%20difference%20between%20OIDC,OAuth%20provides%20access%20to%20resources), 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](https://supertokens.com/blog/oauth-vs-oidc#:~:text=The%20primary%20difference%20between%20OIDC,OAuth%20provides%20access%20to%20resources), that can be used to access protected resource.

### Using OIDC Access Tokens to access User Information

**Android**

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

```java
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
});
```

**iOS**

`OIDAuthState.performAction()` can be used to call endpoints using the availableaccess token.

AccessToken can be fetched by `authState.lastTokenResponse.accessToken`.

```Swift
let userinfoEndpoint = URL(string:"https://YOUR_DOMAIN.auth0.com/userinfo")!
self.authState?.performAction() { (accessToken, error) in

  if error != nil  {
    return
  }
  let accessToken: String? = self.authState?.lastTokenResponse?.accessToken

  // Add access token to request
  var performActionWithFreshTokensRequest = URLRequest(url: userinfoEndpoint)
  urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"]

  // Perform request
      let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
          if let data = data {
              do {
                  // userInfo JSON response
                  let json = try JSONSerialization.jsonObject(with: data, options: [])
              } catch {
                 //exception
              }
          }
      }
}
```

### Using OAuth access tokens to access protected resources

**Android**

Follow this page to [configure API Authorization with resource audience](/docs/applications-and-sso/oidc/protecting-spa.md) 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.

```java
// 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`.

```java
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](#logging-in).

**iOS**

Follow this page to [configure API Authorization with resource audience](/docs/applications-and-sso/oidc/protecting-spa.md) in your IDaaS.

In AppAuth for iOS, the audience value is passed in as `additionalParameters`. You can set the audience value by using the `additionalParameters` field of `OIDAuthorizationRequest`, where `audience` is the URL of the resource you want to protect.
The OAuth access token can be fetched from `OIDAuthState.lastTokenResponse?.accessToken`.

If you also require a refresh token to be include in the response with the access token, include the offline_access scope.

```swift
// Create an OIDAuthorizationRequest with all required attributes
let configuration = OIDServiceConfiguration(
    authorizationEndpoint: authorizationEndpoint,
    tokenEndpoint: tokenEndpoint
)

// Add the audience parameter
let additionalParams = ["audience": "https://protected.com"]

let request = OIDAuthorizationRequest(
    configuration: configuration,
    clientId: clientId,
    scopes: ["openid", "profile", "email", "offline_access"], // Add the scope for refresh token
    redirectURL: redirectUri,
    responseType: OIDResponseTypeCode,
    additionalParameters: additionalParams // Add audience here
)

// perform the authorization request. Exchange the authorization code for the OAuth access token and refresh token
appDelegate.currentAuthorizationFlow =
    OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
  if let authState = authState {
    self.setAuthState(authState)
    // fetch the access token and refresh token by using authState.lastTokenResponse?.accessToken and authState.lastTokenResponse?.refreshToken
  } else {
    self.setAuthState(nil)
  }
}
```
