Android OAuth (AppAuth Android)

authentication libraryAndroidOAuth 2.0OpenID ConnectmobilePKCEsecurity

Library

Android OAuth (AppAuth Android)

Overview

AppAuth for Android is the official OAuth 2.0 and OpenID Connect client library for Android native applications from the OpenID Foundation. As of 2025, it has become the industry standard as an RFC 8252-compliant OAuth implementation for native apps, providing compatibility with major authentication providers including Google, Okta, Auth0, and Microsoft Azure AD. It comprehensively supports secure authentication flows with PKCE, secure browser integration using Custom Tabs or Chrome browser, dynamic client registration, and session management features. It supports redirect URI handling for both Android Secure App Links and custom schemes, and also supports advanced HTTP configurations and certificate pinning for enterprise environments.

Details

AppAuth for Android 0.11 series provides secure OAuth 2.0 flows optimized for mobile applications as a fully RFC 8252 "OAuth 2.0 for Native Apps" compliant implementation. PKCE (Proof Key for Code Exchange) is automatically implemented to prevent authorization code interception attacks. The AuthState class provides comprehensive authentication state management, enabling automatic access token refresh, persistence, and restoration. By using Custom Tabs or Chrome browser, it avoids vulnerabilities in in-app web views and protects user credentials. Dynamic client registration allows integration with OAuth providers without pre-configuration.

Key Features

  • Full RFC 8252 Compliance: OAuth 2.0 best practices implementation for native apps
  • Automatic PKCE Implementation: Automatic prevention of authorization code interception attacks
  • Custom Tabs Integration: Secure browser integration avoiding WebView vulnerabilities
  • Comprehensive State Management: Authentication state persistence and automatic token refresh with AuthState
  • Multi-Provider Support: Compatibility with Google, Microsoft, Auth0, Okta, etc.
  • Enterprise Ready: Certificate pinning, custom HTTP configuration, browser restrictions

Advantages and Disadvantages

Advantages

  • Official OpenID Foundation library providing industry-standard security implementation
  • Automatic PKCE implementation achieves high security level without additional implementation
  • Custom Tabs usage balances user experience improvement and security enhancement
  • Pre-verified compatibility with major authentication providers reduces integration costs
  • Simple and robust authentication state management through AuthState class
  • Supports advanced security requirements in enterprise environments

Disadvantages

  • Dependency on Custom Tabs or Chrome browser creates limitations on older devices
  • Native app specific, not usable for WebView or hybrid applications
  • Does not support authentication methods other than OAuth 2.0 (custom authentication, etc.)
  • Constraints exist for customizing complex authentication flows
  • Dynamic configuration changes in multi-tenant environments require separate implementation
  • Relatively large library size requires consideration of app size impact

Reference Pages

Usage Examples

Basic Setup and Gradle Dependencies

// app/build.gradle - Add AppAuth for Android dependencies
dependencies {
    implementation 'net.openid:appauth:0.11.1'
    
    // Android Support Library (if needed)
    implementation 'androidx.browser:browser:1.4.0'
    implementation 'androidx.appcompat:appcompat:1.4.2'
}

// Manifest placeholder setup for custom scheme
android {
    defaultConfig {
        manifestPlaceholders = [
            'appAuthRedirectScheme': 'com.example.myapp'
        ]
    }
}
<!-- AndroidManifest.xml - Redirect URI receiver configuration -->
<application>
    <!-- RedirectUriReceiverActivity for custom scheme -->
    <activity
        android:name="net.openid.appauth.RedirectUriReceiverActivity"
        android:exported="true"
        tools:node="replace">
        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="${appAuthRedirectScheme}" />
        </intent-filter>
    </activity>
    
    <!-- HTTPS App Link configuration (recommended) -->
    <activity
        android:name="net.openid.appauth.RedirectUriReceiverActivity"
        android:exported="true"
        tools:node="replace">
        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https"
                  android:host="myapp.example.com"
                  android:path="/oauth2redirect" />
        </intent-filter>
    </activity>
</application>
// res/raw/auth_config.json - OAuth configuration file
{
  "client_id": "your-client-id.apps.googleusercontent.com",
  "redirect_uri": "com.example.myapp:/oauth2redirect",
  "end_session_redirect_uri": "com.example.myapp:/logout",
  "authorization_scope": "openid email profile",
  "discovery_uri": "https://accounts.google.com/.well-known/openid-configuration",
  "authorization_endpoint_uri": "",
  "token_endpoint_uri": "",
  "https_required": true
}

