Authentication
Connect your application's credential verifier to the HTTP flow through DI, guards, and Principal.
Lily provides guards, middleware, typed headers/cookies, and Principal infrastructure. Your application chooses the token format and the actual credential verification implementation. This section does not assume a ready-made JWT or OAuth module.
Verifier contract#
In this example, CredentialVerifier is an application-defined interface. AuthGuard receives it through DI, awaits verification, and adds the principal to the request only after success.
use std::sync::Arc;
use lilyrs::http_api::{
ExecutionCancellation, Extensions, GuardInitError, GuardRejection,
GuardTrait, HttpErrorCode, Principal, Request,
};
// The application provides an Injectable singleton verifier for this interface.
#[lilyrs::http_api::async_trait::async_trait]
pub trait CredentialVerifier: Send + Sync {
async fn verify(
&self, request: &Request, cancellation: ExecutionCancellation,
) -> Result<Principal, GuardRejection>;
}
pub struct AuthGuard { verifier: Arc<dyn CredentialVerifier> }
#[lilyrs::http_api::async_trait::async_trait]
impl GuardTrait for AuthGuard {
async fn new(extensions: Arc<Extensions>) -> Result<Self, GuardInitError> {
let verifier = extensions.get_service::<dyn CredentialVerifier>(None).await
.map_err(|_| GuardInitError::Dependency {
guard: "AuthGuard", reason: "credential verifier unavailable".into(),
})?;
Ok(Self { verifier })
}
async fn can_activate(&self, request: &mut Request, cancellation: ExecutionCancellation)
-> Result<(), GuardRejection>
{
let principal = self.verifier.verify(request, cancellation).await?;
request.set_principal(principal);
Ok(())
}
}
pub struct AdminGuard;
#[lilyrs::http_api::async_trait::async_trait]
impl GuardTrait for AdminGuard {
async fn new(_: Arc<Extensions>) -> Result<Self, GuardInitError> { Ok(Self) }
async fn can_activate(&self, request: &mut Request, _: ExecutionCancellation)
-> Result<(), GuardRejection>
{
match request.principal() {
Some(principal) if principal.has_role("admin") => Ok(()),
Some(_) => Err(GuardRejection::forbidden(
HttpErrorCode::new("ADMIN_REQUIRED").unwrap(),
).unwrap()),
None => Err(GuardRejection::unauthorized(
HttpErrorCode::new("AUTH_REQUIRED").unwrap(), "Bearer",
).unwrap()),
}
}
}Register the verifier implementation#
Add #[derive(Injectable)] and #[service(interface = dyn CredentialVerifier, lifetime = "Singleton")] to your real verifier service and implement ServiceTrait. Import these types from lilyrs::http_api with the existing http-api feature; DI setup requires no separate helper dependencies.
The verifier completes credential parsing and checks the signature, issuer, audience, expiry, and revocation when required by its protocol. It produces a Principal after successful verification. Merely finding or decoding a bearer string is not authentication.
Protect an endpoint#
Use #[guard(AuthGuard)] on the controller action. If a role check is also needed, choose the order #[guard(AuthGuard, AdminGuard)]. The action can access the verified identity through a principal: Principal parameter.
Supplying a prebuilt guard through AppBuilder::guard(instance) does not enable global protection. Every protected action must explicitly register its guard.
Errors and cancellation#
Return an appropriate 401 challenge for missing or invalid credentials. Choose 403 when an authenticated user lacks permission. If the verifier calls an external store or service, propagate ExecutionCancellation into its operation context.
This example demonstrates an integration boundary. Without a real verifier registration, the AuthGuard constructor stops startup with a dependency error.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.