/// <summary> /// SAML2 Logout. /// /// The SAML2 logout is complicated as there are two main scenarios. /// /// Scenario 1. /// /// It's this application that triggers the logout. /// The application sends the LogoutRequest to the server /// and the server is supposed to answer with the LogoutResponse, /// sent to this endpoint (assuming the /account/logout is /// registered as the logout endpoint in the IdP). /// /// In this app the LogoutRequest sending is handled in the HomeController /// (home/logout) /// /// Scenario 2. /// /// Another application triggers the logout. The server /// gets the LogoutRequest from this another app and /// sends LogoutRequest here. This app is supposed to /// answer with the LogoutResponse. /// /// Both scenarios means that to handle logouts correctly, /// the app has to be able to both send and receive /// both LogoutRequest and LogoutResponse /// </summary> public ActionResult Logout() { var sam2 = new Saml2AuthenticationModule(); var idpCertificate = new ClientCertificateProvider().GetIdPCertificate(); if (sam2.IsLogoutRequest(this.Request) || sam2.IsLogoutResponse(this.Request) ) { // first check if this is a LogoutResponse from the IdP var logoutResponse = new LogoutResponseFactory().From(this.Request); if (logoutResponse != null) { var result = sam2.MessageSigner.Validate(logoutResponse, idpCertificate); if (!result) { throw new ValidationException("The LogoutResponse is not correctly signed with the IdP certificate"); } } // then check if this is a LogoutRequest from the IdP var logoutRequest = new LogoutRequestFactory().From(this.Request); if (logoutRequest != null) { var result = sam2.MessageSigner.Validate(logoutRequest, idpCertificate); if (!result) { throw new ValidationException("The LogoutResponse is not correctly signed with the IdP certificate"); } } // both possibilities were validated // signout can be performed FormsAuthentication.SignOut(); // create the response in case of the LogoutRequest if (logoutRequest != null) { var assertionIssuer = ConfigurationManager.AppSettings["AssertionIssuer"]; var identityProvider = ConfigurationManager.AppSettings["IdentityProvider"]; var x509Configuration = new X509Configuration() { SignatureCertificate = new ClientCertificateProvider().GetClientCertificate(), IncludeKeyInfo = true, SignatureAlgorithm = Signature.SignatureAlgorithm.SHA256 }; var requestBinding = Binding.POST; var logoutResponseFactory = new LogoutResponseFactory(); logoutResponseFactory.Issuer = assertionIssuer; logoutResponseFactory.Destination = identityProvider; logoutResponseFactory.RequestBinding = requestBinding; logoutResponseFactory.X509Configuration = x509Configuration; // mark the response logoutResponseFactory.InResponseTo = logoutRequest.SessionIndex; switch (logoutResponseFactory.RequestBinding) { case Constants.Binding.POST: return(Content(logoutResponseFactory.CreatePostBindingContent())); default: throw new ArgumentException(string.Format("The {0} logout response binding is not supported", logoutResponseFactory.RequestBinding)); } } } // go back to the main page (possibly with logout return(Redirect(FormsAuthentication.DefaultUrl)); }
/// <summary> /// Logon flow /// </summary> public ActionResult Logon() { var saml2 = new Saml2AuthenticationModule(); // parameters var assertionConsumerServiceURL = ConfigurationManager.AppSettings["AssertionConsumerServiceURL"]; var assertionIssuer = ConfigurationManager.AppSettings["AssertionIssuer"]; var identityProvider = ConfigurationManager.AppSettings["IdentityProvider"]; var artifactResolve = ConfigurationManager.AppSettings["ArtifactResolve"]; var requestBinding = Binding.POST; var responseBinding = Binding.POST; // this is optional var x509Configuration = new X509Configuration() { SignatureCertificate = new ClientCertificateProvider().GetClientCertificate(), IncludeKeyInfo = true, SignatureAlgorithm = Signature.SignatureAlgorithm.SHA256 }; // check if this is if (!saml2.IsSignInResponse(this.Request)) { // AuthnRequest factory var authnRequestFactory = new AuthnRequestFactory(); authnRequestFactory.AssertionConsumerServiceURL = assertionConsumerServiceURL; authnRequestFactory.AssertionIssuer = assertionIssuer; authnRequestFactory.Destination = identityProvider; authnRequestFactory.X509Configuration = x509Configuration; authnRequestFactory.RequestBinding = requestBinding; authnRequestFactory.ResponseBinding = responseBinding; switch (authnRequestFactory.RequestBinding) { case Constants.Binding.REDIRECT: return(Redirect(authnRequestFactory.CreateRedirectBindingContent())); case Constants.Binding.POST: return(Content(authnRequestFactory.CreatePostBindingContent())); default: throw new ArgumentException(string.Format("The {0} request binding is not supported", authnRequestFactory.RequestBinding)); } } else { // the token is created from the IdP's response Saml2SecurityToken securityToken = null; switch (responseBinding) { case Binding.ARTIFACT: var artifactConfig = new ArtifactResolveConfiguration() { ArtifactResolveUri = artifactResolve, Issuer = assertionIssuer, X509Configuration = x509Configuration }; securityToken = saml2.GetArtifactSecurityToken(this.Request, artifactConfig); break; case Binding.POST: securityToken = saml2.GetPostSecurityToken(this.Request); break; default: throw new NotSupportedException(string.Format("The {0} response binding is not yet supported", responseBinding)); } // fail if there is no token if (securityToken == null) { throw new ArgumentNullException("No security token found in the response accoding to the Response Binding configuration"); } // the token will be validated var configuration = new SecurityTokenHandlerConfiguration { CertificateValidator = X509CertificateValidator.None, IssuerNameRegistry = new DemoClientIssuerNameRegistry(), DetectReplayedTokens = false }; configuration.AudienceRestriction.AudienceMode = AudienceUriMode.Never; var tokenHandler = new Saml2SecurityTokenHandler() { Configuration = configuration }; var identity = tokenHandler.ValidateToken(securityToken); // this is the SessionIndex, store it if necessary string sessionIndex = securityToken.Assertion.ID; // the token is validated succesfully var principal = new ClaimsPrincipal(identity); if (principal.Identity.IsAuthenticated) { var formsTicket = new FormsAuthenticationTicket( 1, principal.Identity.Name, DateTime.UtcNow, DateTime.UtcNow.Add(FormsAuthentication.Timeout), false, sessionIndex); this.Response.AppendCookie(new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(formsTicket))); var redirectUrl = FormsAuthentication.GetRedirectUrl(principal.Identity.Name, false); return(Redirect(redirectUrl)); } else { throw new ArgumentNullException("principal", "Unauthenticated principal returned from token validation"); } } }
/// <summary> /// Trigger SAML2 SSO AuthnRequest /// </summary> /// <returns></returns> public ActionResult Logon() { var saml2 = new Saml2AuthenticationModule(); // parameters var assertionConsumerServiceURL = ConfigurationManager.AppSettings["issuerSSO"]; var assertionIssuer = ConfigurationManager.AppSettings["issuerName"]; var identityProvider = ConfigurationManager.AppSettings["ePUAPSSO"]; var artifactResolve = ConfigurationManager.AppSettings["ePUAPArtifact"]; var getTpUserInfoUri = ConfigurationManager.AppSettings["tpUserInfo"]; var requestBinding = Binding.POST; var responseBinding = Binding.ARTIFACT; // this is optional var x509Configuration = new X509Configuration() { SignatureCertificate = new ClientCertificateProvider().GetClientCertificate(), IncludeKeyInfo = true, SignatureAlgorithm = OldMusicBox.Saml2.Signature.SignatureAlgorithm.SHA256 }; // check if this is if (!saml2.IsSignInResponse(this.Request)) { // AuthnRequest factory var authnRequestFactory = new AuthnRequestFactory(); authnRequestFactory.AssertionConsumerServiceURL = assertionConsumerServiceURL; authnRequestFactory.AssertionIssuer = assertionIssuer; authnRequestFactory.Destination = identityProvider; authnRequestFactory.X509Configuration = x509Configuration; authnRequestFactory.RequestBinding = requestBinding; authnRequestFactory.ResponseBinding = responseBinding; return(Content(authnRequestFactory.CreatePostBindingContent())); } else { // the token is created from the IdP's response var artifactConfig = new ArtifactResolveConfiguration() { ArtifactResolveUri = artifactResolve, Issuer = assertionIssuer, X509Configuration = x509Configuration }; var securityToken = saml2.GetArtifactSecurityToken(this.Request, artifactConfig); // fail if there is no token if (securityToken == null || securityToken.Assertion == null || string.IsNullOrEmpty(securityToken.Assertion.ID) ) { throw new ArgumentNullException("No valid security token found in the response accoding to the ARTIFACT Response Binding"); } // the token will be validated #warning Implement token validation // this is the SessionIndex, store it if necessary var sessionIndex = securityToken.Assertion.ID; var client = new ServiceClient(getTpUserInfoUri, x509Configuration.SignatureCertificate); FaultModel fault; var tpUserInfo = client.GetTpUserInfo(sessionIndex, out fault); if (tpUserInfo == null || !tpUserInfo.IsValid) { throw new NullReferenceException(string.Format("GetTpUserInfo returned nothing. Error message is: {0}", fault != null ? fault.FaultString : ".")); } // create the identity var identity = new ClaimsIdentity("ePUAP"); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tpUserInfo.Podpis.Dane.DaneOsobyFizycznej.IdKontaUzytkownikaEpuap)); identity.AddClaim(new Claim(ClaimTypes.GivenName, tpUserInfo.Podpis.Dane.DaneOsobyFizycznej.Imie)); identity.AddClaim(new Claim(ClaimTypes.Surname, tpUserInfo.Podpis.Dane.DaneOsobyFizycznej.Nazwisko.Value)); identity.AddClaim(new Claim(OMBClaimTypes.PESEL, tpUserInfo.Podpis.Dane.DaneOsobyFizycznej.PESEL)); identity.AddClaim(new Claim(OMBClaimTypes.SessionIndex, sessionIndex)); if (!string.IsNullOrEmpty(tpUserInfo.Return.AccountEmailAddress)) { identity.AddClaim(new Claim(ClaimTypes.Email, tpUserInfo.Return.AccountEmailAddress)); } // the token is validated succesfully var principal = new ClaimsPrincipal(identity); if (principal.Identity.IsAuthenticated) { // use the SessionAuthenticationModule to store the auth cookie var sam = FederatedAuthentication.SessionAuthenticationModule; var token = sam.CreateSessionSecurityToken(principal, string.Empty, DateTime.Now.ToUniversalTime(), DateTime.Now.AddMinutes(20).ToUniversalTime(), false); sam.WriteSessionTokenToCookie(token); // fixed return url because the provider redirects here without the query string return(Redirect("/Home/Signed")); } else { throw new ArgumentNullException("principal", "Unauthenticated principal returned from token validation"); } } }