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
HttpClienttrait (bring-your-own; optionalreqwestfeature 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) — optionalReqwestClientadapter.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 (requiresweak-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
samlcrate. - 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:dateTimeparsing and emission. - xml
- Thin DOM-ish XML model on top of
quick-xml. - xmlenc
- XML-Encryption.