Authentication Service Configuration with OpenID Connect Discovery

// AuthenticationManager.java - Authentication management class
import net.openid.appauth.*;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;

public class AuthenticationManager {
    private static final String TAG = "AuthManager";
    private static final String AUTH_STATE_KEY = "auth_state";
    private static final int RC_AUTH = 100;
    
    private Context mContext;
    private AuthorizationService mAuthService;
    private AuthState mAuthState;
    private AuthorizationServiceConfiguration mServiceConfig;
    
    // OAuth configuration
    private static final String CLIENT_ID = "your-client-id.apps.googleusercontent.com";
    private static final Uri REDIRECT_URI = Uri.parse("com.example.myapp:/oauth2redirect");
    private static final Uri DISCOVERY_URI = Uri.parse("https://accounts.google.com/.well-known/openid-configuration");
    private static final String SCOPE = "openid email profile";
    
    public AuthenticationManager(Context context) {
        mContext = context;
        mAuthService = new AuthorizationService(context);
        mAuthState = readAuthState();
        
        // Execute OpenID Connect Discovery
        initializeOAuthConfiguration();
    }
    
    private void initializeOAuthConfiguration() {
        // Retrieve service configuration via Discovery
        AuthorizationServiceConfiguration.fetchFromIssuer(
            DISCOVERY_URI,
            new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
                @Override
                public void onFetchConfigurationCompleted(
                        @Nullable AuthorizationServiceConfiguration serviceConfiguration,
                        @Nullable AuthorizationException ex) {
                    if (ex != null) {
                        Log.e(TAG, "Failed to retrieve OAuth configuration: " + ex.getMessage());
                        return;
                    }
                    
                    mServiceConfig = serviceConfiguration;
                    Log.i(TAG, "OAuth configuration retrieved successfully");
                    
                    // Create new AuthState if empty
                    if (mAuthState.getAuthorizationServiceConfiguration() == null) {
                        mAuthState = new AuthState(serviceConfiguration);
                        writeAuthState(mAuthState);
                    }
                }
            }
        );
    }
    
    // Manual service configuration (when Discovery is unavailable)
    private void configureOAuthManually() {
        mServiceConfig = new AuthorizationServiceConfiguration(
            Uri.parse("https://accounts.google.com/o/oauth2/v2/auth"), // authorization endpoint
            Uri.parse("https://oauth2.googleapis.com/token")            // token endpoint
        );
        
        mAuthState = new AuthState(mServiceConfig);
        writeAuthState(mAuthState);
    }
    
    // Authentication state persistence
    private void writeAuthState(@NonNull AuthState state) {
        SharedPreferences authPrefs = mContext.getSharedPreferences("auth_state", Context.MODE_PRIVATE);
        authPrefs.edit()
                .putString(AUTH_STATE_KEY, state.jsonSerializeString())
                .apply();
    }
    
    // Authentication state restoration
    @NonNull
    private AuthState readAuthState() {
        SharedPreferences authPrefs = mContext.getSharedPreferences("auth_state", Context.MODE_PRIVATE);
        String stateJson = authPrefs.getString(AUTH_STATE_KEY, null);
        if (stateJson != null) {
            try {
                return AuthState.jsonDeserialize(stateJson);
            } catch (JSONException ex) {
                Log.w(TAG, "Failed to restore authentication state, creating new: " + ex.getMessage());
            }
        }
        return new AuthState();
    }
    
    // Authentication state check
    public boolean isAuthenticated() {
        return mAuthState.isAuthorized() && !mAuthState.getNeedsTokenRefresh();
    }
    
    // Current user information retrieval
    @Nullable
    public String getCurrentUserId() {
        return mAuthState.getIdToken();
    }
    
    @Nullable
    public String getAccessToken() {
        return mAuthState.getAccessToken();
    }
}

Authentication Flow Implementation with PKCE Support

// AuthenticationManager.java (continued) - Authentication flow implementation
public class AuthenticationManager {
    
