Kotlin Multiplatform OIDC

repository·main·Indexed 41 days ago

https://github.com/kalinjul/kotlin-multiplatform-oidc

A lightweight Kotlin Multiplatform library for implementing OpenID Connect (OIDC) and OAuth 2.0 using the Authorization Code Grant Flow. It supports Android, iOS, Desktop, and WasmJS, providing core functionality via oidc-appsupport and experimental integrations for Ktor and OkHttp.

Tokens
11.2K
Snippets
44
Records
47
Agent score
99%

What's inside Kotlin Multiplatform OIDC

  1. Authenticate using the Authorization Code Flow

    main

    The library uses CodeAuthFlow to implement the Authorization Code Grant Flow. You must obtain an authFlowFactory (platform-specific implementation) via Dependency Injection.

    To start the login process (which opens the system browser) and retrieve tokens:

    1. Call authFlowFactory.createAuthFlow(client).
    2. Call flow.startLogin().
    3. Call flow.continueLogin() to get the resulting tokens.
    val flow = authFlowFactory.createAuthFlow(client)
    flow.startLogin()
    val tokens = flow.continueLogin()
  2. Install OpenIdConnectClient via Swift Package Manager

    main

    To use this library in your iOS project, add the Swift package from the official repository. If you are using a Swift module, add the dependency to your Package.swift file using the exact version required.

    dependencies: [
        .package(url: "https://github.com/kalinjul/OpenIdConnectClient.git", exact: "<version>")
    ],
  3. Integrate with Ktor (experimental)

    main

    Use the oidc-ktor dependency to install the OIDC authentication provider into your HttpClient. This automates token attachment and refreshing.

    Note: Because of Ktor's internal caching, you must manually call ktorHttpClient.clearTokens() if a token is invalidated outside of Ktor's refresh logic (e.g., during a manual logout).

    HttpClient(engine) {
        install(Auth) {
            oidcBearer(
                tokenStore = tokenStore,
                refreshHandler = refreshHandler,
                client = client,
            )
        }
    }
    
    // On logout:
    ktorHttpClient.clearTokens()
  4. Authenticate using the Authorization Code Flow

    main

    To request access tokens, use the CodeAuthFlowFactory. You can specify whether to use an ephemeralBrowserSession (which uses ASWebAuthenticationSession). Use the factory to create an auth flow and call getAccessToken() to perform the exchange.

    let factory = CodeAuthFlowFactory(ephemeralBrowserSession: false)
    let flow = factory.createAuthFlow(client: client)
    do {
        let tokens = try await flow.getAccessToken()
    } catch {
        print(error)
    }
  5. Handle Android Activity/Process Termination during login

    main

    On Android, the login process can be interrupted if the Activity or Process is killed. To handle this, you should register your activity and check if a login can be resumed during onCreate().

    1. Call authFlowFactory.registerActivity(this) in your Activity.
    2. Check flow.canContinueLogin().
    3. If true, call flow.continueLogin() to resume the flow.
    authFlowFactory.registerActivity(this)
    val flow = authFlowFactory.createAuthFlow(client)
    if (flow.canContinueLogin()) {
        val tokens = flow.continueLogin()
    }
  6. Initialize AndroidCodeAuthFlowFactory

    main

    You must maintain a single global instance of AndroidCodeAuthFlowFactory (ideally managed via Dependency Injection). To ensure the library can handle authentication redirects, you must register your activity within its onCreate() method.

    Important:

    • You must call registerActivity() in onCreate() or earlier so the factory can attach to the ComponentActivity lifecycle.
    • The authentication flow should be created and started from an Application or ViewModel scope to ensure it survives Activity.onDestroy() (e.g., during low memory events) and can process redirect results.
    • If you are not using ComponentActivity, you must implement your own custom factory.
    class MainActivity : ComponentActivity() {
        // There should only be one instance of this factory.
        // The flow should also be created and started from an
        // Application or ViewModel scope, so it persists Activity.onDestroy() e.g. on low memory
        // and is still able to process redirect results during login.
        val codeAuthFlowFactory = AndroidCodeAuthFlowFactory(useWebView = false)
        
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            codeAuthFlowFactory.registerActivity(this)
        }
    }
  7. Handle OIDC redirects in Wasm using routing

    main

    Because the redirect occurs in a new instance of your Wasm application, you must use your application's routing mechanism to detect the redirect path and trigger the authentication flow.

    Define a specific subpath (e.g., /redirect) as your redirect URL. When the application detects this path in window.location.pathname, call PlatformCodeAuthFlow.handleRedirect() to complete the authentication process.

    fun main() {
        CanvasBasedWindow(canvasElementId = "wasm-js-app") {
            val currentPath = window.location.pathname
            when {
                currentPath.isBlank() || currentPath == "/" -> {
                    MainView()
                }
                currentPath.startsWith("/redirect") -> {
                    LaunchedEffect(Unit) {
                        PlatformCodeAuthFlow.handleRedirect()
                    }
                }
            }
        }
    }
  8. Set up OIDC authentication for Wasm projects

    main

    Wasm projects do not require declaring a redirect scheme; they must use https with subpaths of your application URL.

    To initiate authentication in a wasmJs target, instantiate WasmCodeAuthFlowFactory. You can optionally pass window size parameters to the constructor.

    When authentication starts, the app opens a new window for login. The redirect is handled by a new instance of your app opened within that login window. You must ensure that PlatformCodeAuthFlow.handleRedirect() is called only within this specific redirect instance.

    val factory = WasmCodeAuthFlowFactory(/* window size parameters */)
  9. Handle Login/Logout continuation after process termination

    main

    Because authentication occurs in a web browser, the Android OS may terminate your application while it is in the background. To resume an interrupted authentication or logout process when the app restarts, use the following patterns:

    Resume Login

    Check if a login is in progress using authFlow.canContinueLogin() and then call authFlow.continueLogin().

    Resume Logout

    Check if a logout is in progress using endSessionFlow.canContinueLogout() and then call endSessionFlow.continueLogout().

    // Resume Login
    if (authFlow.canContinueLogin()) {
        val tokens = authFlow.continueLogin(configureTokenExchange = null)
        // save tokens
    }
    
    // Resume Logout
    if (endSessionFlow.canContinueLogout()) {
        endSessionFlow.continueLogout()
        // clear tokens
    }
  10. Install Kotlin Multiplatform OIDC dependencies

    main

    Add the core library and optional integration modules to your commonMain sourceSet (for KMP) or Android dependencies.

    • oidc-appsupport: Core library.
    • oidc-ktor: Optional Ktor integration (experimental).
    • oidc-okhttp4: Optional OkHttp integration for Android (experimental).
    implementation("io.github.kalinjul.kotlin.multiplatform:oidc-appsupport:<version>")
    implementation("io.github.kalinjul.kotlin.multiplatform:oidc-ktor:<version>") // optional ktor support
    implementation("io.github.kalinjul.kotlin.multiplatform:oidc-okhttp4:<version>") // optional okhttp support (android only)