Skip to content
Lenso
English
Esc
navigateopen⌘Jpreview
On this page

Protect an Endpoint

Authenticate one inbound credential, attach a signed ActorAssertion, and leave final authorization with the target Plugin.

Protecting a route crosses three owners. Keeping them separate prevents a valid credential from becoming blanket business permission:

1. Bind Auth to the Endpoint

The Endpoint provider declares and resolves one AuthClient during activation. App Composition must publish that exact binding. Web Ingress does not discover an Auth provider and does not make the authentication decision.

Ingress accepts one Authorization credential and converts it into protocol-neutral scheme/value evidence. It removes the credential header before dispatch, so downstream Plugins receive only the evidence or a sealed assertion—not ambient HTTP headers.

2. Make identity visible in the handler

Define the actor kind your HTTP edge expects and delegate extraction to lenso-http-auth:

use lenso_auth_sdk::ActorAssertion;
use lenso_capability_http_endpoint::{ExtractorFuture, FromRequest, HandleRequest};
use lenso_http_auth::{AuthenticatedHttpActor, extract_authenticated_actor};
use lenso_kernel::InvocationContext;

struct UserActor { subject: String }

impl AuthenticatedHttpActor for UserActor {
    const KIND: &'static str = "user";

    fn from_assertion(assertion: &ActorAssertion) -> Self {
        Self { subject: assertion.subject().to_owned() }
    }
}

impl FromRequest<OrdersHttp> for UserActor {
    fn from_request<'a>(
        provider: &'a OrdersHttp,
        context: &'a mut InvocationContext,
        request: &'a HandleRequest,
    ) -> ExtractorFuture<'a, Self> {
        extract_authenticated_actor(provider, context, request)
    }
}

The provider also implements AuthClientSource to return its activation-time client. The helper converts evidence, calls Auth, emits stable authentication responses, and attaches the returned assertion to the invocation context.

Use the actor in the route signature so readers can see the authentication requirement before reading its body:

#[endpoint]
impl OrdersHttp {
    #[get("orders.read", "/orders/{order_id}")]
    async fn read(
        &self,
        actor: UserActor,
        context: InvocationContext,
        Path(path): Path<OrderPath>,
    ) -> Result<HandleResponse, EndpointHandleInvocationError> {
        self.handle_authenticated(context, actor.subject, path.order_id).await
    }
}

3. Authorize at the business operation

Authentication answers “who presented acceptable evidence?” The target Plugin still verifies assertion issuer, Ed25519 proof, validity window, and the exact audience such as orders.api@1:read, then applies tenant, ownership, or role policy for that operation. It receives a verification key, never the Auth signing key.

Keep the response mapping intentional:

Outcome HTTP result
Missing, invalid, expired, or revoked credential 401 with WWW-Authenticate
Valid actor without permission for the operation 403
Auth or business Capability unavailable 503

4. Prove the boundary

Test at least: no credential, malformed credential, expired/revoked credential, valid actor, wrong actor kind, wrong operation audience, and valid actor denied by business policy. Exercise the final cases through a real Ingress socket so the credential stripping and status mapping are covered.

Continue with Auth Plugin when implementing the provider, token issuance, revocation, or target-side verifier.

Last updated on September 6, 2026

Was this page helpful?