Install
$ agentstack add skill-kora-projects-kora-skills-kora-http-server-auth ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Kora HTTP Server Auth
Authenticate and authorize Kora HTTP server endpoints. Kora has no @Secured-style annotation and no thread-local "current user". Authentication is implemented in one of two ways:
- OpenAPI-driven (preferred when you generate the server from a contract):
implement HttpServerPrincipalExtractor and bind it with @Tag(ApiSecurity..class). The generated controller invokes the matching extractor before your delegate runs.
- Manual (no OpenAPI contract): an
HttpServerInterceptorvalidates credentials and
short-circuits, and/or an HttpServerRequestMapper turns the request into a typed argument injected via @Mapping.
All Kora artifacts inherit the version from the kora-parent BOM (1.2.17 in .kora-agent/kora-examples). Never pin individual ru.tinkoff.kora:* versions.
Quick Start (OpenAPI security)
1. Dependencies
dependencies {
koraBom platform("ru.tinkoff.kora:kora-parent:1.2.17")
annotationProcessor "ru.tinkoff.kora:annotation-processors" // mandatory: generates the graph + controllers
implementation "ru.tinkoff.kora:http-server-undertow"
implementation "ru.tinkoff.kora:json-module"
implementation "ru.tinkoff.kora:config-hocon"
implementation "ru.tinkoff.kora:logging-logback"
}
The OpenAPI kora generator produces the ApiSecurity class (one nested marker per securityScheme) from the contract's components.securitySchemes. See [kora-openapi-generator-server](../kora-openapi-generator-server/SKILL.md) for the generator wiring.
2. Declare the security scheme in the contract
security:
- apiKeyAuth: [ ] # apply globally to every operation
components:
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: Authorization
Generation emits ApiSecurity.ApiKeyAuth (and BearerAuth, BasicAuth, OAuth for the corresponding scheme types).
3. Define a Principal
Principal is the framework marker interface ru.tinkoff.kora.common.Principal. Implement it on your own record.
import ru.tinkoff.kora.common.Principal;
public record DataApiPrincipal(String name) implements Principal {}
4. Bind the extractor with @Tag(ApiSecurity..class)
Declare the extractor as a default method on @KoraApp (or on a @Module interface). The lambda receives the HttpServerRequest and the raw credential value parsed from the scheme's header. Throw SecurityException to reject; the generated transport wraps the returned CompletionStage.
import java.util.concurrent.CompletableFuture;
import ru.tinkoff.kora.common.Principal;
import ru.tinkoff.kora.common.Tag;
import ru.tinkoff.kora.http.server.common.auth.HttpServerPrincipalExtractor;
@Tag(ApiSecurity.ApiKeyAuth.class)
default HttpServerPrincipalExtractor apiKeyHttpServerPrincipalExtractor(DataApiAuthConfig config) {
return (request, value) -> {
if (value == null || !config.value().equals(value)) {
throw new SecurityException("Invalid API key");
}
return CompletableFuture.completedFuture(new DataApiPrincipal("data-api-client"));
};
}
Externalize the expected secret via @ConfigSource:
import ru.tinkoff.kora.config.common.annotation.ConfigSource;
@ConfigSource("auth.apiKey")
public interface DataApiAuthConfig {
String value();
}
auth { apiKey { value = ${API_KEY} } }
5. Map SecurityException to 403
Kora does not translate SecurityException to an HTTP status automatically. Register an error-handling HttpServerInterceptor (tag with @Tag(HttpServerModule.class) to apply to every controller).
import ru.tinkoff.kora.common.Component;
import ru.tinkoff.kora.common.Context;
import ru.tinkoff.kora.common.Tag;
import ru.tinkoff.kora.http.common.body.HttpBody;
import ru.tinkoff.kora.http.server.common.HttpServerInterceptor;
import ru.tinkoff.kora.http.server.common.HttpServerModule;
import ru.tinkoff.kora.http.server.common.HttpServerRequest;
import ru.tinkoff.kora.http.server.common.HttpServerResponse;
import ru.tinkoff.kora.http.server.common.HttpServerResponseException;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
@Tag(HttpServerModule.class)
@Component
public final class AuthErrorInterceptor implements HttpServerInterceptor {
@Override
public CompletionStage intercept(Context context, HttpServerRequest request, InterceptChain chain)
throws Exception {
return chain.process(context, request).exceptionally(throwable -> {
var cause = (throwable instanceof CompletionException && throwable.getCause() != null)
? throwable.getCause() : throwable;
if (cause instanceof HttpServerResponseException ex) {
return ex;
}
if (cause instanceof SecurityException) {
var msg = cause.getMessage() != null ? cause.getMessage() : "Access denied";
return HttpServerResponse.of(403, HttpBody.plaintext(msg));
}
return HttpServerResponse.of(500, HttpBody.plaintext("Internal error"));
});
}
}
What's in this skill
| File | Purpose | |------|---------| | [references/openapi-security-reference.md](references/openapi-security-reference.md) | HttpServerPrincipalExtractor + ApiSecurity markers for Bearer/Basic/API-Key/OAuth, PrincipalWithScopes, scope checks. | | [references/manual-auth-reference.md](references/manual-auth-reference.md) | Non-OpenAPI auth: HttpServerInterceptor, HttpServerRequestMapper + @Mapping, header parsing, 401/403 responses. | | assets/ApiKeyExtractor.java.template / .kt.template | API-key HttpServerPrincipalExtractor skeleton. | | assets/BasicAuthExtractor.java.template / .kt.template | HTTP Basic HttpServerPrincipalExtractor skeleton. | | [assets/README.md](assets/README.md) | How to copy and wire the templates. | | evals/evals.json | Behavioral evals for this skill. |
Auth scheme reference
| Scheme | OpenAPI type/scheme | Header (typical) | @Tag marker | value passed to extractor | |--------|-------------------------|------------------|---------------|------------------------------| | Bearer / JWT | http / bearer | Authorization: Bearer | ApiSecurity.BearerAuth.class | the credential after the scheme prefix | | API Key | apiKey (in: header) | the header named in the scheme | ApiSecurity.ApiKeyAuth.class | the header value | | Basic | http / basic | Authorization: Basic | ApiSecurity.BasicAuth.class | the credential after the scheme prefix | | OAuth (scopes) | oauth2 | Authorization: Bearer | ApiSecurity.OAuth.class | the credential after the scheme prefix |
For OAuth, return a PrincipalWithScopes so the generated transport can enforce the operation's required scopes:
import ru.tinkoff.kora.http.common.auth.PrincipalWithScopes;
public record UserPrincipal(String name) implements PrincipalWithScopes {
@Override public java.util.Collection scopes() { return java.util.List.of("read", "write"); }
}
@Tag(ApiSecurity.OAuth.class)
default HttpServerPrincipalExtractor oauthHttpServerPrincipalExtractor() {
return (request, value) -> CompletableFuture.completedFuture(new UserPrincipal("name"));
}
Key principles
Principalisru.tinkoff.kora.common.Principal— a framework marker interface.
Your principal record must implements Principal. Do not define a local Principal.
- One extractor per scheme, bound by
@Tag(ApiSecurity..class)— the
generated controller picks the extractor matching the operation's security requirement.
- Reject by throwing
SecurityException(or completing the future exceptionally).
There is no Principal.current() and no request-attribute bag — the principal flows through the generated transport, not a thread-local.
- Map auth failures to HTTP codes yourself via an
HttpServerInterceptor:
401 = authentication failed (bad/missing credentials), 403 = authorization failed (missing role/scope).
PrincipalWithScopesfor OAuth scope enforcement — the only built-in
authorization hook; richer role checks live in your delegate or interceptor.
- Externalize secrets with
@ConfigSourceand${ENV}substitution — never inline
keys/passwords.
Manual auth (no OpenAPI)
When there is no generated ApiSecurity, validate credentials in an HttpServerInterceptor and/or derive a typed principal with an HttpServerRequestMapper injected via @Mapping. Full patterns: [references/manual-auth-reference.md](references/manual-auth-reference.md).
import ru.tinkoff.kora.common.Mapping;
import ru.tinkoff.kora.http.common.HttpMethod;
import ru.tinkoff.kora.http.server.common.HttpServerRequest;
import ru.tinkoff.kora.http.server.common.HttpServerRequestMapper;
public record CallerContext(String userId) {}
public static final class CallerMapper implements HttpServerRequestMapper {
@Override public CallerContext apply(HttpServerRequest request) {
var userId = request.headers().getFirst("x-user-id");
if (userId == null) {
throw new SecurityException("Missing x-user-id");
}
return new CallerContext(userId);
}
}
@HttpRoute(method = HttpMethod.GET, path = "/me")
public String me(@Mapping(CallerMapper.class) CallerContext caller) {
return caller.userId();
}
Common pitfalls
| Symptom | Cause | Fix | |---------|-------|-----| | cannot find symbol: method current() on Principal | Principal.current() does not exist in Kora | Receive the principal through the generated delegate, or derive it with HttpServerRequestMapper + @Mapping. | | cannot find symbol: getAttribute on HttpServerRequest | Kora has no request-attribute bag | Read headers via request.headers().getFirst(...); pass data as a typed @Mapping argument. | | Extractor never runs | Missing/wrong @Tag(ApiSecurity..class), or the operation has no security requirement | Add security: in the contract and tag the extractor with the matching marker. | | SecurityException surfaces as HTTP 500 | No error interceptor mapping it | Add an HttpServerInterceptor tagged @Tag(HttpServerModule.class) that maps SecurityException to 403. | | Required dependency not found: ...PrincipalExtractor | Extractor not declared as a graph component | Add the default extractor method to @KoraApp or a @Module the app extends. | | OAuth scopes not enforced | Returning a plain Principal | Return a PrincipalWithScopes so the transport can check the operation's scopes. |
Related skills
- [kora-http-server](../kora-http-server/SKILL.md) — controllers, routes, interceptors, error handling
- [kora-openapi-generator-server](../kora-openapi-generator-server/SKILL.md) — generates
ApiSecuritymarkers and delegates - [kora-http-client-auth](../kora-http-client-auth/SKILL.md) — outbound credential interceptors
- [kora-config-hocon](../kora-config-hocon/SKILL.md) —
@ConfigSourcefor secrets - [kora-json](../kora-json/SKILL.md) — JWT claim DTO serialization
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kora-projects
- Source: kora-projects/kora-skills
- License: Apache-2.0
- Homepage: http://kora-projects.github.io/kora-docs
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.