Skip to content

Authentication ​

CoApi provides a robust authentication system that integrates seamlessly with Spring WebFlux reactive programming model. The authentication framework supports token-based authentication with automatic caching and expiry management, ensuring secure and efficient communication with external APIs while maintaining type safety and reactive principles.

Overview ​

The authentication system in CoApi is designed to handle the common requirements of modern API authentication, particularly JWT (JSON Web Token) based authentication with Bearer tokens. The framework provides reusable components that can be easily configured and composed to meet various authentication scenarios, from simple static tokens to complex dynamic token providers with automatic refresh capabilities.

The authentication layer leverages Spring WebFlux's reactive programming model throughout, ensuring non-blocking operation and efficient resource utilization. The architecture follows a modular design with clear separation of concerns between token providers, header mappers, and request filters.

At-a-Glance ​

ComponentResponsibilityKey FeaturesSource
HeaderSetFilterGeneric header injection on requestsConfigurable header names, value providers, mappersHeaderSetFilter.kt
BearerTokenFilterAuthorization header injectionBearer token prefix, token provider integrationBearerTokenFilter.kt
ExpirableTokenToken with expiration informationJWT expiry detection, time-based validationExpirableTokenProvider.kt
ExpirableTokenProviderToken provider interfaceReactive token fetching, header value mappingExpirableTokenProvider.kt
CachedExpirableTokenProviderCached token providerReactive caching, automatic expiry invalidationCachedExpirableTokenProvider.kt

Class Hierarchy ​

The authentication framework follows a clean inheritance hierarchy with clear responsibilities:

mermaid
classDiagram
    class HeaderSetFilter {
        +String headerName
        +HeaderValueProvider headerValueProvider
        +HeaderValueMapper headerValueMapper
        +filter(ClientRequest, ExchangeFunction)
    }
    class ExchangeFilterFunction {
        <<interface>>
        +filter(ClientRequest, ExchangeFunction)
    }
    class HeaderValueProvider {
        <<fun interface>>
        +getHeaderValue(): Mono~String~
    }
    class HeaderValueMapper {
        <<fun interface>>
        +map(String): String
    }
    class BearerTokenFilter {
        -ExpirableTokenProvider tokenProvider
        +filter(ClientRequest, ExchangeFunction)
    }
    class BearerHeaderValueMapper {
        <<object>>
        +map(String): String
        +String.withBearerPrefix()
    }
    class ExpirableToken {
        +String token
        +Long expireAt
        +Boolean isExpired
        +String.jwtToExpirableToken()
    }
    class ExpirableTokenProvider {
        <<interface>>
        +getToken(): Mono~ExpirableToken~
        +getHeaderValue(): Mono~String~
    }
    class CachedExpirableTokenProvider {
        +ExpirableTokenProvider delegate
        +Mono~ExpirableToken~ tokenCache
        +getToken(): Mono~ExpirableToken~
    }
    HeaderSetFilter --> ExchangeFilterFunction : implements
    HeaderSetFilter --> HeaderValueProvider : uses
    HeaderSetFilter --> HeaderValueMapper : uses
    BearerTokenFilter --> HeaderSetFilter : extends
    BearerTokenFilter --> ExpirableTokenProvider : uses
    BearerHeaderValueMapper --> HeaderValueMapper : implements
    ExpirableTokenProvider --> HeaderValueProvider : extends
    CachedExpirableTokenProvider --> ExpirableTokenProvider : implements

Token Caching Flow ​

The reactive caching mechanism ensures efficient token management while maintaining thread safety and automatic invalidation:

mermaid
sequenceDiagram
    participant C as CachedExpirableTokenProvider
    participant D as Delegate Provider
    participant T as Mono
    participant R as Reactive Cache
    participant U as User Request
    participant E as Expiration Check
    autonumber
    U->>C: getToken
    C->>T: tokenCache
    alt Cache Empty
        T->>D: getToken
        D->>D: Fetch token
        D->>T: Return ExpirableToken
        T->>R: cacheInvalidateIf
        R->>C: Return cached Mono
        T->>U: Return cached token
    else Cache Valid
        T->>U: Return cached token
    else Cache Expired
        T->>E: Check expiration
        E->>R: cacheInvalidateIf
        R->>T: Force refresh
        T->>D: getToken
        D->>D: Fetch fresh token
        D->>T: Return ExpirableToken
        T->>R: Update cache
        T->>U: Return fresh token
    end
    note over C,C: Reactive caching

