Services send service_name and tags in POST /v1/auth/login. MCIAS evaluates auth:login policy with these as the resource context after credentials are verified, enabling rules like: deny guest/viewer human accounts from env:restricted services deny guest accounts from specific named services - loginRequest: add ServiceName and Tags fields - handleLogin: evaluate policy after credential+TOTP check; policy deny returns 403 (not 401) to distinguish access restriction from bad credentials - Go client: Options.ServiceName/Tags stored on Client, sent automatically in every Login() call - Python client: service_name/tags on __init__, sent in login() - Rust client: ClientOptions.service_name/tags, LoginRequest fields, Client stores and sends them in login() - openapi.yaml: document service_name/tags request fields and 403 response for policy-denied logins - engineering-standards.md: document service_name/tags in [mcias] config section with policy examples Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mcias-client (Rust)
Async Rust client library for the MCIAS identity and access management API.
Requirements
- Rust 2021 edition (stable toolchain)
- Tokio async runtime
Installation
Add to Cargo.toml:
[dependencies]
mcias-client = { path = "path/to/clients/rust" }
Quick Start
use mcias_client::{Client, ClientOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new(
"https://auth.example.com".to_string(),
ClientOptions::default(),
)?;
// Authenticate.
let (token, expires_at) = client.login("alice", "s3cret", None).await?;
println!("token expires at {expires_at}");
// The token is stored in the client automatically.
let accounts = client.list_accounts().await?;
// Revoke the token when done.
client.logout().await?;
Ok(())
}
Custom CA Certificate
let ca_pem = std::fs::read("/etc/mcias/ca.pem")?;
let client = Client::new(
"https://auth.example.com".to_string(),
ClientOptions {
ca_cert_pem: Some(ca_pem),
token: None,
},
)?;
Error Handling
All methods return Result<_, MciasError>:
use mcias_client::MciasError;
match client.login("alice", "wrongpass", None).await {
Err(MciasError::Auth { message }) => eprintln!("auth failed: {message}"),
Err(MciasError::Forbidden { message }) => eprintln!("forbidden: {message}"),
Err(MciasError::NotFound { message }) => eprintln!("not found: {message}"),
Err(MciasError::InvalidInput { message }) => eprintln!("bad input: {message}"),
Err(MciasError::Conflict { message }) => eprintln!("conflict: {message}"),
Err(MciasError::Server { status, message }) => eprintln!("server error {status}: {message}"),
Err(MciasError::Transport(e)) => eprintln!("network error: {e}"),
Err(MciasError::Decode(e)) => eprintln!("parse error: {e}"),
Ok((token, _)) => println!("ok: {token}"),
}
Thread Safety
Client is Send + Sync. The internal token is wrapped in
Arc<RwLock<Option<String>>> for safe concurrent access.
Running Tests
cargo test
cargo clippy -- -D warnings