Getting Started
Rust Quickstart
Embed coresdk-engine directly in your Rust service — no sidecar required.
Rust Quickstart
coresdk-engine embeds the full engine in-process. Auth, Rego policy evaluation, PII masking, and OTel tracing all run inside your Rust service — no sidecar, no network hop.
Install
cargo add coresdk-engine tokio --features tokio/full
cargo add axum serde serde_json tracing tracing-subscriberCargo.toml:
[dependencies]
coresdk-engine = "0.1"
tokio = { version = "1", features = ["full"] }
axum = "0.7"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tracing = "0.1"Initialize the engine
use std::sync::Arc;
use coresdk_engine::Engine;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
// Initialize from environment variables
let engine = Arc::new(Engine::from_env()?);
// ...
Ok(())
}| Env var | Default | Description |
|---|---|---|
CORESDK_JWKS_URL | — | Remote JWKS URL for real JWT verification |
CORESDK_TENANT_ID | default | Default tenant if not in JWT |
CORESDK_SERVICE_NAME | axum-app | OTel service name |
CORESDK_FAIL_MODE | open | open = allow on error · closed = deny |
CORESDK_POLICY_POOL_SIZE | min(cpus, 8) | Rego engine pool size |
Auth middleware (Tower / Axum)
use axum::{
extract::{Request, State},
http::{HeaderMap, StatusCode},
middleware::{self, Next},
response::{IntoResponse, Response},
};
use coresdk_engine::{Engine, auth::decision::AuthRequest, error::ProblemDetail};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
engine: Arc<Engine>,
}
/// Tower middleware: validates JWT, injects user claims as a request extension.
async fn auth_middleware(
State(state): State<AppState>,
headers: HeaderMap,
mut request: Request,
next: Next,
) -> Response {
let token = headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.unwrap_or("");
if token.is_empty() {
return ProblemDetail::unauthorized("Missing Bearer token")
.into_response();
}
match state.engine.auth().authorize(AuthRequest {
token: token.to_string(),
..Default::default()
}) {
Ok(decision) if decision.allowed => {
request.extensions_mut().insert(decision.claims);
next.run(request).await
}
Ok(_) => ProblemDetail::unauthorized("Token rejected").into_response(),
Err(e) => ProblemDetail::unauthorized(e.to_string()).into_response(),
}
}Full Axum app
use axum::{Router, routing::get, extract::State, Json};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let state = AppState {
engine: Arc::new(Engine::from_env()?),
};
let app = Router::new()
.route("/healthz", get(healthz))
.route("/me", get(me))
.route("/products", get(list_products))
.layer(middleware::from_fn_with_state(state.clone(), auth_middleware))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
tracing::info!("listening on :3000");
axum::serve(listener, app).await?;
Ok(())
}
async fn healthz() -> Json<serde_json::Value> {
Json(serde_json::json!({"status": "ok"}))
}
async fn me(
axum::extract::Extension(claims): axum::extract::Extension<serde_json::Value>,
) -> Json<serde_json::Value> {
Json(claims)
}
async fn list_products(
axum::extract::Extension(claims): axum::extract::Extension<serde_json::Value>,
State(state): State<AppState>,
) -> Json<serde_json::Value> {
let tenant = claims["tenant_id"].as_str().unwrap_or("default");
Json(serde_json::json!({
"tenant": tenant,
"products": [],
}))
}Role guard
fn check_role(claims: &serde_json::Value, role: &str) -> Result<(), Response> {
let roles = claims["roles"].as_array().map(|a| {
a.iter().filter_map(|r| r.as_str()).collect::<Vec<_>>()
}).unwrap_or_default();
if !roles.contains(&role) {
return Err(ProblemDetail::forbidden(
format!("Role '{}' required", role)
).into_response());
}
Ok(())
}
async fn create_product(
axum::extract::Extension(claims): axum::extract::Extension<serde_json::Value>,
Json(body): Json<serde_json::Value>,
) -> Response {
if let Err(r) = check_role(&claims, "editor") { return r; }
Json(serde_json::json!({"created": body})).into_response()
}Rego policy evaluation (ABAC)
The engine pool (N regorus::Engine instances, one per blocking thread) evaluates policies without mutex contention:
use coresdk_engine::policy::decision::PolicyInput;
async fn get_document(
axum::extract::Extension(claims): axum::extract::Extension<serde_json::Value>,
Path(doc_id): Path<String>,
State(state): State<AppState>,
) -> Response {
let tenant = claims["tenant_id"].as_str().unwrap_or("default").to_string();
let subject = claims["sub"].as_str().unwrap_or("").to_string();
let roles: Vec<String> = claims["roles"]
.as_array().unwrap_or(&vec![])
.iter().filter_map(|r| r.as_str().map(String::from)).collect();
let engine = state.engine.clone();
let doc_id_clone = doc_id.clone();
// spawn_blocking: regorus eval is sync + CPU-bound
let allowed = tokio::task::spawn_blocking(move || {
engine.policy().evaluate("data.authz.allow", PolicyInput {
tenant_id: tenant,
subject,
action: "read".into(),
resource: format!("documents/{}", doc_id_clone),
context: serde_json::json!({"roles": roles}),
})
}).await.unwrap_or(Ok(false)).unwrap_or(false);
if !allowed {
return ProblemDetail::forbidden("Access denied").into_response();
}
Json(serde_json::json!({"id": doc_id, "content": "..."})).into_response()
}RFC 9457 errors
ProblemDetail serializes to application/problem+json:
use coresdk_engine::error::ProblemDetail;
use axum::response::IntoResponse;
// Helpers
ProblemDetail::unauthorized("Missing Bearer token").into_response()
ProblemDetail::forbidden("Role 'admin' required").into_response()
ProblemDetail::not_found("Product 42 not found").into_response()Response shape:
{
"type": "https://coresdk.io/errors/unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "Missing Bearer token"
}Run
RUST_LOG=info cargo run
# Health check
curl http://localhost:3000/healthz
# With JWT (fail-open without JWKS configured)
curl -H "Authorization: Bearer my-token" http://localhost:3000/meFull working example
rust/axum-app/ — complete project with auth middleware, policy pool, ABAC route, RFC 9457 errors, OTel tracing, Docker.