agentsclimarketplace

Kora http server auth

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-http-server-auth

HTTP server authentication and authorization in Kora. Covers HttpServerPrincipalExtractor<T> wired to OpenAPI-generated ApiSecurity markers (BearerAuth/BasicAuth/ApiKeyAuth/OAuth) via @Tag, the Principal / PrincipalWithScopes marker interfaces, SecurityException-to-403 mapping through an HttpServerInterceptor, and the manual HttpServerInterceptor + HttpServerRequestMapper path for non-OpenAPI auth. Use when securing @HttpController/@HttpRoute endpoints, validating Bearer/JWT/API-key/Basic credentials, integrating an OpenAPI security scheme, or returning 401/403 from a Kora HTTP server.From its SKILL.md

Install
npx -y skills add kora-projects/kora-skills --skill kora-http-server-auth

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • reads credentialsReads from 2 credential sources: `auth.apiKey` and 1 more.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

11.8 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it

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<P> and bind it with @Tag(ApiSecurity.<Scheme>.class). The generated controller invokes the matching extractor before your delegate runs.
  • Manual (no OpenAPI contract): an HttpServerInterceptor validates credentials and short-circuits, and/or an HttpServerRequestMapper<P> 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 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.<Scheme>.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<Principal> 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<HttpServerResponse> 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

FilePurpose
references/openapi-security-reference.mdHttpServerPrincipalExtractor + ApiSecurity markers for Bearer/Basic/API-Key/OAuth, PrincipalWithScopes, scope checks.
references/manual-auth-reference.mdNon-OpenAPI auth: HttpServerInterceptor, HttpServerRequestMapper + @Mapping, header parsing, 401/403 responses.
assets/ApiKeyExtractor.java.template / .kt.templateAPI-key HttpServerPrincipalExtractor skeleton.
assets/BasicAuthExtractor.java.template / .kt.templateHTTP Basic HttpServerPrincipalExtractor skeleton.
assets/README.mdHow to copy and wire the templates.
evals/evals.jsonBehavioral evals for this skill.

Auth scheme reference

SchemeOpenAPI type/schemeHeader (typical)@Tag markervalue passed to extractor
Bearer / JWThttp / bearerAuthorization: Bearer <token>ApiSecurity.BearerAuth.classthe credential after the scheme prefix
API KeyapiKey (in: header)the header named in the schemeApiSecurity.ApiKeyAuth.classthe header value
Basichttp / basicAuthorization: Basic <base64>ApiSecurity.BasicAuth.classthe credential after the scheme prefix
OAuth (scopes)oauth2Authorization: Bearer <token>ApiSecurity.OAuth.classthe 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<String> scopes() { return java.util.List.of("read", "write"); }
}

@Tag(ApiSecurity.OAuth.class)
default HttpServerPrincipalExtractor<PrincipalWithScopes> oauthHttpServerPrincipalExtractor() {
    return (request, value) -> CompletableFuture.completedFuture(new UserPrincipal("name"));
}

Key principles

  1. Principal is ru.tinkoff.kora.common.Principal — a framework marker interface. Your principal record must implements Principal. Do not define a local Principal.
  2. One extractor per scheme, bound by @Tag(ApiSecurity.<Scheme>.class) — the generated controller picks the extractor matching the operation's security requirement.
  3. 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.
  4. Map auth failures to HTTP codes yourself via an HttpServerInterceptor: 401 = authentication failed (bad/missing credentials), 403 = authorization failed (missing role/scope).
  5. PrincipalWithScopes for OAuth scope enforcement — the only built-in authorization hook; richer role checks live in your delegate or interceptor.
  6. Externalize secrets with @ConfigSource and ${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.

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<CallerContext> {
    @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

SymptomCauseFix
cannot find symbol: method current() on PrincipalPrincipal.current() does not exist in KoraReceive the principal through the generated delegate, or derive it with HttpServerRequestMapper + @Mapping.
cannot find symbol: getAttribute on HttpServerRequestKora has no request-attribute bagRead headers via request.headers().getFirst(...); pass data as a typed @Mapping argument.
Extractor never runsMissing/wrong @Tag(ApiSecurity.<Scheme>.class), or the operation has no security requirementAdd security: in the contract and tag the extractor with the matching marker.
SecurityException surfaces as HTTP 500No error interceptor mapping itAdd an HttpServerInterceptor tagged @Tag(HttpServerModule.class) that maps SecurityException to 403.
Required dependency not found: ...PrincipalExtractorExtractor not declared as a graph componentAdd the default extractor method to @KoraApp or a @Module the app extends.
OAuth scopes not enforcedReturning a plain PrincipalReturn a PrincipalWithScopes so the transport can check the operation's scopes.

Related skills

What ships with it: 8 files

31.7 KB alongside SKILL.md, 1 of them executable

evals/

scripts/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.