Request Authentication Flow ​

The complete authentication flow demonstrates how Bearer tokens are automatically added to HTTP requests:

mermaid
sequenceDiagram
    participant U as User Application
    participant R as WebClient
    participant F as BearerTokenFilter
    participant T as CachedExpirableTokenProvider
    participant C as Cache
    participant P as ExpirableToken
    autonumber
    U->>R: makeRequest
    R->>F: filter request
    alt Authorization Header Exists
        F->>R: Skip
        R->>U: Return response
    else No Authorization Header
        F->>T: getHeaderValue
        alt Token Cached
            T->>C: Get cached token
            C->>T: Return ExpirableToken
            T->>F: Return token
        else Token Expired
            T->>P: getToken
            P->>P: Check expired
            alt Expired
                P->>C: Force refresh
                C->>T: Invalidate
                T->>P: fetchNewToken
                P->>P: return new token
            else Valid
                P->>T: Return token
            end
            T->>F: Return token
        end
        F->>F: Bearer prefix
        F->>F: Authorization header
        F->>R: Continue
        R->>U: Return response
    end

JWT Expiry Check State Diagram ​

The token management system handles JWT expiry checks through a state machine approach:

mermaid
stateDiagram-v2
    [*] --> NotRequested
    NotRequested --> Fetching : getToken called
    Fetching --> ValidToken : Token received, not expired
    Fetching --> InvalidToken : Token received, expired
    Fetching --> Error : Token fetch failed
    ValidToken --> Cached : Token cached
    Cached --> ValidCheck : Check if still valid
    ValidCheck --> ValidToken : Token still valid
    ValidCheck --> Expired : Token expired
    Expired --> Fetching : Auto-refresh triggered
    InvalidToken --> Fetching : Force refresh
    Error --> Fetching : Retry after delay
    [*] --> ValidToken : Initial token fetch
    ValidToken --> [*] : Application shutdown

Core Components ​

HeaderSetFilter ​

The HeaderSetFilter is a generic reactive filter that can set any header on HTTP requests. It follows the Spring WebFlux ExchangeFilterFunction interface and provides a flexible way to inject headers into requests.

kotlin
open class HeaderSetFilter(
    private val headerName: String,
    private val headerValueProvider: HeaderValueProvider,
    private val headerValueMapper: HeaderValueMapper = HeaderValueMapper.IDENTITY
) : ExchangeFilterFunction {
    override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
        if (request.headers().containsHeader(headerName)) {
            return next.exchange(request)
        }
        return headerValueProvider.getHeaderValue()
            .map { headerValue ->
                ClientRequest.from(request)
                    .headers { headers ->
                        headers[headerName] = headerValueMapper.map(headerValue)
                    }
                    .build()
            }
            .flatMap { next.exchange(it) }
    }
}

The filter first checks if the header already exists in the request to avoid overwriting existing values. If the header is not present, it retrieves the header value from the provider, maps it using the configured mapper, and sets it on the request before proceeding to the next exchange function.

BearerTokenFilter ​

The BearerTokenFilter is a specialized version of HeaderSetFilter that specifically handles Bearer token authentication. It extends the base filter with pre-configured values for the Authorization header and the Bearer token prefix.

kotlin
class BearerTokenFilter(tokenProvider: ExpirableTokenProvider) :
    HeaderSetFilter(
        headerName = HttpHeaders.AUTHORIZATION,
        headerValueProvider = tokenProvider,
        headerValueMapper = BearerHeaderValueMapper
    )

The BearerHeaderValueMapper ensures that all token values are prefixed with "Bearer " according to the OAuth 2.0 Bearer Token specification.

ExpirableToken ​

The ExpirableToken data class wraps a token string with its expiration timestamp, providing convenient methods for checking if the token has expired.

