usso 0.4.0

The usso provides a universal single sign-on (SSO) integration for microservices, making it easy to add secure, scalable authentication across different frameworks. This client simplifies the process of connecting any microservice to the USSO service.
Documentation

USSO-RS

Crates.io Docs Rust Crate CI MIT License

Universal Single Sign-On (USSO) client for Rust microservices.

Authenticate users, validate JWTs, check authorization scopes, and manage sessions against the USSO identity platform — all with a single crate.


Features

  • JWT validation — Decode and verify JWTs signed with RS256/RS384/RS512/PS256/PS384/PS512, ES256/ES384/ES512, or EdDSA (auto-detected from the JWT header). JWKS keys support both RSA and EC key types.
  • API key authentication — Verify API keys against the USSO backend.
  • Agent (service-to-service) auth — Generate Ed25519-signed agent JWTs and exchange them for access tokens.
  • Token refresh — Automatically refresh expired access tokens via the USSO refresh endpoint.
  • Scope-based authorization (RBAC) — Built-in hierarchical permission engine with wildcard path/filter matching, owner authorization, scope intersection, and filter scoring.
  • Sync + Async — Every API surface is available in both blocking and async variants.
  • User management — List, create, and profile users via the USSO REST API.
  • Configurable — Customize header names, cookie names, algorithms, and JWKS URLs.
  • Axum integration (optional)FromRequestParts extractors for painless auth in axum web apps.

Quick Start

[dependencies]
usso = "0.3"

# Optional: axum integration
usso = { version = "0.3", features = ["axum"] }

Validate a JWT (sync)

use usso::core::Usso;

let usso = Usso::new(None, Some("https://sso.usso.io/website/jwks.json".into()), None);
match usso.user_data_from_token("eyJ...") {
    Ok(user) => println!("Hello, {}", user.email.unwrap_or_default()),
    Err(e) => eprintln!("Auth failed: {}", e),
}

Validate a JWT (async)

use usso::jwks::init_jwks_async;
use usso::core::decode_token_with_jwks;

init_jwks_async("https://sso.usso.io/website/jwks.json").await.unwrap();
let user = decode_token_with_jwks("https://sso.usso.io/website/jwks.json", "eyJ...").unwrap();

Validate with token-type enforcement

use usso::core::UssoAuth;

let auth = UssoAuth::new(None, Some("https://sso.usso.io".into()));
// Enforce that this must be an "access" token
let user = auth.user_data_from_token("eyJ...", Some("access"))?;

API key verification

use std::collections::HashMap;
let user = auth.user_data_from_api_key("sk-...", &HashMap::new())?;

Interact with the USSO API

use usso::client::sync::UssoClient;

let mut client = UssoClient::new(
    "https://sso.usso.io",
    Some("api-key-123".into()),
    None, None, None,
);
let users = client.get_users()?;

Agent authentication (server-to-server)

let mut client = UssoClient::new(
    "https://sso.usso.io",
    None,
    Some("agent-id-xyz".into()),
    Some("-----BEGIN PRIVATE KEY-----...".into()),
    None,
);
let token = client.use_agent_token(
    &["read:users".into()],
    "my-service",
    None,
)?;

Authorization checks

use usso::authorization::check_access;

let scopes = vec!["admin:users".into(), "read:reports".into()];
let allowed = check_access(&scopes, "users", Some("delete"), None, false);

Axum integration (requires axum feature)

use std::sync::Arc;
use axum::{Router, routing::get, Extension};
use usso::core::UssoAuth;
use usso::integrations::axum::AuthenticatedUser;

let auth = Arc::new(UssoAuth::new(None, Some("https://sso.usso.io".into())));

async fn me(user: AuthenticatedUser) -> String {
    format!("Hello {}", user.0.sub.as_deref().unwrap_or("unknown"))
}

let app: Router<()> = Router::new()
    .route("/me", get(me))
    .layer(Extension(auth));

Modules

Module Description
core JWT decoding (RSA, EC, EdDSA, ES512), Usso and UssoAuth auth orchestrators
config AuthConfig, HeaderConfig, APIHeaderConfig
jwks JWKS fetching (sync/async) with global caching via OnceLock
authorization Scope-based RBAC: check_access, has_subset_scope, is_authorized, owner_authorization, broadest_scope_filter, get_common_scopes
client Full API client (UssoClient / AsyncUssoClient) with session management
session Lightweight session wrapper (UssoSession / AsyncUssoSession)
schemas Data types: UserData, Jwk (RSA + EC), Jwks, UserResponse, UserIdentifierSchema
exceptions Error types: USSOError, JwksError, JwtError
integrations Framework integrations (axum — feature-gated)
utils Agent JWT generation and base64↔UUID conversion

