C# Web API集成SP发起的SAML 2.0 POST及生成SP元数据求助
Got it, let's walk through how to implement SP-initiated SAML 2.0 POST in a C# Web API and generate valid SP metadata for your clients. I'll start with the core concepts, then dive into actionable code snippets using a battle-tested library to avoid reinventing the wheel.
SP-initiated SAML 2.0 POST follows this flow:
- A user tries to access a protected endpoint in your Web API.
- Your API redirects the user to the Identity Provider (IdP) login page with a signed SAML Authentication Request.
- After successful login, the IdP sends a SAML Assertion back to your API's Assertion Consumer Service (ACS) endpoint via HTTP POST.
- Your API validates the assertion's signature, checks its validity (expiry, issuer, etc.), and creates a local user session (e.g., JWT cookie or token).
- The user is redirected to the original protected resource with access granted.
For SP metadata: This is a standardized XML document that tells the IdP how to communicate with your SP—including your entity ID, ACS endpoint URL, signing certificate, and supported algorithms. You'll expose this as a public endpoint for your clients to import into their IdP configuration.
We'll use the ITfoxtec.Identity.Saml2 library (the go-to choice for .NET SAML implementations) to handle all the heavy lifting of SAML message serialization, signature validation, and metadata generation.
Step 1: Install the Library
First, add the NuGet package to your project:
# Using .NET CLI dotnet add package ITfoxtec.Identity.Saml2.AspNetCore
Step 2: Configure SAML Settings
Add your SP and IdP configuration to appsettings.json:
"Saml2Settings": { "Issuer": "https://your-sp-api.com/api/saml/metadata", // Unique SP entity ID (usually your metadata endpoint URL) "AcsUrl": "https://your-sp-api.com/api/saml/acs", // ACS endpoint where IdP sends assertions "SignatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "Certificate": { "FilePath": "Certificates/SP-Signing-Cert.pfx", // Path to your SP's signing certificate "Password": "your-cert-password" }, "IdPMetadata": { "MetadataUrl": "https://your-client-idp.com/metadata" // Your client's IdP metadata URL } }
Step 3: Register SAML Services in Program.cs
For .NET 6+, wire up the SAML services in your Program.cs:
using ITfoxtec.Identity.Saml2; using ITfoxtec.Identity.Saml2.MvcCore; var builder = WebApplication.CreateBuilder(args); // Bind SAML configuration from appsettings builder.Services.AddSaml2(options => { builder.Configuration.GetSection("Saml2Settings").Bind(options); options.SignAuthnRequest = true; // Always sign authentication requests for security }); // Add authentication with SAML2 scheme builder.Services.AddAuthentication(Saml2Defaults.AuthenticationScheme) .AddSaml2(); builder.Services.AddControllers(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthentication(); // Enable authentication middleware app.UseAuthorization(); // Enable authorization middleware app.MapControllers(); app.Run();
Create a SamlController to handle login initiation, ACS callbacks, and metadata generation:
using ITfoxtec.Identity.Saml2; using ITfoxtec.Identity.Saml2.MvcCore; using ITfoxtec.Identity.Saml2.Schemas; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; [Route("api/saml")] [ApiController] public class SamlController : ControllerBase { private readonly Saml2Configuration _samlConfig; private readonly ILogger<SamlController> _logger; public SamlController(IOptions<Saml2Configuration> samlConfig, ILogger<SamlController> logger) { _samlConfig = samlConfig.Value; _logger = logger; } // Initiate SP-initiated SAML login [HttpGet("login")] public IActionResult Login(string returnUrl = "/api/protected") { var authnRequest = new Saml2AuthnRequest(_samlConfig, new Saml2PostBinding()) { Destination = _samlConfig.IdPSsoUrl, RelayState = returnUrl }; var binding = new Saml2PostBinding(); binding.SetRelayStateQuery(new Dictionary<string, string> { { "ReturnUrl", returnUrl } }); // Redirect user to IdP login page with signed AuthnRequest return binding.Bind(authnRequest).ToActionResult(); } // ACS endpoint: Receive and validate SAML assertion from IdP [HttpPost("acs")] [ValidateAntiForgeryToken] public async Task<IActionResult> Acs() { var samlResponse = new Saml2AuthnResponse(_samlConfig); try { var binding = new Saml2PostBinding(); binding.ReadSamlResponse(Request.ToGenericHttpRequest(), samlResponse); // Check if the SAML response is successful if (samlResponse.Status != Saml2StatusCodes.Success) { throw new Exception($"SAML authentication failed with status: {samlResponse.Status}"); } // Validate the assertion's signature and create a user session samlResponse.CreateSession(HttpContext, claimsTransform: principal => { // Add custom claims if needed principal.Identities.First().AddClaim(new System.Security.Claims.Claim("api_access", "full")); return principal; }); // Redirect back to the original protected resource var returnUrl = binding.GetRelayStateQuery()["ReturnUrl"]; return Redirect(returnUrl); } catch (Exception ex) { _logger.LogError(ex, "Error processing SAML ACS request"); return BadRequest("SAML authentication failed. Please try again."); } } // Expose SP metadata for clients to import into their IdP [HttpGet("metadata")] public IActionResult Metadata() { var metadata = new Saml2Metadata(_samlConfig) { SigningCertificate = _samlConfig.SigningCertificate, WantAssertionsSigned = true, // Require IdP to sign assertions for security Organization = new Saml2Organization { Names = new List<Saml2LocalizedName> { new() { Value = "Your Company Name", Lang = "en" } }, DisplayNames = new List<Saml2LocalizedName> { new() { Value = "Your SP API", Lang = "en" } }, Urls = new List<Saml2LocalizedUri> { new() { Value = "https://your-sp-api.com", Lang = "en" } } } }; return new Saml2MetadataContentResult(metadata); } }
Protected Endpoint Example
Create a protected API endpoint to test the flow:
[Route("api/protected")] [ApiController] [Authorize(AuthenticationSchemes = Saml2Defaults.AuthenticationScheme)] public class ProtectedController : ControllerBase { [HttpGet] public IActionResult Get() { var userDetails = new { UserName = User.Identity.Name, Claims = User.Claims.Select(c => new { c.Type, c.Value }) }; return Ok(new { Message = "Access granted to protected resource", User = userDetails }); } }
- Certificate Management: Use a valid SSL certificate for signing SAML requests/responses. Share the public key (from your PFX) with your clients so their IdP can verify your signatures.
- Security Best Practices: Always sign AuthnRequests and require signed assertions. Use HTTPS for all SAML-related endpoints to prevent man-in-the-middle attacks.
- Metadata Updates: If your client's IdP changes their metadata (e.g., endpoints, certificates), update your
IdPMetadataconfiguration promptly. - Error Handling: Add robust error handling in the ACS endpoint to catch invalid signatures, expired assertions, and other common SAML errors.
- RelayState Validation: Validate the
ReturnUrlin the RelayState to prevent open redirect attacks (e.g., check if it's a valid path in your API).
内容的提问来源于stack exchange,提问作者Neet Singh