    // Start authentication (PKCE automatically implemented)
    public void startAuthentication(Activity activity) {
        if (mServiceConfig == null) {
            Log.e(TAG, "OAuth configuration not initialized");
            return;
        }
        
        // Create authorization request (PKCE is automatically implemented)
        AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder(
            mServiceConfig,
            CLIENT_ID,
            ResponseTypeValues.CODE,
            REDIRECT_URI
        );
        
        // Set optional parameters
        AuthorizationRequest authRequest = authRequestBuilder
            .setScope(SCOPE)
            .setLoginHint("[email protected]") // Optional: login hint
            .setPrompt("select_account")      // Optional: force account selection
            .build();
        
        // Start authentication in browser
        Intent authIntent = mAuthService.getAuthorizationRequestIntent(authRequest);
        activity.startActivityForResult(authIntent, RC_AUTH);
    }
    
    // Handle authorization response
    public void handleAuthorizationResponse(Intent data, AuthCallback callback) {
        AuthorizationResponse authResponse = AuthorizationResponse.fromIntent(data);
        AuthorizationException authException = AuthorizationException.fromIntent(data);
        
        // Update authentication state
        mAuthState.update(authResponse, authException);
        writeAuthState(mAuthState);
        
        if (authResponse != null) {
            // Authentication success: execute token exchange
            exchangeTokens(authResponse, callback);
        } else {
            // Authentication failure
            Log.e(TAG, "Authentication failed: " + (authException != null ? authException.getMessage() : "Unknown error"));
            callback.onAuthError(authException);
        }
    }
    
    // Token exchange processing
    private void exchangeTokens(AuthorizationResponse authResponse, AuthCallback callback) {
        TokenRequest tokenRequest = authResponse.createTokenExchangeRequest();
        
        mAuthService.performTokenRequest(
            tokenRequest,
            new AuthorizationService.TokenResponseCallback() {
                @Override
                public void onTokenRequestCompleted(
                        @Nullable TokenResponse tokenResponse,
                        @Nullable AuthorizationException ex) {
                    
                    // Update authentication state
                    mAuthState.update(tokenResponse, ex);
                    writeAuthState(mAuthState);
                    
                    if (tokenResponse != null) {
                        Log.i(TAG, "Token exchange successful");
                        callback.onAuthSuccess(tokenResponse);
                    } else {
                        Log.e(TAG, "Token exchange failed: " + (ex != null ? ex.getMessage() : "Unknown error"));
                        callback.onAuthError(ex);
                    }
                }
            }
        );
    }
    
    // API execution with fresh tokens
    public void performActionWithFreshTokens(AuthStateAction action) {
        mAuthState.performActionWithFreshTokens(
            mAuthService,
            new AuthState.AuthStateAction() {
                @Override
                public void execute(
                        @Nullable String accessToken,
                        @Nullable String idToken,
                        @Nullable AuthorizationException ex) {
                    
                    if (ex != null) {
                        Log.e(TAG, "Token refresh failed: " + ex.getMessage());
                        action.onError(ex);
                        return;
                    }
                    
                    // Execute action with fresh tokens
                    action.onSuccess(accessToken, idToken);
                }
            }
        );
    }
    
    // Logout processing
    public void logout() {
        // Clear authentication state
        mAuthState = new AuthState();
        writeAuthState(mAuthState);
        
        // Dispose AuthorizationService
        mAuthService.dispose();
        mAuthService = new AuthorizationService(mContext);
        
        Log.i(TAG, "Logout completed");
    }
    
    // Resource cleanup
    public void dispose() {
        if (mAuthService != null) {
            mAuthService.dispose();
        }
    }
    
    // Callback interface
    public interface AuthCallback {
        void onAuthSuccess(TokenResponse tokenResponse);
        void onAuthError(AuthorizationException exception);
    }
    
    // Action interface
    public interface AuthStateAction {
        void onSuccess(String accessToken, String idToken);
        void onError(AuthorizationException exception);
    }
}

OAuth Integration and Lifecycle Management in Activity

