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, oauth2};
use aliri_tower::Oauth2Authorizer;
use axum::{
    extract::Path,
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::{get, post},
    Server, Router,
};
use std::net::SocketAddr;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct CustomClaims {
    iss: jwt::Issuer,
    aud: jwt::Audiences,
    sub: jwt::Subject,
    scope: oauth2::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 oauth2::HasScope for CustomClaims {
    fn scope(&self) -> &oauth2::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 server = Server::bind(&SocketAddr::new([0, 0, 0, 0].into(), 3000))
        .serve(router.into_make_service())
        .await
        .unwrap();

    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

Defines a scope policy for a given endpoint guard