How to Add Bearer Token Authentication to CoApi Clients
Implement ExpirableTokenProvider to supply tokens and register BearerTokenFilter as a Spring bean to automatically inject Authorization: Bearer <token> headers into every outbound request.
CoApi, the Spring-based open-source API client framework from the ahoo-wang/coapi repository, provides a lightweight, pluggable authentication mechanism built around reactive filters and token providers. Adding Bearer token authentication to your CoApi clients requires implementing a token supply interface and exposing a filter bean that the framework automatically wires into the WebClient request pipeline.
Understanding CoApi's Authentication Architecture
CoApi's authentication system centers on two core components in the me.ahoo.coapi.spring.client.reactive.auth package: the ExpirableTokenProvider interface and the BearerTokenFilter class.
The ExpirableTokenProvider Interface
The ExpirableTokenProvider interface defines the contract for supplying authentication tokens that may expire and require refresh. Located in spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/auth/ExpirableTokenProvider.kt, this interface extends HeaderValueProvider and provides the raw token value that filters inject into HTTP headers.
The BearerTokenFilter Filter
BearerTokenFilter, defined in spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/auth/BearerTokenFilter.kt, extends HeaderSetFilter to automatically set the Authorization header on every request. The filter uses BearerHeaderValueMapper (an inner object within the same file) to prefix the raw token with Bearer before injection. When WebClientFactoryBean constructs the reactive client, it automatically detects and applies any HeaderSetFilter beans registered in the Spring context.
Implementing Bearer Token Authentication in CoApi
Follow these steps to add Bearer token authentication to your CoApi reactive client.
Step 1: Create a Token Provider
Implement ExpirableTokenProvider to handle token retrieval, caching, and refresh logic. For simple use cases, extend CachedExpirableTokenProvider from spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/auth/CachedExpirableTokenProvider.kt to reuse caching semantics.
import me.ahoo.coapi.spring.client.reactive.auth.ExpirableTokenProvider
import java.time.Instant
class MyBearerTokenProvider : ExpirableTokenProvider {
private var cachedToken: String? = null
private var expiry: Instant = Instant.MIN
override fun getHeaderValue(): String {
if (Instant.now().isAfter(expiry)) {
cachedToken = fetchNewToken()
expiry = Instant.now().plusSeconds(3600)
}
return cachedToken ?: ""
}
private fun fetchNewToken(): String {
// Replace with your OAuth2 server call or secret retrieval
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
Step 2: Register the BearerTokenFilter
Expose your token provider and the BearerTokenFilter as Spring beans. The filter constructor accepts an ExpirableTokenProvider and automatically coordinates with WebClientFactoryBean to insert the header.
import me.ahoo.coapi.spring.client.reactive.auth.BearerTokenFilter
import me.ahoo.coapi.spring.client.reactive.auth.ExpirableTokenProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class CoApiAuthConfig {
@Bean
fun bearerTokenProvider(): ExpirableTokenProvider = MyBearerTokenProvider()
@Bean
fun bearerTokenFilter(bearerTokenProvider: ExpirableTokenProvider): BearerTokenFilter {
return BearerTokenFilter(bearerTokenProvider)
}
}
With these beans registered, WebClientFactoryBean in spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/WebClientFactoryBean.kt automatically adds the filter to the client builder, ensuring every request includes the Authorization header.
Step 3: Consume the Authenticated Client
Inject WebClientFactoryBean into your service to create fully configured WebClient instances. All outgoing requests automatically carry the Bearer token.
import me.ahoo.coapi.spring.client.reactive.WebClientFactoryBean
import org.springframework.stereotype.Service
@Service
class GitHubService(
private val webClientFactoryBean: WebClientFactoryBean
) {
suspend fun listRepos(user: String): String {
val client = webClientFactoryBean.create()
return client.get()
.uri("https://api.github.com/users/$user/repos")
.retrieve()
.bodyToMono(String::class.java)
.awaitSingle()
}
}
The resulting HTTP requests include the header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Synchronous Client Support
For synchronous clients using RestClientFactoryBean from spring/src/main/kotlin/me/ahoo/coapi/spring/client/sync/RestClientFactoryBean.kt, use a ClientHttpRequestInterceptor instead of a reactive filter. The pattern remains identical: implement the interceptor to read from the same ExpirableTokenProvider and manually set the Authorization header.
import me.ahoo.coapi.spring.client.reactive.auth.ExpirableTokenProvider
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpRequest
import org.springframework.http.client.ClientHttpRequestExecution
import org.springframework.http.client.ClientHttpRequestInterceptor
import org.springframework.http.client.ClientHttpResponse
class BearerAuthInterceptor(
private val tokenProvider: ExpirableTokenProvider
) : ClientHttpRequestInterceptor {
override fun intercept(
request: HttpRequest,
body: ByteArray,
execution: ClientHttpRequestExecution
): ClientHttpResponse {
request.headers.set(
HttpHeaders.AUTHORIZATION,
"Bearer ${tokenProvider.getHeaderValue()}"
)
return execution.execute(request, body)
}
}
Register this interceptor in your Spring configuration to apply Bearer authentication to synchronous CoApi clients.
Summary
- CoApi uses
BearerTokenFilterinspring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/auth/BearerTokenFilter.ktto injectAuthorizationheaders automatically. - Implement
ExpirableTokenProviderto supply tokens with refresh logic, or reuseCachedExpirableTokenProviderfor built-in caching. - Exposing
BearerTokenFilteras a Spring bean enables automatic discovery byWebClientFactoryBean, requiring no manual client configuration. - For synchronous clients, implement
ClientHttpRequestInterceptorusing the same token provider pattern. - The
BearerHeaderValueMapperautomatically prefixes tokens withBearer, ensuring RFC 6750 compliance.
Frequently Asked Questions
How does BearerTokenFilter automatically add the Authorization header?
BearerTokenFilter extends HeaderSetFilter, which implements Spring's ExchangeFilterFunction. When declared as a bean, WebClientFactoryBean automatically collects all HeaderSetFilter instances and adds them to the WebClient builder. The filter invokes your ExpirableTokenProvider for each request, maps the value through BearerHeaderValueMapper to add the Bearer prefix, and sets the Authorization header before the request executes.
Can I use synchronous RestTemplate-based clients with CoApi Bearer authentication?
Yes. While BearerTokenFilter is designed for reactive WebClient instances, synchronous clients built via RestClientFactoryBean support authentication through ClientHttpRequestInterceptor. Implement an interceptor that references your ExpirableTokenProvider, manually construct the Bearer prefixed header value, and add it to the request headers via HttpHeaders.AUTHORIZATION. Register the interceptor as a bean to apply it to the sync client.
How do I handle token expiration and caching in CoApi?
Implement ExpirableTokenProvider to track expiration timestamps and refresh logic, or extend CachedExpirableTokenProvider which provides a template for caching tokens until expiry. Your implementation of getHeaderValue() should check the current time against the expiry instant, fetch a new token from your OAuth2 server or secret manager when expired, and return the valid token string. The filter calls this method on every request, ensuring tokens are refreshed transparently.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →