Ktor client authentication and authorization plugin for JavaScript platforms supporting Basic, Digest, and Bearer token authentication with automatic token refresh
—
Bearer token authentication provider with support for automatic token refresh, access/refresh token pairs, circuit breaker functionality, and configurable token loading and refresh mechanisms for OAuth flows and API token authentication.
Install the Bearer authentication provider with configuration for token management.
/**
* Installs the client's BearerAuthProvider
* @param block Configuration block for Bearer authentication
*/
fun AuthConfig.bearer(block: BearerAuthConfig.() -> Unit)Usage Example:
import io.ktor.client.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
val client = HttpClient {
Auth {
bearer {
loadTokens {
BearerTokens("access_token", "refresh_token")
}
refreshTokens { params ->
// Refresh token logic
refreshAccessToken(params.oldTokens?.refreshToken)
}
}
}
}Configuration class for BearerAuthProvider with token loading, refreshing, and sending behavior control.
/**
* Configuration for BearerAuthProvider
*/
class BearerAuthConfig {
/**
* Optional realm specification for Bearer authentication
*/
var realm: String?
/**
* Configures a callback that refreshes a token when the 401 status code is received
* @param block Suspend function that handles token refresh with refresh parameters
*/
fun refreshTokens(block: suspend RefreshTokensParams.() -> BearerTokens?)
/**
* Configures a callback that loads a cached token from a local storage
* Note: Using the same client instance here to make a request will result in a deadlock
* @param block Suspend function that returns cached tokens or null
*/
fun loadTokens(block: suspend () -> BearerTokens?)
/**
* Configures when to send credentials without waiting for HttpStatusCode.Unauthorized
* @param block Function that determines based on request whether to send credentials proactively
*/
fun sendWithoutRequest(block: (HttpRequestBuilder) -> Boolean)
}Usage Examples:
bearer {
// Load tokens from storage
loadTokens {
loadTokensFromSecureStorage()
}
// Refresh token logic
refreshTokens { params ->
val oldRefreshToken = params.oldTokens?.refreshToken
if (oldRefreshToken != null) {
// Use a different client instance to avoid deadlock
val refreshClient = HttpClient()
// Mark as refresh request to prevent infinite loops
val response = refreshClient.post("/oauth/refresh") {
markAsRefreshTokenRequest()
setBody(RefreshTokenRequest(oldRefreshToken))
}
if (response.status.isSuccess()) {
val tokenResponse = response.body<TokenResponse>()
BearerTokens(tokenResponse.accessToken, tokenResponse.refreshToken)
} else null
} else null
}
// Send tokens proactively for API calls
sendWithoutRequest { request ->
request.url.host == "api.example.com"
}
realm = "api"
}Container class for Bearer authentication tokens supporting access/refresh token pairs.
/**
* Container for bearer tokens
* @param accessToken The access token used for authentication
* @param refreshToken Optional refresh token used to obtain new access tokens
*/
class BearerTokens(
val accessToken: String,
val refreshToken: String?
)Parameters passed to the token refresh callback providing context for the refresh operation.
/**
* Parameters passed to refreshTokens lambda
* @param client HttpClient instance for making refresh requests
* @param response HttpResponse that triggered the token refresh
* @param oldTokens Previously cached tokens, may be null
*/
class RefreshTokensParams(
val client: HttpClient,
val response: HttpResponse,
val oldTokens: BearerTokens?
) {
/**
* Marks that this request is for refreshing auth tokens, resulting in special handling
* This prevents the request from triggering additional authentication flows
*/
fun HttpRequestBuilder.markAsRefreshTokenRequest()
}Usage Example:
refreshTokens { params ->
val refreshToken = params.oldTokens?.refreshToken
if (refreshToken != null) {
try {
// Create separate client for refresh to avoid deadlock
val refreshClient = HttpClient()
val response = refreshClient.post("/auth/refresh") {
// Important: mark as refresh request
markAsRefreshTokenRequest()
header("Authorization", "Bearer $refreshToken")
contentType(ContentType.Application.Json)
setBody(mapOf("refresh_token" to refreshToken))
}
if (response.status.isSuccess()) {
val newTokens = response.body<TokenResponse>()
BearerTokens(newTokens.access_token, newTokens.refresh_token)
} else {
null // Refresh failed
}
} catch (e: Exception) {
null // Handle refresh errors
}
} else {
null // No refresh token available
}
}Authentication provider implementation for the Bearer HTTP authentication scheme supporting OAuth flows and API token authentication.
/**
* Authentication provider for the Bearer HTTP authentication scheme
* Bearer authentication involves security tokens called bearer tokens
* These tokens can be used as part of OAuth flow to authorize users of your application
* by using external providers, such as Google, Facebook, Twitter, and so on
*/
class BearerAuthProvider(
private val refreshTokens: suspend RefreshTokensParams.() -> BearerTokens?,
loadTokens: suspend () -> BearerTokens?,
private val sendWithoutRequestCallback: (HttpRequestBuilder) -> Boolean = { true },
private val realm: String?
) : AuthProvider {
// Note: clearToken() method is available but marked as @InternalAPI
override fun sendWithoutRequest(request: HttpRequestBuilder): Boolean
override fun isApplicable(auth: HttpAuthHeader): Boolean
override suspend fun addRequestHeaders(request: HttpRequestBuilder, authHeader: HttpAuthHeader?)
override suspend fun refreshToken(response: HttpResponse): Boolean
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
// Simple bearer token setup
val client = HttpClient {
Auth {
bearer {
loadTokens {
BearerTokens("my-access-token", null)
}
}
}
}
// OAuth flow with refresh tokens
val oauthClient = HttpClient {
Auth {
bearer {
loadTokens {
// Load from secure storage
val tokens = secureStorage.getTokens()
if (tokens != null) {
BearerTokens(tokens.accessToken, tokens.refreshToken)
} else null
}
refreshTokens { params ->
val refreshToken = params.oldTokens?.refreshToken
if (refreshToken != null) {
val refreshClient = HttpClient()
val response = refreshClient.post("https://oauth.provider.com/token") {
markAsRefreshTokenRequest()
parameter("grant_type", "refresh_token")
parameter("refresh_token", refreshToken)
parameter("client_id", "my-client-id")
}
if (response.status.isSuccess()) {
val tokenData = response.body<OAuthTokenResponse>()
// Cache new tokens
secureStorage.saveTokens(tokenData)
BearerTokens(tokenData.access_token, tokenData.refresh_token)
} else null
} else null
}
sendWithoutRequest { request ->
// Always send for API requests
request.url.host.contains("api.")
}
}
}
}
// Note: clearToken() is available as internal API for clearing cached tokensloadTokens() is called to retrieve cached tokenssendWithoutRequest returns true, Bearer header is added immediatelyisApplicable() for Bearer schemerefreshTokens() is called with refresh parametersmarkAsRefreshTokenRequest() skip authenticationInstall with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-auth-js