// MainActivity.java - Main Activity integration example
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.TokenResponse;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private static final int RC_AUTH = 100;
    
    private AuthenticationManager mAuthManager;
    private Button mSignInButton;
    private Button mSignOutButton;
    private Button mApiCallButton;
    private TextView mUserInfoTextView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // Initialize AuthenticationManager
        mAuthManager = new AuthenticationManager(this);
        
        // Initialize UI
        initializeUI();
        
        // Check authentication state and update UI
        updateUI();
    }
    
    private void initializeUI() {
        mSignInButton = findViewById(R.id.sign_in_button);
        mSignOutButton = findViewById(R.id.sign_out_button);
        mApiCallButton = findViewById(R.id.api_call_button);
        mUserInfoTextView = findViewById(R.id.user_info_text);
        
        // Sign in button
        mSignInButton.setOnClickListener(v -> {
            Log.i(TAG, "Starting sign in");
            mAuthManager.startAuthentication(this);
        });
        
        // Sign out button
        mSignOutButton.setOnClickListener(v -> {
            Log.i(TAG, "Executing sign out");
            mAuthManager.logout();
            updateUI();
            Toast.makeText(this, "Sign out completed", Toast.LENGTH_SHORT).show();
        });
        
        // API call button
        mApiCallButton.setOnClickListener(v -> {
            performApiCall();
        });
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        
        if (requestCode == RC_AUTH) {
            // Handle authorization response
            mAuthManager.handleAuthorizationResponse(data, new AuthenticationManager.AuthCallback() {
                @Override
                public void onAuthSuccess(TokenResponse tokenResponse) {
                    runOnUiThread(() -> {
                        Log.i(TAG, "Authentication successful");
                        updateUI();
                        Toast.makeText(MainActivity.this, "Sign in successful", Toast.LENGTH_SHORT).show();
                        
                        // Fetch user information
                        fetchUserInfo();
                    });
                }
                
                @Override
                public void onAuthError(AuthorizationException exception) {
                    runOnUiThread(() -> {
                        Log.e(TAG, "Authentication error: " + exception.getMessage());
                        updateUI();
                        Toast.makeText(MainActivity.this, "Sign in error: " + exception.getMessage(), 
                                Toast.LENGTH_LONG).show();
                    });
                }
            });
        }
    }
    
    // Update UI state
    private void updateUI() {
        boolean isAuthenticated = mAuthManager.isAuthenticated();
        
        mSignInButton.setVisibility(isAuthenticated ? android.view.View.GONE : android.view.View.VISIBLE);
        mSignOutButton.setVisibility(isAuthenticated ? android.view.View.VISIBLE : android.view.View.GONE);
        mApiCallButton.setVisibility(isAuthenticated ? android.view.View.VISIBLE : android.view.View.GONE);
        
        if (!isAuthenticated) {
            mUserInfoTextView.setText("Please sign in");
        }
    }
    
    // Fetch user information
    private void fetchUserInfo() {
        mAuthManager.performActionWithFreshTokens(new AuthenticationManager.AuthStateAction() {
            @Override
            public void onSuccess(String accessToken, String idToken) {
                // Example: Google User Info API call
                fetchUserInfoFromAPI(accessToken);
            }
            
            @Override
            public void onError(AuthorizationException exception) {
                runOnUiThread(() -> {
                    Log.e(TAG, "Token retrieval error: " + exception.getMessage());
                    mUserInfoTextView.setText("User info retrieval error");
                });
            }
        });
    }
    
    // API call implementation example
    private void performApiCall() {
        mAuthManager.performActionWithFreshTokens(new AuthenticationManager.AuthStateAction() {
            @Override
            public void onSuccess(String accessToken, String idToken) {
                // Call protected API
                callProtectedAPI(accessToken);
            }
            
            @Override
            public void onError(AuthorizationException exception) {
                runOnUiThread(() -> {
                    Log.e(TAG, "API call error: " + exception.getMessage());
                    Toast.makeText(MainActivity.this, "API call error", Toast.LENGTH_SHORT).show();
                });
            }
        });
    }
    
    // Google User Info API call
    private void fetchUserInfoFromAPI(String accessToken) {
        // HTTP client implementation (e.g., OkHttp, Volley, etc.)
        // Implementation example: https://www.googleapis.com/oauth2/v2/userinfo
        new Thread(() -> {
            try {
                // HTTP request implementation
                String userInfo = makeHttpRequest("https://www.googleapis.com/oauth2/v2/userinfo", accessToken);
                
                runOnUiThread(() -> {
                    mUserInfoTextView.setText("User info: " + userInfo);
                });
            } catch (Exception e) {
                Log.e(TAG, "User info retrieval error", e);
                runOnUiThread(() -> {
                    mUserInfoTextView.setText("User info retrieval failed");
                });
            }
        }).start();
    }
    
    // Protected API call
    private void callProtectedAPI(String accessToken) {
        // Implementation for calling any protected API endpoint
        new Thread(() -> {
            try {
                String response = makeHttpRequest("https://api.example.com/protected", accessToken);
                
                runOnUiThread(() -> {
                    Log.i(TAG, "API response: " + response);
                    Toast.makeText(MainActivity.this, "API call successful", Toast.LENGTH_SHORT).show();
                });
            } catch (Exception e) {
                Log.e(TAG, "API call error", e);
                runOnUiThread(() -> {
                    Toast.makeText(MainActivity.this, "API call failed", Toast.LENGTH_SHORT).show();
                });
            }
        }).start();
    }
    
    // HTTP request helper method
    private String makeHttpRequest(String url, String accessToken) throws Exception {
        // Actual HTTP client implementation (OkHttp, HttpURLConnection, etc.)
        java.net.URL requestUrl = new java.net.URL(url);
        java.net.HttpURLConnection connection = (java.net.HttpURLConnection) requestUrl.openConnection();
        connection.setRequestProperty("Authorization", "Bearer " + accessToken);
        connection.setRequestMethod("GET");
        
        // Response reading implementation
        if (connection.getResponseCode() == 200) {
            java.io.BufferedReader reader = new java.io.BufferedReader(
                new java.io.InputStreamReader(connection.getInputStream()));
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();
            return response.toString();
        } else {
            throw new Exception("HTTP " + connection.getResponseCode() + ": " + connection.getResponseMessage());
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mAuthManager != null) {
            mAuthManager.dispose();
        }
    }
}

