Skip to main content

Crate saml

Crate saml 

Source
Expand description

saml — stateless, async-native SAML 2.0 toolkit.

§Features

  • SAML 2.0 Service Provider, Identity Provider, and proxy composition.
  • Pure-Rust XML / XML-DSig / XML-Canonicalization / XML-Encryption.
  • No libxml2 / xmlsec / openssl C build chain.
  • Async-runtime-agnostic backchannel HTTP via the HttpClient trait (bring-your-own; optional reqwest feature inherits whatever transitive deps the caller’s reqwest configuration brings).
  • Stateless API: caller owns clock, persistence, replay storage.
  • XSW-resistant by structure: validated payload extraction is bound to the signature’s resolved element via VerifiedSignature.
  • Weak algorithms (SHA-1 / RSA-PKCS#1-v1.5 / DSA-SHA1) feature-gated behind weak-algos, off by default.

§Quickstart — Service Provider

use std::time::{Duration, SystemTime};
use saml::{
    Binding, ConsumeResponse, Dispatch, Endpoint, IdpDescriptor, KeyPair,
    LoginTracker, NameIdFormat, PeerCryptoPolicy, DigestAlgorithm, ReplayMode,
    ServiceProvider, ServiceProviderConfig, SignatureAlgorithm, SpLogoutSigning,
    SpLogoutWantSigned, SpWantSigned, SsoResponseBinding, SsoResponseEndpoint,
    StartLogin,
};

let sp = ServiceProvider::new(ServiceProviderConfig {
    entity_id: "https://app.example.com/saml".into(),
    acs: vec![SsoResponseEndpoint::post(
        "https://app.example.com/saml/acs", 0, true,
    )],
    slo: vec![Endpoint::post(
        "https://app.example.com/saml/slo", 0, true,
    )],
    name_id_formats: vec![NameIdFormat::EmailAddress, NameIdFormat::Persistent],
    signing_key: Some(KeyPair::from_pkcs8_pem(sp_priv)?),
    decryption_key: Some(KeyPair::from_pkcs8_pem(sp_enc_priv)?),
    sign_authn_requests: true,
    want_signed: SpWantSigned { response: false, assertions: true },
    allow_unsolicited: false,
    logout_signing: SpLogoutSigning { sign_requests: true, sign_responses: true },
    logout_want_signed: SpLogoutWantSigned { requests: true, responses: true },
    default_peer_crypto_policy: PeerCryptoPolicy::strong_defaults(),
    outbound_signature_algorithm: SignatureAlgorithm::RsaSha256,
    outbound_digest_algorithm: DigestAlgorithm::Sha256,
})?;

let idp = IdpDescriptor::from_metadata_xml(idp_metadata_xml)?;

// --- /auth/login handler ---
let start = sp.start_login(&idp, StartLogin {
    relay_state,
    binding: Binding::HttpRedirect,
    force_authn: false,
    is_passive: false,
    requested_name_id_format: None,
    requested_authn_context: None,
    acs_index: None,
    acs_url: None,
    response_binding: None,
})?;
match start.dispatch {
    Dispatch::Redirect(_url) => { /* redirect user agent */ }
    Dispatch::Post(_form) => { /* render autosubmit form */ }
}

// --- /saml/acs handler ---
let _identity = sp.consume_response(ConsumeResponse {
    idp: &idp,
    peer_crypto_policy: None,
    saml_response,
    binding: SsoResponseBinding::HttpPost,
    relay_state,
    tracker: Some(&tracker),
    expected_destination: "https://app.example.com/saml/acs",
    now: SystemTime::now(),
    clock_skew: Duration::from_secs(60),
    replay_cache: None,
    replay_mode: ReplayMode::All,
    holder_of_key_cert: None,
})?;
// Dedupe identity.assertion_id against your replay store, or pass
// `Some(&InMemoryReplayCache::default())` in the field above.
// Create app session keyed off identity.name_id + identity.session_index.

§Quickstart — Identity Provider

use std::time::{Duration, SystemTime};
use saml::{
    Attribute, AuthnContextClassRef, Binding, C14nAlgorithm, ConsumeAuthnRequest,
    DataEncryptionAlgorithm, DetachedSignature, DigestAlgorithm, Endpoint, IdentityProvider,
    IdentityProviderConfig, IdpAssertionSigning, IdpLogoutSigning, IdpLogoutWantSigned,
    IssueResponse, KeyPair, KeyTransportAlgorithm, NameId, NameIdFormat, PeerCryptoPolicy,
    SignatureAlgorithm, SpDescriptor,
};

let idp = IdentityProvider::new(IdentityProviderConfig {
    entity_id: "https://idp.example.com/saml".into(),
    sso: vec![
        Endpoint::redirect("https://idp.example.com/saml/sso", 0, true),
        Endpoint::post("https://idp.example.com/saml/sso", 1, false),
    ],
    slo: vec![Endpoint::post("https://idp.example.com/saml/slo", 0, true)],
    artifact_resolution: vec![],
    supported_name_id_formats: vec![NameIdFormat::Persistent, NameIdFormat::EmailAddress],
    default_name_id_format: NameIdFormat::Persistent,
    signing_key: KeyPair::from_pkcs8_pem(idp_priv)?,
    decryption_key: None,
    want_authn_requests_signed: true,
    assertion_signing: IdpAssertionSigning { sign_responses: false, sign_assertions: true },
    encrypt_assertions_when_possible: true,
    logout_signing: IdpLogoutSigning { sign_requests: true, sign_responses: true },
    logout_want_signed: IdpLogoutWantSigned { requests: true, responses: true },
    default_session_duration: Duration::from_secs(3600),
    default_peer_crypto_policy: PeerCryptoPolicy::strong_defaults(),
    outbound_signature_algorithm: SignatureAlgorithm::RsaSha256,
    outbound_digest_algorithm: DigestAlgorithm::Sha256,
    outbound_c14n: C14nAlgorithm::ExclusiveCanonical,
    outbound_data_encryption_algorithm: DataEncryptionAlgorithm::Aes256Gcm,
    outbound_key_transport_algorithm: KeyTransportAlgorithm::RsaOaep,
})?;

// --- /saml/sso handler ---
let parsed = idp.consume_authn_request(ConsumeAuthnRequest {
    sp: &sp,
    peer_crypto_policy: None,
    saml_request,
    binding: Binding::HttpRedirect,
    relay_state,
    detached_signature: Some(DetachedSignature {
        signature,
        sig_alg,
        raw_query_string: raw_query,
    }),
    expected_destination: "https://idp.example.com/saml/sso",
    now: SystemTime::now(),
    clock_skew: Duration::from_secs(60),
})?;

// Authenticate the user out of band, then issue the Response.
let _dispatch = idp.issue_response(IssueResponse {
    sp: &sp,
    in_response_to: &parsed,
    name_id: NameId::persistent_for_sp(user_opaque_id, &sp.entity_id),
    attributes: vec![
        Attribute::email(user_email),
        Attribute::display_name(user_display_name),
    ],
    authn_instant: user_authenticated_at,
    session_index: format!("sess-{}", user_session_id),
    session_not_on_or_after: Some(SystemTime::now() + Duration::from_secs(3600)),
    authn_context_class_ref: AuthnContextClassRef::PasswordProtectedTransport,
    force_encrypt_assertion: None,
    now: SystemTime::now(),
    assertion_lifetime: Duration::from_secs(300),
    subject_confirmation_lifetime: Duration::from_secs(300),
    holder_of_key_cert: None,
})?;

§Quickstart — Proxy

use std::time::{Duration, SystemTime};
use saml::{
    Binding, BounceToUpstream, ConsumeAuthnRequest, ConsumeResponse, IdpDescriptor,
    IdentityProvider, NameIdFormat, OpaqueHandleCodec, PersistentPerSpHmac, Proxy,
    ProxyContext, ProxyContextStore, RelayToDownstream, ReleaseAllowList, ReplayMode,
    ServiceProvider, SpDescriptor, SsoResponseBinding,
};

let proxy = Proxy::new(
    &sp,
    &idp,
    Box::new(OpaqueHandleCodec {
        store: redis_store,
        handle_byte_len: 24,
        ttl: Duration::from_secs(600),
    }),
);

// --- /saml/sso handler (downstream SP → proxy) ---
let parsed = idp.consume_authn_request(ConsumeAuthnRequest {
    sp: &downstream_sp,
    peer_crypto_policy: None,
    saml_request,
    binding: Binding::HttpPost,
    relay_state: downstream_relay_state,
    detached_signature: None,
    expected_destination: "https://hub.example.com/saml/sso",
    now: SystemTime::now(),
    clock_skew: Duration::from_secs(60),
})?;

let bounce = proxy.bounce_to_upstream(BounceToUpstream {
    upstream_idp: &upstream_idp,
    downstream_request: &parsed,
    propagate_request_flags: true,
    propagate_authn_context: true,
    propagate_name_id_policy: true,
    upstream_binding: Binding::HttpRedirect,
    now: SystemTime::now(),
})?;
// Dispatch to upstream IdP with `bounce.upstream_relay_state` carrying context.
let _ = bounce;

// --- /saml/acs handler (upstream IdP → proxy) ---
let context: ProxyContext = proxy.context_codec().decode(&upstream_relay_state)?;
let upstream_identity = sp.consume_response(ConsumeResponse {
    idp: &upstream_idp,
    peer_crypto_policy: None,
    saml_response,
    binding: SsoResponseBinding::HttpPost,
    relay_state: Some(&upstream_relay_state),
    tracker: Some(&context.upstream_tracker),
    expected_destination: "https://hub.example.com/saml/acs",
    now: SystemTime::now(),
    clock_skew: Duration::from_secs(60),
    replay_cache: None,
    replay_mode: ReplayMode::All,
    holder_of_key_cert: None,
})?;

let _dispatch = proxy.relay_to_downstream(RelayToDownstream {
    context: &context,
    upstream_identity: &upstream_identity,
    downstream_sp: &downstream_sp,
    attribute_release: &ReleaseAllowList {
        names: vec!["email".into(), "displayName".into(), "groups".into()],
    },
    name_id_transform: &PersistentPerSpHmac {
        key: name_id_hmac_key,
        format: NameIdFormat::Persistent,
    },
    passthrough_authn_context: true,
    now: SystemTime::now(),
    session_lifetime: Duration::from_secs(3600),
    subject_confirmation_lifetime: Duration::from_secs(300),
})?;

§Feature flags

  • reqwest-client (default) — optional ReqwestClient adapter.
  • rsa-sha (default) — RSA-SHA256/384/512 signature algorithms.
  • ecdsa-sha (default) — ECDSA-SHA256/384/512 signature algorithms.
  • xmlenc (default) — XML Encryption (EncryptedAssertion).
  • slo (default) — Single Logout.
  • metadata-emit (default) — metadata_xml / metadata_xml_with_extras.
  • artifact-binding — HTTP-Artifact binding (requires weak-algos).
  • idp-disco — IdP Discovery: Common Domain Cookie + discovery service protocol (off by default).
  • ecp — Enhanced Client or Proxy profile + PAOS binding (off by default).
  • weak-algos — SHA-1 / RSA-PKCS#1-v1.5 / DSA-SHA1 (off by default).

See docs/rfcs/RFC-001-architecture.md for the full design discussion.

Re-exports§

pub use crate::error::Error;
pub use crate::http::HttpClient;
pub use crate::http::HttpRequest;
pub use crate::http::HttpResponse;
pub use crate::replay::InMemoryReplayCache;
pub use crate::replay::ReplayCache;
pub use crate::replay::ReplayMode;
pub use crate::time::format_xs_datetime;
pub use crate::time::parse_xs_datetime;
pub use crate::attribute::Attribute;
pub use crate::authn_context::AuthnContextClassRef;
pub use crate::authn_context::AuthnContextComparison;
pub use crate::authn_context::RequestedAuthnContext;
pub use crate::conditions::Conditions;
pub use crate::nameid::NameId;
pub use crate::nameid::NameIdFormat;
pub use crate::binding::ArtifactRedirect;
pub use crate::binding::Binding;
pub use crate::binding::DecodedWire;
pub use crate::binding::Dispatch;
pub use crate::binding::Endpoint;
pub use crate::binding::PostForm;
pub use crate::binding::SsoResponseBinding;
pub use crate::binding::SsoResponseDispatch;
pub use crate::binding::SsoResponseEndpoint;
pub use crate::binding::SsoResponsePostForm;
pub use crate::binding::WireDirection;
pub use crate::binding::decode_wire;
pub use crate::crypto::cert::PublicKey;
pub use crate::crypto::cert::PublicKeyAlgorithm;
pub use crate::crypto::cert::X509Certificate;
pub use crate::crypto::keypair::KeyPair;
pub use crate::crypto::keypair::OaepDigest;
pub use crate::crypto::verifier::DefaultVerifier;
pub use crate::crypto::verifier::KeyInfo;
pub use crate::crypto::verifier::SignatureVerifier;
pub use crate::crypto::verifier::VerifyMatch;
pub use crate::descriptor::IdpDescriptor;
pub use crate::descriptor::SpDescriptor;
pub use crate::dsig::algorithms::C14nAlgorithm;
pub use crate::dsig::algorithms::DigestAlgorithm;
pub use crate::dsig::algorithms::PeerCryptoPolicy;
pub use crate::dsig::algorithms::SignatureAlgorithm;
pub use crate::dsig::verify::VerifiedSignature;
pub use crate::metadata::parse::EntitiesDescriptor;
pub use crate::metadata::parse::MetadataEntry;
pub use crate::metadata::parse::StreamControl;
pub use crate::metadata::parse::VerifyMetadata;
pub use crate::metadata::parse::parse_signed_entities_descriptor;
pub use crate::metadata::parse::parse_signed_idp_descriptor;
pub use crate::metadata::parse::parse_signed_sp_descriptor;
pub use crate::metadata::parse::stream_entities;
pub use crate::metadata::parse::stream_signed_entities;
pub use crate::metadata::parse::verify_metadata_signature;
pub use crate::metadata::MetadataContact;
pub use crate::metadata::MetadataContactType;
pub use crate::metadata::MetadataExtras;
pub use crate::metadata::MetadataOrganization;
pub use crate::response::Identity;
pub use crate::response::issue::SamlStatusCode;
pub use crate::sp::ConsumeResponse;
pub use crate::sp::LoginTracker;
pub use crate::sp::ServiceProvider;
pub use crate::sp::ServiceProviderConfig;
pub use crate::sp::SpWantSigned;
pub use crate::sp::StartLogin;
pub use crate::sp::StartLoginResult;
pub use crate::sp::SpLogoutSigning;
pub use crate::sp::SpLogoutWantSigned;
pub use crate::idp::AcsSelection;
pub use crate::idp::ConsumeAuthnRequest;
pub use crate::idp::ConsumeAuthnRequestWire;
pub use crate::idp::DetachedSignature;
pub use crate::idp::IdentityProvider;
pub use crate::idp::IdentityProviderConfig;
pub use crate::idp::IdpAssertionSigning;
pub use crate::idp::IssueErrorResponse;
pub use crate::idp::IssueResponse;
pub use crate::idp::ParsedAuthnRequest;
pub use crate::idp::ConsumeLogoutRequestWire;
pub use crate::idp::ConsumeLogoutResponseWire;
pub use crate::idp::IdpLogoutSigning;
pub use crate::idp::IdpLogoutWantSigned;
pub use crate::proxy::Aes256GcmCodec;
pub use crate::proxy::AttributeReleasePolicy;
pub use crate::proxy::AuthnContextComparator;
pub use crate::proxy::BounceResult;
pub use crate::proxy::BounceToUpstream;
pub use crate::proxy::NameIdFromAttribute;
pub use crate::proxy::NameIdTransform;
pub use crate::proxy::OpaqueHandleCodec;
pub use crate::proxy::PassThroughNameId;
pub use crate::proxy::PerSpFormat;
pub use crate::proxy::PersistentPerSpHmac;
pub use crate::proxy::Proxy;
pub use crate::proxy::ProxyContext;
pub use crate::proxy::ProxyContextCodec;
pub use crate::proxy::ProxyContextStore;
pub use crate::proxy::RelayToDownstream;
pub use crate::proxy::ReleaseAll;
pub use crate::proxy::ReleaseAllowList;
pub use crate::proxy::ReleaseNone;
pub use crate::proxy::ReleasePerSp;
pub use crate::proxy::StandardComparator;
pub use crate::proxy::FrontChannelChain;
pub use crate::proxy::FrontChannelState;
pub use crate::proxy::FrontChannelTarget;
pub use crate::xmlenc::algorithms::DataEncryptionAlgorithm;
pub use crate::xmlenc::algorithms::KeyTransportAlgorithm;
pub use crate::logout::ConsumeLogoutRequest;
pub use crate::logout::ConsumeLogoutResponse;
pub use crate::logout::LogoutDispatch;
pub use crate::logout::LogoutOutcome;
pub use crate::logout::LogoutStatus;
pub use crate::logout::LogoutTracker;
pub use crate::logout::ParsedLogoutRequest;
pub use crate::logout::StartLogout;
pub use crate::http::ReqwestClient;

Modules§

attribute
SAML 2.0 <saml:Attribute> representation.
authn
<samlp:AuthnRequest> build, parse, validate.
authn_context
<saml:AuthnContext> / <samlp:RequestedAuthnContext> types.
binding
SAML 2.0 bindings (HTTP-Redirect, HTTP-POST, HTTP-Artifact, SOAP).
conditions
<saml:Conditions> parsed fields.
crypto
Crypto primitives: KeyPair, X509Certificate, SignatureVerifier trait.
descriptor
Parsed metadata descriptors for peer entities.
dsig
XML-DSig: canonicalization, signature verification, signing, transforms.
error
Error type for the saml crate.
http
HTTP backchannel client abstraction.
idp
Identity Provider role.
logout
Single Logout. See docs/rfcs/RFC-007-single-logout.md.
metadata
SAML 2.0 metadata parse + emit.
nameid
SAML 2.0 <saml:NameID> representation.
proxy
Identity proxy composition: act as SP toward upstream IdPs and IdP toward downstream SPs, with a stateless context codec carrying state across the round trip.
replay
Anti-replay protection for SAML assertion IDs.
response
<samlp:Response> + <saml:Assertion> parse, validate, issue, identity extraction.
sp
Service Provider role.
time
XML Schema xs:dateTime parsing and emission.
xml
Thin DOM-ish XML model on top of quick-xml.
xmlenc
XML-Encryption.