OAuth client for Kotlin: integration with providers via ktor-client and kotlinx-serialization
OAuth 2.0 is a standard authorization protocol that allows applications to obtain limited access to user resources without transmitting their credentials. In the Kotlin ecosystem, thanks to JVM compatibility and multiplatform capabilities, you can efficiently implement an OAuth client using modern libraries. In this article, we will look at creating an OAuth client in Kotlin using ktor-client for HTTP requests and kotlinx-serialization for JSON processing.
Main components of an OAuth client
- HTTP client —
ktor-clientfor sending requests to authorization and resource servers. - Serialization —
kotlinx-serializationfor parsing responses into data classes. - Token storage — in-memory or persistent (SharedPreferences, files).
- State manager — handling authorization code, refresh token, CSRF protection.
Adding dependencies
To work with OAuth in Kotlin, add the following dependencies to build.gradle.kts (or build.gradle):
// build.gradle.ktsdependencies { implementation("io.ktor:ktor-client-core:2.3.12") implementation("io.ktor:ktor-client-cio:2.3.12") // engine implementation("io.ktor:ktor-client-content-negotiation:2.3.12") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")}Basic architecture of an OAuth client
Let's split the logic into three layers:
- Models (DTO) — data classes for requests/responses.
- Service — a class managing authorization flows.
- Client — a wrapper over ktor-client with methods for different grants.
Data models
import kotlinx.serialization.SerialNameimport kotlinx.serialization.Serializable
@Serializabledata class TokenResponse( @SerialName("access_token") val accessToken: String, @SerialName("token_type") val tokenType: String = "Bearer", @SerialName("expires_in") val expiresIn: Long? = null, @SerialName("refresh_token") val refreshToken: String? = null, @SerialName("scope") val scope: String? = null, @SerialName("id_token") val idToken: String? = null // for OpenID Connect)
@Serializabledata class OAuthError( val error: String, @SerialName("error_description") val errorDescription: String? = null)OAuth2 client
import io.ktor.client.*import io.ktor.client.call.*import io.ktor.client.plugins.contentnegotiation.*import io.ktor.client.request.*import io.ktor.client.statement.*import io.ktor.http.*import io.ktor.serialization.kotlinx.json.*import kotlinx.serialization.json.Json
class OAuth2Client( private val clientId: String, private val clientSecret: String, private val redirectUri: String, private val tokenEndpoint: String, private val authEndpoint: String) { private val httpClient = HttpClient(CIO) { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true isLenient = true }) } }
// Generate URL for redirect to authorization page fun getAuthorizationUrl(state: String, scope: String = "openid profile email"): String { return "$authEndpoint?response_type=code" + "&client_id=$clientId" + "&redirect_uri=${java.net.URLEncoder.encode(redirectUri, "UTF-8")}" + "&scope=${java.net.URLEncoder.encode(scope, "UTF-8")} Also in library
Rate Limiting in Swift: Managing Request Frequency in iOS/macOS
Working with S3 and Cloud Storage in Node.js: @aws-sdk/client-s3 Library
Geolocation and Maps in C++: Overview of Libraries and Tools
PHP Task Scheduler: Complete Guide to Cron Jobs and Task Scheduler
Hyper for Rust: Complete Guide with Examples