Ppoppo Docs

Integrate on mobile (iOS & Android)

This guide adapts the OAuth2 PKCE flow to mobile. One rule matters above all: use the system browser, never an embedded WebView. The flow is unchanged — generate a PKCE pair, authorize in the browser, receive a deep-link callback, and exchange the code on your backend — only the platform plumbing differs. PKCE generation is covered in the OAuth2 PKCE guide; the snippets below focus on the mobile-specific parts.

iOS (Swift)

Use ASWebAuthenticationSession with a custom callback scheme:

import AuthenticationServices

func startAuth() {
    let state = UUID().uuidString
    KeychainHelper.save(key: "oauth_state", value: state)
    let challenge = generateCodeChallenge(verifier: codeVerifier)  // see PKCE guide

    var components = URLComponents(string: "https://accounts.ppoppo.com/oauth/authorize")!
    components.queryItems = [
        .init(name: "client_id", value: Config.ppoppoClientId),
        .init(name: "redirect_uri", value: "yourapp://auth/callback"),
        .init(name: "response_type", value: "code"),
        .init(name: "scope", value: "openid profile"),
        .init(name: "state", value: state),
        .init(name: "code_challenge", value: challenge),
        .init(name: "code_challenge_method", value: "S256"),
    ]

    let session = ASWebAuthenticationSession(url: components.url!, callbackURLScheme: "yourapp") {
        callbackURL, _ in
        guard let url = callbackURL else { return }
        self.handleCallback(url: url)   // verify state, then exchange the code on your backend
    }
    session.presentationContextProvider = self
    session.start()
}

In handleCallback, confirm the returned state matches the Keychain value before exchanging the code.

Android (Kotlin)

Use Custom Tabs to launch the browser:

val authUri = Uri.Builder()
    .scheme("https").authority("accounts.ppoppo.com")
    .appendPath("oauth").appendPath("authorize")
    .appendQueryParameter("client_id", BuildConfig.PPOPPO_CLIENT_ID)
    .appendQueryParameter("redirect_uri", "yourapp://auth/callback")
    .appendQueryParameter("response_type", "code")
    .appendQueryParameter("scope", "openid profile")
    .appendQueryParameter("state", state)
    .appendQueryParameter("code_challenge", codeChallenge)
    .appendQueryParameter("code_challenge_method", "S256")
    .build()

CustomTabsIntent.Builder().build().launchUrl(activity, authUri)

Register the callback scheme so the deep link returns to your app:

<activity android:name=".AuthCallbackActivity" android:exported="true">
    <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="yourapp" android:host="auth" android:path="/callback" />
    </intent-filter>
</activity>

Refreshing tokens on mobile

Store the refresh token in secure platform storage — the iOS Keychain or Android EncryptedSharedPreferences — and refresh proactively, before the one-hour access-token expiry:

if tokenExpiresAt.timeIntervalSinceNow < 300 { refreshTokens() }

If a refresh fails with invalid_grant, the user has revoked access — clear stored tokens and restart the flow. See the Sign-in reference for the refresh request.