Enterprise Security Configuration and Advanced Settings

// EnterpriseAuthConfiguration.java - Enterprise configuration
import net.openid.appauth.*;
import net.openid.appauth.browser.*;
import android.content.Context;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class EnterpriseAuthConfiguration {
    
    // HTTP connection configuration with certificate pinning
    public static AuthorizationService createSecureAuthService(Context context) {
        AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
            .setConnectionBuilder(new SecureConnectionBuilder())
            .setBrowserMatcher(createSecureBrowserMatcher())
            .setSkipIssuerHttpsCheck(false) // Always false in production
            .build();
        
        return new AuthorizationService(context, appAuthConfig);
    }
    
    // Secure HTTP connection builder
    private static class SecureConnectionBuilder implements ConnectionBuilder {
        @Override
        public HttpURLConnection openConnection(Uri uri) throws IOException {
            URL url = new URL(uri.toString());
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            
            if (connection instanceof HttpsURLConnection) {
                HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
                
                // Custom SSLSocketFactory configuration (certificate pinning)
                SSLSocketFactory customSSLFactory = createPinnedSSLSocketFactory();
                if (customSSLFactory != null) {
                    httpsConnection.setSSLSocketFactory(customSSLFactory);
                }
                
                // Custom HostnameVerifier configuration (optional)
                // httpsConnection.setHostnameVerifier(createCustomHostnameVerifier());
            }
            
            // Additional HTTP header configuration
            connection.setRequestProperty("User-Agent", "MyEnterpriseApp/1.0");
            connection.setConnectTimeout(30000); // 30 second timeout
            connection.setReadTimeout(30000);
            
            return connection;
        }
        
        private SSLSocketFactory createPinnedSSLSocketFactory() {
            // Certificate pinning implementation
            // In actual implementation, use OkHttp's CertificatePinner, etc.
            return null; // Placeholder
        }
    }
    
    // Secure browser selection configuration
    private static BrowserMatcher createSecureBrowserMatcher() {
        // Allow only Chrome Custom Tabs or Samsung Browser Custom Tabs
        return new BrowserAllowList(
            VersionedBrowserMatcher.CHROME_CUSTOM_TAB,
            VersionedBrowserMatcher.SAMSUNG_CUSTOM_TAB
        );
    }
    
    // Block specific browser versions
    private static BrowserMatcher createRestrictiveBrowserMatcher() {
        // Block old Samsung Browser versions
        return new BrowserDenyList(
            new VersionedBrowserMatcher(
                Browsers.SBrowser.PACKAGE_NAME,
                Browsers.SBrowser.SIGNATURE_SET,
                true, // When using Custom Tab
                VersionRange.atMost("5.3") // Block versions 5.3 and below
            )
        );
    }
    
    // Dynamic client registration implementation
    public static void performDynamicClientRegistration(
            AuthorizationServiceConfiguration serviceConfig,
            Uri redirectUri,
            RegistrationCallback callback) {
        
        RegistrationRequest registrationRequest = new RegistrationRequest.Builder(
            serviceConfig,
            java.util.Arrays.asList(redirectUri)
        )
        .setTokenEndpointAuthenticationMethod(ClientSecretBasic.NAME)
        .setApplicationType(RegistrationRequest.APPLICATION_TYPE_NATIVE)
        .build();
        
        AuthorizationService service = new AuthorizationService(null); // Use actual context
        service.performRegistrationRequest(
            registrationRequest,
            new AuthorizationService.RegistrationResponseCallback() {
                @Override
                public void onRegistrationRequestCompleted(
                        @androidx.annotation.Nullable RegistrationResponse resp,
                        @androidx.annotation.Nullable AuthorizationException ex) {
                    
                    if (resp != null) {
                        // Registration successful: save client ID and secret
                        AuthState authState = new AuthState(resp);
                        callback.onRegistrationSuccess(authState, resp.clientId, resp.clientSecret);
                    } else {
                        // Registration failed
                        callback.onRegistrationError(ex);
                    }
                }
            }
        );
    }
    
    public interface RegistrationCallback {
        void onRegistrationSuccess(AuthState authState, String clientId, String clientSecret);
        void onRegistrationError(AuthorizationException exception);
    }
    
    // Client secret authentication (deprecated: for demo purposes only)
    public static void performTokenRequestWithClientSecret(
            AuthorizationService service,
            TokenRequest tokenRequest,
            String clientSecret,
            AuthorizationService.TokenResponseCallback callback) {
        
        // ClientSecretBasic authentication (HTTP Basic authentication)
        ClientAuthentication clientAuth = new ClientSecretBasic(clientSecret);
        
        service.performTokenRequest(tokenRequest, clientAuth, callback);
    }
    
    // End session (logout) implementation
    public static void performEndSession(
            Activity activity,
            AuthorizationServiceConfiguration serviceConfig,
            String idToken,
            Uri postLogoutRedirectUri) {
        
        EndSessionRequest endSessionRequest = new EndSessionRequest.Builder(serviceConfig)
            .setIdTokenHint(idToken)
            .setPostLogoutRedirectUri(postLogoutRedirectUri)
            .build();
        
        AuthorizationService authService = new AuthorizationService(activity);
        Intent endSessionIntent = authService.getEndSessionRequestIntent(endSessionRequest);
        activity.startActivityForResult(endSessionIntent, 101); // RC_END_SESSION
    }
}

