agentsclimarketplace

Kora http server auth

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

Agent Skills for Kora Framework — compile-time DI for Java/Kotlin backend development.

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.

One thing to look at

  • 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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

11.8 KB, 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

Keep looking

Skills are one crate of 328,083. 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.