Repository: ktorio/ktor
File: ktor-client/ktor-client-plugins/ktor-client-auth/common/src/io/ktor/client/plugins/auth/Auth.kt (Kotlin)

Code before:
import io.ktor.util.*
import io.ktor.util.collections.*
import io.ktor.util.logging.*
import io.ktor.utils.io.*
import kotlinx.atomicfu.*

internal val LOGGER = KtorSimpleLogger("io.ktor.client.plugins.auth.Auth")

private class AtomicCounter {
    val atomic = atomic(0)
}

/**
 * Configuration used by [Auth] plugin.
 */
@KtorDsl
public class AuthConfig {
    /**
     * [AuthProvider] list to use.
     */
    public val providers: MutableList<AuthProvider> = mutableListOf()

    /**
     * A lambda function to control whether a response is unauthorized and should trigger a refresh / re-auth.
     */
    public var isUnauthorized: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized }
}

/**
 * Shows that request should skip auth and refresh token procedure.
 */
public val AuthCircuitBreaker: AttributeKey<Unit> = AttributeKey("auth-request")

/**
 * A client's plugin that handles authentication and authorization.
 * Typical usage scenarios include logging in users and gaining access to specific resources.
 *
 * You can learn more from [Authentication and authorization](https://ktor.io/docs/auth.html).
 *
 * @see [AuthConfig] for configuration options.
 */
public val Auth: ClientPlugin<AuthConfig> = createClientPlugin("Auth", ::AuthConfig) {
    val providers = pluginConfig.providers.toList()

    client.attributes.put(AuthProvidersKey, providers)

    val tokenVersions = ConcurrentMap<AuthProvider, AtomicCounter>()
    val tokenVersionsAttributeKey =
        AttributeKey<MutableMap<AuthProvider, Int>>("ProviderVersionAttributeKey")

    @OptIn(InternalAPI::class)

Review comment (security):
Let's refine the API a bit to make it more consistent to the existing ones.

```suggestion
    internal var isUnauthorized: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized }

    /** ... */
    public fun isUnauthorized(block: suspend (HttpResponse) -> Boolean) {
        isUnauthorized = block
    }
```

Though, I'm not sure about naming. Probably, `isUnauthorizedResponse` would be better. @marychatte, what do you think?

Code after:
import io.ktor.util.collections.*
import io.ktor.util.logging.*
import io.ktor.utils.io.*
import kotlinx.atomicfu.*

internal val LOGGER = KtorSimpleLogger("io.ktor.client.plugins.auth.Auth")

private class AtomicCounter {
    val atomic = atomic(0)
}

/**
 * Configuration used by [Auth] plugin.
 */
@KtorDsl
public class AuthConfig {
    /**
     * [AuthProvider] list to use.
     */
    public val providers: MutableList<AuthProvider> = mutableListOf()

    /**
     * The currently set function to control whether a response is unauthorized and should trigger a refresh / re-auth.
     *
     * By default checks against HTTP status 401.
     *
     * You can set this value via [reAuthorizeOnResponse].
     */
    @InternalAPI
    public var isUnauthorizedResponse: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized }
        private set

    /**
     * Sets a custom function to control whether a response is unauthorized and should trigger a refresh / re-auth.
     *
     * Use this to change the value of [isUnauthorizedResponse].
     */
    public fun reAuthorizeOnResponse(block: suspend (HttpResponse) -> Boolean) {
        @OptIn(InternalAPI::class)
        isUnauthorizedResponse = block
    }
}

/**
 * Shows that request should skip auth and refresh token procedure.
 */
public val AuthCircuitBreaker: AttributeKey<Unit> = AttributeKey("auth-request")

/**
 * A client's plugin that handles authentication and authorization.
 * Typical usage scenarios include logging in users and gaining access to specific resources.