Created by
ITfoxtec
Anders Revsgaard
[email protected]
Support
If you have questions please ask them on Stack Overflow. Tag your questions with 'itfoxtec-identity-saml2' and I will answer as soon as possible.
Package
Releases
NuGet ITfoxtec Identity SAML 2.0
NuGet ITfoxtec Identity SAML 2.0 MVC
NuGet ITfoxtec Identity SAML 2.0 MVC Core
License
GitHub code and example
The open source ITfoxtec Identity Saml2 package adds SAML-P support for both Identity Provider (IdP) and Relying Party (RP) on top of the SAML 2.0 functionality implemented in .NET.
The ITfoxtec Identity Saml2 package implements the most important parts of the SAML-P standard and some optional features. Message signing and validation as well as decryption is supported. The package supports SAML 2.0 login, logout, single logout and metadata. Both SP Initiated and IdP Initiated sign on is supported.
The ITfoxtec Identity Saml2 package support signing/encryption certificates in Azure Key Vault.
The ITfoxtec Identity Saml2 package is tested for compliance with AD FS, Azure AD and Azure AD B2C.
Furthermore, the Danish OIOSAML 2.0 profile is supported and the Danish NemLog-in is tested for compliance.
Supported bindings:
The bindings can be used as needed for:
SHA1/SHA256/SHA384/SHA512 is supported for message signing.
ASP.NET MVC and ASP.NET MVC Core is supported by the ITfoxtec Identity SAML 2.0 MVC and MVC Core packages which helps to integrate the ITfoxtec SAML 2.0 package into a MVC og MCV Core application. The code examples in the GetHub repository shows the ASP.NET MVC and MVC Core integrations.
In ASP.NET MVC web Farms is supported by configuring Windows Identity Foundation (WIF), see WIF and Web Farms.
The code shown is only a selection of the example code in GitHub.
The ITfoxtec Identity Saml2 package is integrated into a ASP.NET MVC Core Relying Party application by configuration in Startup and adding an Auth Controller with the following four methods. The binding shown can be changed as needed depending on the requirements.
It is furthermore possible to set some optional parameters on the Saml2AuthnRequest and Saml2LogoutRequest. On the Saml2AuthnRequest e.g. ForceAuthn is supported, which will force the user to enter login credentials even though an SSO context already exists on the Security Token Service (STS) in this case a AD FS.
services.Configure<Saml2Configuration>(Configuration.GetSection("Saml2")); services.Configure<Saml2Configuration>(saml2Configuration => { saml2Configuration.SigningCertificate = CertificateUtil.Load(AppEnvironment.MapToPhysicalFilePath( Configuration["Saml2:SigningCertificateFile"]), Configuration["Saml2:SigningCertificatePassword"]); saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer); var entityDescriptor = new EntityDescriptor(); entityDescriptor.ReadIdPSsoDescriptorFromUrl(new Uri(Configuration["Saml2:IdPMetadata"])); if (entityDescriptor.IdPSsoDescriptor != null) { saml2Configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location; saml2Configuration.SingleLogoutDestination = entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.First().Location; saml2Configuration.SignatureValidationCertificates.AddRange(entityDescriptor.IdPSsoDescriptor.SigningCertificates); } else { throw new Exception("IdPSsoDescriptor not loaded from metadata."); } }); services.AddSaml2();
services.Configure<Saml2Configuration>(Configuration.GetSection("Saml2")); services.Configure<Saml2Configuration>(saml2Configuration => { saml2Configuration.SigningCertificate = CertificateUtil.Load(AppEnvironment.MapToPhysicalFilePath( Configuration["Saml2:SigningCertificateFile"]), Configuration["Saml2:SigningCertificatePassword"]); saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer); saml2Configuration.SignatureValidationCertificates.Add(CertificateUtil.Load(AppEnvironment.MapToPhysicalFilePath( Configuration["Saml2:SignatureValidationCertificateFile"]))); }); services.AddSaml2();
[Route("Login")] public IActionResult Login(string returnUrl = null) { var binding = new Saml2RedirectBinding(); binding.SetRelayStateQuery(new Dictionary<string, string> { { relayStateReturnUrl, returnUrl ?? Url.Content("~/") } }); return binding.Bind(new Saml2AuthnRequest(config)).ToActionResult(); }
[Route("AssertionConsumerService")] public async Task<IActionResult> AssertionConsumerService() { var binding = new Saml2PostBinding(); var saml2AuthnResponse = new Saml2AuthnResponse(config); binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse); await saml2AuthnResponse.CreateSession(HttpContext, ClaimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal)); var returnUrl = binding.GetRelayStateQuery()[relayStateReturnUrl]; return Redirect(string.IsNullOrWhiteSpace(returnUrl) ? Url.Content("~/") : returnUrl); }
[HttpPost("Logout")] [ValidateAntiForgeryToken] public async Task<IActionResult> Logout() { if (!User.Identity.IsAuthenticated) { return Redirect(Url.Content("~/")); } var binding = new Saml2PostBinding(); var saml2LogoutRequest = await new Saml2LogoutRequest(config, User).DeleteSession(HttpContext); return binding.Bind(saml2LogoutRequest).ToActionResult(); }
[Route("LoggedOut")] public IActionResult LoggedOut() { var binding = new Saml2PostBinding(); binding.Unbind(Request.ToGenericHttpRequest(), new Saml2LogoutResponse(config)); return Redirect(Url.Content("~/")); }
[Route("SingleLogout")] public async Task<IActionResult> SingleLogout() { Saml2StatusCodes status; var requestBinding = new Saml2PostBinding(); var logoutRequest = new Saml2LogoutRequest(config, User); try { requestBinding.Unbind(Request.ToGenericHttpRequest(), logoutRequest); status = Saml2StatusCodes.Success; await logoutRequest.DeleteSession(HttpContext); } catch (Exception exc) { // log exception Debug.WriteLine("SingleLogout error: " + exc.ToString()); status = Saml2StatusCodes.RequestDenied; } var responsebinding = new Saml2PostBinding(); responsebinding.RelayState = requestBinding.RelayState; var saml2LogoutResponse = new Saml2LogoutResponse(config) { InResponseToAsString = logoutRequest.IdAsString, Status = status, }; return responsebinding.Bind(saml2LogoutResponse).ToActionResult(); }