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 BearerTokenFilter in spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/auth/BearerTokenFilter.kt to inject Authorization headers automatically.
  • Implement ExpirableTokenProvider to supply tokens with refresh logic, or reuse CachedExpirableTokenProvider for built-in caching.
  • Exposing BearerTokenFilter as a Spring bean enables automatic discovery by WebClientFactoryBean, requiring no manual client configuration.
  • For synchronous clients, implement ClientHttpRequestInterceptor using the same token provider pattern.
  • The BearerHeaderValueMapper automatically prefixes tokens with Bearer , 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →