Kora HTTP Client Auth
Kora sub-skill — obey the kora-v1 meta rules on every task: R0 ensure
.kora-agent/docs+examples are cloned · R1 read this sub-skill before writing code · R2 Kora APIs only — no Spring/Micronaut/Quarkus, no invented annotations or config keys · R3 journal any incorrect Kora usage. Add comments/Javadoc only if asked.
Authenticate outgoing requests from a declarative @HttpClient. Kora ships
ready-made HttpClientInterceptor implementations for Basic, API-key and Bearer
schemes; attach any interceptor with @InterceptWith. For dynamic tokens
(OAuth2 client credentials, refreshable JWT) you implement HttpClientTokenProvider
or write your own HttpClientInterceptor.
Level: Intermediate (requires kora-http-client and kora-di-compile).
Key facts (verify against the source of truth):
- There is no
ru.tinkoff.kora:http-client-authartifact. Auth lives inhttp-client-commonand is wired through interceptors. - Built-in interceptors:
BasicAuthHttpClientInterceptor,ApiKeyHttpClientInterceptor,BearerAuthHttpClientInterceptor. HttpClientTokenProvideris the extension point for Bearer tokens; the Bearer interceptor calls it on every request.- Interceptors are attached with
@InterceptWith(...), not aninterceptors = {...}attribute on@HttpClient. - The target URL is set in config (
httpClient.<client>.url), not abaseUrlannotation attribute.
Quick Start
build.gradle — note the mandatory annotation processor:
dependencies {
koraBom platform("ru.tinkoff.kora:kora-parent:1.2.19")
annotationProcessor "ru.tinkoff.kora:annotation-processors"
implementation "ru.tinkoff.kora:config-hocon"
implementation "ru.tinkoff.kora:http-client-common"
implementation "ru.tinkoff.kora:http-client-ok" // OkHttp transport
implementation "ru.tinkoff.kora:json-module"
implementation "ru.tinkoff.kora:logging-logback"
}
@KoraApp
public interface Application extends
HoconConfigModule,
JsonModule,
LogbackModule,
OkHttpClientModule,
BearerAuthModule { }
Provide a token, register the built-in Bearer interceptor in a @Module, and
attach it to the client:
@Component
public final class StaticTokenProvider implements HttpClientTokenProvider {
private final ApiTokenConfig config;
public StaticTokenProvider(ApiTokenConfig config) {
this.config = config;
}
@Override
public CompletionStage<String> getToken(HttpClientRequest request) {
return CompletableFuture.completedFuture(config.token());
}
}
@Module
public interface BearerAuthModule {
default BearerAuthHttpClientInterceptor bearerAuther(HttpClientTokenProvider tokenProvider) {
return new BearerAuthHttpClientInterceptor(tokenProvider);
}
}
@HttpClient(configPath = "httpClient.secureApi")
public interface SecureApiClient {
@InterceptWith(BearerAuthHttpClientInterceptor.class)
@HttpRoute(method = HttpMethod.GET, path = "/protected")
@Json
ProtectedResponse getProtected();
}
httpClient.secureApi {
url = "https://api.example.com"
}
api.token = ${API_TOKEN} // externalize the secret
@ConfigSource for the token:
@ConfigSource("api")
public interface ApiTokenConfig {
String token();
}
When to use vs NOT
Use this skill when you:
- add
Authorization: Basic/Beareror an API-key header to outbound requests; - implement
HttpClientTokenProviderfor OAuth2 client-credentials or JWT refresh; - write a custom
HttpClientInterceptorfor a non-standard scheme; - get
401 Unauthorizedfrom an external API and need to fix the credentials flow.
Do not use this skill when you:
- authenticate requests on the server — see
kora-http-server-auth; - need OAuth2 with a user context (authorization-code flow) — Kora ships only the building blocks; the client-credentials pattern here is service-to-service.
Reference files
| Topic | Reference |
|---|---|
Built-in interceptors (Basic / API-key / Bearer), @InterceptWith placement, config |
references/http-client-auth-reference.md |
Custom HttpClientInterceptor (header & query-param API key) |
references/apikey-interceptor-reference.md |
HttpClientTokenProvider with caching/refresh |
references/jwt-token-provider-reference.md |
| Thread-safe token cache | references/token-cache-reference.md |
| OAuth2 client-credentials end-to-end | references/oauth2-client-credentials-reference.md |
Templates and a generator script live in assets/.
Core patterns
1. Built-in Basic / API-key / Bearer
Register the interceptor as a component in a @Module, then attach it.
@Module
public interface ApiKeyAuthModule {
@ConfigSource("openapiAuth.apiKeyAuth")
interface ApiKeyAuthConfig {
String apiKey();
}
default ApiKeyHttpClientInterceptor apiKeyAuther(ApiKeyAuthConfig config) {
return new ApiKeyHttpClientInterceptor(ApiKeyLocation.HEADER, "X-API-KEY", config.apiKey());
}
}
@HttpClient(configPath = "httpClient.someClient")
public interface SomeClient {
@InterceptWith(ApiKeyHttpClientInterceptor.class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
void hello();
}
@InterceptWith may sit on the interface (applies to every method) or on a single
method. ApiKeyLocation is HEADER, QUERY, or COOKIE.
BasicAuthHttpClientInterceptor(username, password) Base64-encodes the credentials
for you. BearerAuthHttpClientInterceptor takes an HttpClientTokenProvider (or a
static token string) and adds the Authorization header per request.
2. Custom interceptor for a non-standard scheme
Implement HttpClientInterceptor directly when the built-ins do not fit. The
signature returns a CompletionStage<HttpClientResponse> and you must call
chain.process(...):
@Component
public final class CustomHeaderInterceptor implements HttpClientInterceptor {
private final ApiKeyAuthConfig config;
public CustomHeaderInterceptor(ApiKeyAuthConfig config) {
this.config = config;
}
@Override
public CompletionStage<HttpClientResponse> processRequest(
Context ctx, InterceptChain chain, HttpClientRequest request) throws Exception {
var authorized = request.toBuilder()
.header("X-Custom-Token", config.value())
.build();
return chain.process(ctx, authorized);
}
}
Use request.toBuilder().header(name, value) for headers and
.queryParam(name, value) for query parameters; never mutate the original request.
3. Dynamic token via HttpClientTokenProvider
For tokens that must be fetched and refreshed (OAuth2 client credentials, JWT),
implement HttpClientTokenProvider and return a CompletionStage<String> so the
fetch stays non-blocking. Cache the token and refresh ahead of expiry. See
references/jwt-token-provider-reference.md
and references/oauth2-client-credentials-reference.md.
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
Required dependency not found: ...http-client-auth |
The artifact does not exist | Depend on http-client-common + a transport (http-client-ok); use interceptors |
| Interceptor never runs | Used a non-existent interceptors = {...} attribute |
Attach with @InterceptWith(YourInterceptor.class) |
cannot find symbol: method baseUrl() |
@HttpClient has no baseUrl attribute |
Set httpClient.<client>.url in config (or configPath) |
@Value/@ConfigValue not resolved |
Those annotations do not exist in Kora | Bind a @ConfigSource interface and inject it via the constructor |
401 after refresh |
Token expired mid-flight | Refresh ahead of expiry with a margin (e.g. 60s); see token cache reference |
| Duplicate token fetches under load | Concurrent refresh | Double-check + lock (or volatile fields); see token cache reference |
| Secret committed to VCS | Hard-coded credentials | Externalize with ${VAR} substitution in config |
Testing
Replace the real provider with a @TestComponent so no network call is made:
@TestComponent
public final class TestTokenProvider implements HttpClientTokenProvider {
@Override
public CompletionStage<String> getToken(HttpClientRequest request) {
return CompletableFuture.completedFuture("test-token-12345");
}
}
@KoraAppTest(Application.class)
class SecureApiClientTest {
@Test
void addsAuthorization(@TestComponent SecureApiClient client) {
// Drive the client against a stub server (e.g. Testcontainers/WireMock)
// and assert the upstream received the Authorization header.
assertThat(client.getProtected()).isNotNull();
}
}
See kora-testing-junit-java and kora-testing-blackbox for the full setup.
Related skills
kora-http-client— declarative HTTP clients,@HttpRoute, interceptorskora-http-server-auth— server-side Basic/Bearer/API-keykora-config-hocon—@ConfigSource, env substitutionkora-aop-logging—@Logfor client callskora-telemetry-tracing— distributed tracing for outbound calls
Source of truth
- Doc:
.kora-agent/kora-docs/mkdocs/docs/en/documentation/http-client.md(Authorization section) - Guide:
.kora-agent/kora-docs/mkdocs/docs/en/guides/http-client-advanced.md - Example:
.kora-agent/kora-examples/guides/java/kora-java-guide-http-client-advanced-app - Example:
.kora-agent/kora-examples/examples/java/kora-java-http-client