Quickstart

This page takes you from an empty Cargo.toml to a working, fail-closed authorization gate in a few
minutes. It assumes you have a running Laravel IAM server
and a service token (Client Credentials).

1. Add the dependency

[dependencies]
laravel-iam = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde_json = "1"

The crate is async-first and built on reqwest + tokio. If you want the synchronous client instead,
enable the blocking feature.

2. Build a client

use std::time::Duration;
use laravel_iam::IamClient;

let iam = IamClient::builder()
    .base_url("https://iam.example.com/api/iam/v1")
    .token(std::env::var("IAM_SERVICE_TOKEN")?) // Client Credentials service token
    .timeout(Duration::from_secs(2))            // default is already 2s
    .build()?;

The base_url is the versioned API root (/api/iam/v1), not the bare host. The SDK appends the
endpoint paths (/decisions/check, /decisions/list-resources, /.well-known/jwks.json). A trailing
slash is trimmed for you.

3. Ask for a decision

use laravel_iam::{DecisionQuery, Subject, ResultExt};
use serde_json::json;

let decision = iam.check(DecisionQuery {
    subject: Subject::user("usr_123"),
    application: Some("warehouse".into()),
    permission: "stock.adjust".into(),
    resource: Some("wh_milan".into()),
    context: json!({ "amount": 300 }),
    ..Default::default()
}).await;

if decision.is_allowed() {
    // allow — the ONLY path here is an explicit server permit
} else {
    // deny — server denial OR any transport/parse failure
}

check() returns Result<Decision, IamError>. The ResultExt::is_allowed helper —
brought into scope by use laravel_iam::ResultExt; — collapses every error into false, so the
gate cannot accidentally open. This is the heart of the SDK; see
Fail-closed authorization.

4. The full program

use std::time::Duration;
use laravel_iam::{IamClient, DecisionQuery, Subject, ResultExt};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let iam = IamClient::builder()
        .base_url("https://iam.example.com/api/iam/v1")
        .token(std::env::var("IAM_SERVICE_TOKEN")?)
        .timeout(Duration::from_secs(2))
        .build()?;

    let decision = iam.check(DecisionQuery {
        subject: Subject::user("usr_123"),
        application: Some("warehouse".into()),
        permission: "stock.adjust".into(),
        resource: Some("wh_milan".into()),
        context: json!({ "amount": 300 }),
        ..Default::default()
    }).await;

    if !decision.is_allowed() {
        return Err("forbidden".into()); // fail-closed
    }

    println!("allowed");
    Ok(())
}

5. Inspect the decision (optional)

When you need the details — auditing, or driving a step-up flow — match on the Ok(Decision):

use laravel_iam::{Decision, IamError};

match iam.check(query).await {
    Ok(d) if d.granted()         => { /* allow */ }
    Ok(d) if d.requires_step_up  => { /* prompt step-up to d.required_aal */ }
    Ok(_)                        => { /* explicit deny */ }
    Err(_)                       => { /* transport/parse failure → deny */ }
}

granted() is allowed && !requires_step_up. See Core concepts for the difference
between allowed, granted(), and ResultExt::is_allowed().

Next steps

Verify tokens

Validate OIDC access tokens against the server’s JWKS.

Verifying tokens →

Fail-closed patterns

Idioms for wiring is_allowed() into middleware and gates.

Fail-closed patterns →

Configuration

Timeouts, issuer/audience, environment-driven setup.

Configuration →