Crate aliri_axum

source ·
Expand description

Axum utilities that make it easier to enforce OAuth2 authorization scopes in your application.

§Full Example

use aliri::jwt;
use aliri_clock::UnixTime;
use aliri_oauth2::{Authority, HasScope, Scope};
use aliri_tower::Oauth2Authorizer;
use axum::{
    extract::Path,
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::{get, post},
    Router,
};
use std::net::SocketAddr;
use serde::Deserialize;

#[derive(Clone, Debug, Deserialize)]
pub struct CustomClaims {
    iss: jwt::Issuer,
    aud: jwt::Audiences,
    sub: jwt::Subject,
    scope: Scope,
}

impl jwt::CoreClaims for CustomClaims {
    fn nbf(&self) -> Option<UnixTime> { None }
    fn exp(&self) -> Option<UnixTime> { None }
    fn aud(&self) -> &jwt::Audiences { &self.aud }
    fn iss(&self) -> Option<&jwt::IssuerRef> { Some(&self.iss) }
    fn sub(&self) -> Option<&jwt::SubjectRef> { Some(&self.sub) }
}

impl HasScope for CustomClaims {
    fn scope(&self) -> &Scope {
       &self.scope
    }
}

mod scope {
    aliri_axum::scope_guards! {
        type Claims = super::CustomClaims;

        pub scope AdminOnly = "admin";
        pub scope List = "list";
        pub scope Read = "read";
        pub scope Write = "write";
        pub scope ReadWrite = "read write";
        pub scope ReadOrList = ["read" || "list"];
    }
}

async fn admin_action(guard: scope::AdminOnly) -> String {
    format!("You're an admin, {}!", guard.claims().sub)
}

async fn create_resource(_: scope::Write) -> Response {
    (StatusCode::CREATED, "Created resource").into_response()
}

async fn read_resource(
    scope::Read(claims): scope::Read,
    Path(id): Path<String>,
) -> String {
    format!("{} read resource {id}", claims.sub)
}

async fn construct_authority() -> Result<Authority, Box<dyn std::error::Error>> {
    // Construct an authority
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let authority = construct_authority().await?;
    let authorizer = Oauth2Authorizer::new()
        .with_claims::<CustomClaims>()
        .with_terse_error_handler();
    // For verbose error handling, use `with_verbose_error_handler()`
    // or your own custom handler.

    // Build the router
    let router = Router::new()
        .route("/admin", get(admin_action))
        .route("/resource", post(create_resource))
        .route("/resource/{id}", get(read_resource))
        .layer(authorizer.jwt_layer(authority));
    // For verbose scope errors, add the following layer:
    // .layer(axum::Extension(aliri_axum::VerboseAuthxErrors));

    // Construct the server
    let listener = tokio::net::TcpListener::bind(&SocketAddr::new([0, 0, 0, 0].into(), 3000))
        .await?;
    axum::serve(listener, router).await?;

    Ok(())
}

Macros§

  • Constructs an extractor that enables easily asserting that a provided token has the expected set of scopes.
  • Convenience macro for services that need to define many scopes.

Structs§

  • Add this type as an extension to produce verbose errors when authentication or authorization fails

Enums§

  • An error indicating that the request could not be authorized

Traits§