Configuration

AuthConfig

use usso::config::{AuthConfig, HeaderConfig, APIHeaderConfig};

let config = AuthConfig {
    jwks_url: Some("https://sso.usso.io/.well-known/jwks.json".into()),
    api_key_header: Some(APIHeaderConfig {
        header_name: Some("x-api-key".into()),
        verify_endpoint: String::new(), // defaults to /api/sso/v1/apikeys/verify
    }),
    jwt_header: Some(HeaderConfig {
        header_name: Some("Authorization".into()),
        cookie_name: Some("usso-access-token".into()),
    }),
    algorithm: "RS256".into(), // algorithm is auto-detected from the JWT header
};

Environment

JWKS_URL=https://sso.usso.io/website/jwks.json

Algorithm Support

Token verification auto-detects the signing algorithm from the JWT header:

Algorithm Key Type JWK Fields Support
RS256 / RS384 / RS512 RSA n, e
PS256 / PS384 / PS512 RSA-PSS n, e
ES256 / ES384 ECDSA (P-256, P-384) crv, x, y
ES512 ECDSA (P-521) crv, x, y
EdDSA (Ed25519) Edwards x

Authorization System

Scopes follow the format: <action>:<resource>/<path>?<filters>

Privilege hierarchy: none(0) < read(10) < create(20) < update(30) < delete(40) < manage(50) < admin(60) < owner/*(90) < superadmin(100)

Wildcards (*) are supported in path segments and filter values:

  • admin:* matches any resource
  • read:users/* matches any sub-resource of users
  • read:users?region=* matches any region

Available functions

Function Purpose
check_access Check if any of the user's scopes grant access to a resource
is_authorized Check a single user scope against a resource path
has_subset_scope / is_subset_scope Scope containment / delegation checks
owner_authorization Check if a user has owner-level access via user/workspace ID filters
broadest_scope_filter Pick the least restrictive filter from a list (by restriction score)
get_common_scopes Intersect two scope lists, preserving permitted scopes
get_scope_filters Extract filters from scopes matching an action and resource
parse_scope Parse a scope string into (action, path_segments, filters)
use usso::authorization::{
    check_access, has_subset_scope, is_authorized, parse_scope,
    owner_authorization, broadest_scope_filter, get_common_scopes,
};

// Parse a scope
let (action, path, filters) = parse_scope("admin:users/123?region=us-east");

// Check a single scope
let ok = is_authorized("admin:users", "users", Some("delete"), None, false);

// Check against multiple scopes
let ok = check_access(&["admin:users".into()], "users", Some("read"), None, false);

// Check scope containment
let ok = has_subset_scope("read:users", &["admin:*".into()]);

// Owner authorization
let filter = std::collections::HashMap::from([("user_id".into(), "u1".into())]);
let ok = owner_authorization(Some(&filter), Some("u1"), None, None, None, None);

// Broadest (least restrictive) filter
let filters = vec![
    std::collections::HashMap::from([("tenant_id".into(), "t1".into()), ("user_id".into(), "u1".into())]),
    std::collections::HashMap::from([("tenant_id".into(), "t1".into())]),
];
let broadest = broadest_scope_filter(&filters);

// Common scopes
let common = get_common_scopes(&["admin:users".into()], &["read:users".into()]);

Architecture

Your Microservice
  │
  ├── UssoAuth ──► JWT validation (RSA / EC / EdDSA / ES512) ──► USSO Server
  │               ► API key verify
  │
  ├── UssoClient ──► User management API
  │                 ► Token refresh
  │                 ► Agent auth (Ed25519 JWT exchange)
  │                 ► Scope resolution
  │
  ├── authorization (RBAC engine — 11 public functions)
  ├── jwks (global JWKS cache via OnceLock)
  ├── config (header/cookie extraction)
  └── integrations (axum extractors — feature-gated)

Error Handling

Error HTTP Status Description
USSOError::InvalidSignature 401 JWT signature mismatch
USSOError::InvalidToken 401 Malformed or unrecognized token
USSOError::ExpiredToken 401 Token has expired
USSOError::Unauthorized 401 Missing or invalid credentials
USSOError::InvalidTokenType 401 Token type mismatch (e.g. expected access but got refresh)
USSOError::PermissionDenied 403 Insufficient scope for the requested action

Development

# Build
cargo build

# Build with axum integration
cargo build --features axum

# Test
cargo test

# Lint
cargo clippy -- -D warnings

# Format
cargo fmt

# All checks
just precommit

The project uses just as a task runner. See justfile for available commands.


License

MIT — see LICENCE.txt.