kotlin
data class ExpirableToken(val token: String, val expireAt: Long) {
    val isExpired: Boolean
        get() = expiresWithin(Duration.ZERO)

    fun expiresWithin(margin: Duration): Boolean {
        return System.currentTimeMillis() + margin.toMillis() > expireAt
    }

    companion object {
        private val jwtParser = JWT()
        fun String.jwtToExpirableToken(): ExpirableToken {
            val decodedJWT = jwtParser.decodeJwt(this)
            val expiresAt = checkNotNull(decodedJWT.expiresAt) {
                "JWT token has no exp claim - required for expirable tokens."
            }
            return ExpirableToken(this, expiresAt.time)
        }
    }
}

The companion object provides a convenient extension function to convert JWT strings to ExpirableToken instances by decoding the JWT and extracting the expiration timestamp. The exp claim is required: since v2.2.0, a JWT without it fails with "JWT token has no exp claim - required for expirable tokens." (previously a bare Required value was null.). Note that JWT exp is a Unix timestamp in seconds, so the millisecond precision of the original Date is truncated.

CachedExpirableTokenProvider ​

The CachedExpirableTokenProvider implements reactive caching using Project Reactor's Mono.cacheInvalidateIf operator: thread-safe caching that fetches a new token once the cached one expires within refreshBeforeExpiry (default 60 seconds, the same as Spring Security's OAuth2 client clock skew).

kotlin
class CachedExpirableTokenProvider @JvmOverloads constructor(
    tokenProvider: ExpirableTokenProvider,
    private val refreshBeforeExpiry: Duration = DEFAULT_REFRESH_BEFORE_EXPIRY // 60s
) : ExpirableTokenProvider {
    private val tokenCache: Mono<ExpirableToken> = tokenProvider.getToken()
        .cacheInvalidateIf { it.expiresWithin(refreshBeforeExpiry) }

    override fun getToken(): Mono<ExpirableToken> {
        return tokenCache
    }
}

Refreshing early (since v3.1.0) keeps a token that is about to expire from being sent and rejected in flight because of request latency or clock skew. Before v3.1.0 the token was only refreshed after it had expired. Tokens whose whole lifetime is shorter than the margin are fetched on every use; pass a smaller refreshBeforeExpiry for such tokens.

Configuration Examples ​

Basic Bearer Token Authentication ​

kotlin
@Configuration
class AuthenticationConfig {
    
    @Bean
    fun tokenProvider(): ExpirableTokenProvider {
        return object : ExpirableTokenProvider {
            override fun getToken(): Mono<ExpirableToken> {
                return Mono.just(
                    ExpirableToken(
                        token = "your.jwt.token",
                        expireAt = System.currentTimeMillis() + 3600000 // 1 hour
                    )
                )
            }
        }
    }
    
    @Bean
    fun authenticationFilter(): BearerTokenFilter {
        return BearerTokenFilter(tokenProvider())
    }
}

Dynamic Token Provider with JWT Decoding ​

kotlin
@Configuration
class DynamicAuthenticationConfig {
    
    @Bean
    fun jwtTokenProvider(): ExpirableTokenProvider {
        return object : ExpirableTokenProvider {
            override fun getToken(): Mono<ExpirableToken> {
                return Mono.fromCallable {
                    // Fetch token from external source (e.g., OAuth service)
                    val jwtToken = fetchTokenFromAuthService()
                    jwtToken.jwtToExpirableToken()
                }
            }
            
            private fun fetchTokenFromAuthService(): String {
                // Implementation to fetch JWT from authentication service
                return "dynamic.jwt.token"
            }
        }
    }
    
    @Bean
    fun cachedTokenProvider(): ExpirableTokenProvider {
        return CachedExpirableTokenProvider(jwtTokenProvider())
    }
}

WebClient Integration ​

kotlin
@Configuration
class WebClientConfig {
    
    @Bean
    fun webClient(builder: WebClient.Builder): WebClient {
        return builder
            .filter(authenticationFilter())
            .build()
    }
    
    @Bean
    fun authenticationFilter(): ExchangeFilterFunction {
        return BearerTokenFilter(cachedTokenProvider())
    }
}

Cross-References ​

References ​

Source Files ​