Error Handling and Log Management

// AuthErrorHandler.java - Error handling and log management
import net.openid.appauth.AuthorizationException;
import android.util.Log;

public class AuthErrorHandler {
    private static final String TAG = "AuthErrorHandler";
    
    // Detailed analysis of OAuth exceptions
    public static void handleAuthorizationException(AuthorizationException ex) {
        if (ex == null) return;
        
        Log.e(TAG, "Authentication error details:");
        Log.e(TAG, "  Error code: " + ex.code);
        Log.e(TAG, "  Error message: " + ex.getMessage());
        Log.e(TAG, "  Error detail: " + ex.error);
        Log.e(TAG, "  Error description: " + ex.errorDescription);
        Log.e(TAG, "  Error URI: " + ex.errorUri);
        
        // Error type specific handling
        switch (ex.type) {
            case AuthorizationException.TYPE_OAUTH_AUTHORIZATION_ERROR:
                handleOAuthAuthorizationError(ex);
                break;
            case AuthorizationException.TYPE_OAUTH_TOKEN_ERROR:
                handleOAuthTokenError(ex);
                break;
            case AuthorizationException.TYPE_OAUTH_REGISTRATION_ERROR:
                handleRegistrationError(ex);
                break;
            case AuthorizationException.TYPE_GENERAL_ERROR:
                handleGeneralError(ex);
                break;
            default:
                Log.e(TAG, "Unknown error type: " + ex.type);
        }
    }
    
    private static void handleOAuthAuthorizationError(AuthorizationException ex) {
        Log.w(TAG, "OAuth authorization error handling");
        
        // Common authorization error handling
        if ("access_denied".equals(ex.error)) {
            Log.i(TAG, "User denied authorization");
        } else if ("invalid_request".equals(ex.error)) {
            Log.e(TAG, "Invalid request");
        } else if ("unauthorized_client".equals(ex.error)) {
            Log.e(TAG, "Client authentication failed");
        } else if ("unsupported_response_type".equals(ex.error)) {
            Log.e(TAG, "Unsupported response type");
        } else if ("invalid_scope".equals(ex.error)) {
            Log.e(TAG, "Invalid scope");
        } else if ("server_error".equals(ex.error)) {
            Log.e(TAG, "Authorization server error");
        } else if ("temporarily_unavailable".equals(ex.error)) {
            Log.w(TAG, "Authorization server temporarily unavailable");
        }
    }
    
    private static void handleOAuthTokenError(AuthorizationException ex) {
        Log.w(TAG, "OAuth token error handling");
        
        if ("invalid_request".equals(ex.error)) {
            Log.e(TAG, "Invalid token request");
        } else if ("invalid_client".equals(ex.error)) {
            Log.e(TAG, "Client authentication failed");
        } else if ("invalid_grant".equals(ex.error)) {
            Log.e(TAG, "Invalid authorization grant");
        } else if ("unauthorized_client".equals(ex.error)) {
            Log.e(TAG, "Unauthorized client");
        } else if ("unsupported_grant_type".equals(ex.error)) {
            Log.e(TAG, "Unsupported grant type");
        } else if ("invalid_scope".equals(ex.error)) {
            Log.e(TAG, "Invalid scope");
        }
    }
    
    private static void handleRegistrationError(AuthorizationException ex) {
        Log.w(TAG, "Dynamic client registration error handling");
        
        if ("invalid_redirect_uri".equals(ex.error)) {
            Log.e(TAG, "Invalid redirect URI");
        } else if ("invalid_client_metadata".equals(ex.error)) {
            Log.e(TAG, "Invalid client metadata");
        }
    }
    
    private static void handleGeneralError(AuthorizationException ex) {
        Log.w(TAG, "General error handling");
        
        // Network-related errors
        if (ex.getCause() instanceof java.net.UnknownHostException) {
            Log.e(TAG, "Network connection error: Host not found");
        } else if (ex.getCause() instanceof java.net.SocketTimeoutException) {
            Log.e(TAG, "Network timeout");
        } else if (ex.getCause() instanceof javax.net.ssl.SSLException) {
            Log.e(TAG, "SSL/TLS connection error");
        }
    }
    
    // Generate user-friendly error messages
    public static String getUserFriendlyErrorMessage(AuthorizationException ex) {
        if (ex == null) return "An unknown error occurred";
        
        switch (ex.type) {
            case AuthorizationException.TYPE_OAUTH_AUTHORIZATION_ERROR:
                if ("access_denied".equals(ex.error)) {
                    return "Authentication was cancelled";
                } else if ("server_error".equals(ex.error)) {
                    return "An error occurred on the authentication server. Please try again later";
                }
                break;
                
            case AuthorizationException.TYPE_OAUTH_TOKEN_ERROR:
                if ("invalid_grant".equals(ex.error)) {
                    return "Authentication expired. Please sign in again";
                }
                break;
                
            case AuthorizationException.TYPE_GENERAL_ERROR:
                if (ex.getCause() instanceof java.net.UnknownHostException) {
                    return "Please check your network connection";
                } else if (ex.getCause() instanceof java.net.SocketTimeoutException) {
                    return "Connection timed out. Please try again";
                }
                break;
        }
        
        return "Authentication error occurred: " + ex.getMessage();
    }
    
    // Security audit log
    public static void logSecurityEvent(String event, String details) {
        Log.i(TAG, String.format("[SECURITY] %s - %s", event, details));
        
        // In actual implementation, send security logs to dedicated system
        // analyticsService.trackSecurityEvent(event, details);
    }
}