ursus
09/11/2025, 2:53 AMAleksei Tirman [JB]
09/11/2025, 7:32 AMBearerAuthProvider.clearToken method to remove the currently stored tokens:
client.authProvider<BearerAuthProvider>()?.clearToken()ursus
09/11/2025, 10:35 AMAleksei Tirman [JB]
09/11/2025, 10:37 AMursus
09/11/2025, 10:41 AMAleksei Tirman [JB]
09/11/2025, 12:27 PMursus
09/11/2025, 2:46 PMursus
09/11/2025, 11:54 PMobject StatelessAuth {
class Config {
// Always fetch the token fresh; you control where it comes from (disk, keystore, IPC, etc.)
lateinit var tokenProvider: suspend () -> String
// Optional: called after a 401 to refresh credentials
var refresh: (suspend () -> Unit)? = null
var headerName: String = HttpHeaders.Authorization
var scheme: String = "Bearer"
var retryOnUnauthorized: Boolean = true
var maxRetries: Int = 1
}
val Plugin = createClientPlugin("StatelessAuth", ::Config) {
val tokenProvider = pluginConfig.tokenProvider
val refresh = pluginConfig.refresh
val headerName = pluginConfig.headerName
val scheme = pluginConfig.scheme
val retryOnUnauthorized = pluginConfig.retryOnUnauthorized
val maxRetries = pluginConfig.maxRetries
suspend fun applyAuth(builder: HttpRequestBuilder) {
val token = tokenProvider()
builder.headers.remove(headerName)
builder.headers.append(headerName, if (scheme.isEmpty()) token else "$scheme $token")
}
on(Send) { original ->
// 1) send with fresh token (no caching in the plugin)
applyAuth(original)
var call = proceed(original)
// 2) optional retry on 401 without keeping any token in memory
var attempts = 0
while (retryOnUnauthorized && call.response.status == HttpStatusCode.Unauthorized && attempts < maxRetries) {
attempts += 1
refresh?.invoke()
val retry = HttpRequestBuilder().takeFrom(call.request) // rebuild same request
applyAuth(retry)
call = proceed(retry)
}
call
}
}
}
How does something like this look to you?Aleksei Tirman [JB]
09/12/2025, 9:01 AMAuthProvider interface instead of writing another client plugin.