Skip to main content

sso/
sso.rs

1//! End-to-end typed SSO: SP starts an `AuthnRequest`, the IdP receives it and
2//! issues a signed `Response`, and the SP finishes with a typed session.
3//!
4//! Run with: `cargo run -p saml-rs --example sso`
5//! (the `crypto-bergshamra` feature is on by default).
6#![allow(
7    deprecated,
8    reason = "the example keeps exercising the deprecated strict() preset"
9)]
10
11#[cfg(any(
12    feature = "crypto-rustcrypto",
13    feature = "crypto-aws-lc",
14    feature = "crypto-fips"
15))]
16fn main() -> Result<(), Box<dyn std::error::Error>> {
17    use saml_rs::{
18        AcsEndpoint, AuthnRequest, BrowserInput, CertificatePem, Credentials, EntityId, IdpConfig,
19        IdpDescriptor, IdpValidationPolicy, MetadataTrustPolicy, NameId, PrivateKeyPem,
20        RelayStateParam, ReplayPolicy, RespondSso, Saml, SamlValidationContext, SpConfig,
21        SpDescriptor, SpValidationPolicy, SsoEndpoint, SsoResponse, StartSso, Subject,
22    };
23    use std::time::SystemTime;
24
25    let privkey = include_str!("../tests/fixtures/key/sp_privkey.pem");
26    let cert = include_str!("../tests/fixtures/key/sp_signing_cert.cer");
27    let credentials = || Credentials {
28        signing_key: Some(PrivateKeyPem::new(privkey)),
29        signing_certificate: Some(CertificatePem::new(cert)),
30        ..Credentials::default()
31    };
32    // DisabledForCompatibility preserves the raw API, which has no replay cache.
33    let validation =
34        || SamlValidationContext::new(SystemTime::now(), ReplayPolicy::DisabledForCompatibility);
35
36    let sp = Saml::sp(
37        SpConfig::builder(EntityId::try_new("https://sp.example.com/metadata")?)
38            .acs_endpoint(AcsEndpoint::post("https://sp.example.com/acs")?)
39            .credentials(credentials())
40            .validation(SpValidationPolicy::strict())
41            .build()?,
42    )?;
43    let idp = Saml::idp(
44        IdpConfig::builder(EntityId::try_new("https://idp.example.com/metadata")?)
45            .sso_endpoint(SsoEndpoint::post("https://idp.example.com/sso")?)
46            .credentials(credentials())
47            .validation(IdpValidationPolicy::strict())
48            .build()?,
49    )?;
50
51    let sp_descriptor = SpDescriptor::from_metadata_xml_for(
52        EntityId::try_new("https://sp.example.com/metadata")?,
53        sp.metadata_xml(),
54        // UnsignedForCompatibility preserves an unsigned samlify-port metadata import.
55        MetadataTrustPolicy::UnsignedForCompatibility,
56    )?;
57    let idp_descriptor = IdpDescriptor::from_metadata_xml_for(
58        EntityId::try_new("https://idp.example.com/metadata")?,
59        idp.metadata_xml(),
60        // UnsignedForCompatibility preserves an unsigned samlify-port metadata import.
61        MetadataTrustPolicy::UnsignedForCompatibility,
62    )?;
63
64    let relay_state = RelayStateParam::try_from_option(Some("demo-state".to_string()))?;
65    let started = sp.start_sso(&idp_descriptor, StartSso::post().relay_state(relay_state))?;
66    println!(
67        "SP  -> AuthnRequest id = {}",
68        started.pending.request_id().as_str()
69    );
70
71    let request = idp.receive_sso(
72        &sp_descriptor,
73        BrowserInput::<AuthnRequest>::post(started.outbound.post_form()?.fields().to_vec()),
74        validation(),
75    )?;
76    println!(
77        "IdP <- request issuer  = {}",
78        request.message().issuer().as_str()
79    );
80
81    let response = idp.respond_sso(
82        &sp_descriptor,
83        &request,
84        Subject::new(NameId::new("alice@example.com", None), Vec::new()),
85        RespondSso::post(),
86    )?;
87
88    let session = sp.finish_sso(
89        &idp_descriptor,
90        &started.pending,
91        BrowserInput::<SsoResponse>::post(response.post_form()?.fields().to_vec()),
92        validation(),
93    );
94    let session = session?;
95    println!("SP  <- authenticated   = {}", session.name_id().value());
96    Ok(())
97}
98
99#[cfg(not(any(
100    feature = "crypto-rustcrypto",
101    feature = "crypto-aws-lc",
102    feature = "crypto-fips"
103)))]
104fn main() {
105    eprintln!("Enable a crypto provider feature (`crypto-bergshamra` is on by default).");
106}