/// <summary>
        /// Handles the sign in.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="SecurityTokenException">No token validator was found for the given token.</exception>
        private async Task <HandleRequestResult> HandleSignIn()
        {
            if (Request.Method != HttpMethods.Post)
            {
                return(HandleRequestResult.Fail("Request method must be an HTTP-Post Method"));
            }

            var form = await Request.ReadFormAsync();

            var response   = form[Saml2Constants.Parameters.SamlResponse];
            var relayState = form[Saml2Constants.Parameters.RelayState].ToString()?.DeflateDecompress();

            AuthenticationProperties authenticationProperties = Options.StateDataFormat.Unprotect(relayState);

            try
            {
                if (authenticationProperties == null)
                {
                    if (!Options.AllowUnsolicitedLogins)
                    {
                        return(HandleRequestResult.Fail("Unsolicited logins are not allowed."));
                    }
                }

                if (authenticationProperties.Items.TryGetValue(CorrelationProperty, out string correlationId) &&
                    !ValidateCorrelationId(authenticationProperties))
                {
                    return(HandleRequestResult.Fail("Correlation failed.", authenticationProperties));
                }

                string       base64EncodedSamlResponse = response;
                ResponseType idpSamlResponseToken      = _saml2Service.GetSamlResponseToken(base64EncodedSamlResponse, Saml2Constants.ResponseTypes.AuthnResponse, Options);

                IRequestCookieCollection cookies = Request.Cookies;
                string originalSamlRequestId     = cookies[cookies.Keys.FirstOrDefault(key => key.StartsWith(Options.AuthenticationScheme))];

                _saml2Service.CheckIfReplayAttack(idpSamlResponseToken.InResponseTo, originalSamlRequestId);
                _saml2Service.CheckStatus(idpSamlResponseToken);

                string token = _saml2Service.GetAssertion(idpSamlResponseToken, Options);

                AssertionType assertion     = new AssertionType();
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(AssertionType));
                using (MemoryStream memStm = new MemoryStream(Encoding.UTF8.GetBytes(token)))
                {
                    assertion = (AssertionType)xmlSerializer.Deserialize(memStm);
                }

                if (Options.WantAssertionsSigned)
                {
                    var doc = new XmlDocument
                    {
                        XmlResolver        = null,
                        PreserveWhitespace = true
                    };
                    doc.LoadXml(token);

                    if (!_saml2Service.ValidateX509CertificateSignature(doc, Options))
                    {
                        throw new Exception("Assertion signature is not valid");
                    }
                }

                AuthnStatementType session = new AuthnStatementType();

                if (assertion.Items.Any(x => x.GetType() == typeof(AuthnStatementType)))
                {
                    session = (AuthnStatementType)assertion.Items.FirstOrDefault(x => x.GetType() == typeof(AuthnStatementType));
                }

                if (assertion.Subject.Items.Any(x => x.GetType() == typeof(NameIDType)))
                {
                    Options.NameIDType = (NameIDType)assertion.Subject.Items.FirstOrDefault(x => x.GetType() == typeof(NameIDType));
                }

                if (_configuration == null)
                {
                    _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
                }

                var             tvp         = Options.TokenValidationParameters.Clone();
                var             validator   = Options.Saml2SecurityTokenHandler;
                ClaimsPrincipal principal   = null;
                SecurityToken   parsedToken = null;

                var issuers = new[] { _configuration.Issuer };
                tvp.ValidateIssuerSigningKey = Options.WantAssertionsSigned;
                tvp.ValidateTokenReplay      = !Options.IsPassive;
                tvp.ValidateIssuer           = true;
                tvp.ValidateAudience         = true;
                tvp.ValidIssuers             = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers));
                tvp.IssuerSigningKeys        = (tvp.IssuerSigningKeys == null ? _configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(_configuration.SigningKeys));

                if (!Options.WantAssertionsSigned) // in case they aren't signed
                {
                    tvp.RequireSignedTokens = false;
                }

                if (validator.CanReadToken(token))
                {
                    principal = validator.ValidateToken(token, tvp, out parsedToken);
                }

                if (principal == null)
                {
                    throw new SecurityTokenException("No token validator was found for the given token.");
                }

                if (Options.UseTokenLifetime && parsedToken != null)
                {
                    // Override any session persistence to match the token lifetime.
                    var issued = parsedToken.ValidFrom;
                    if (issued != DateTime.MinValue)
                    {
                        authenticationProperties.IssuedUtc = issued.ToUniversalTime();
                    }
                    var expires = parsedToken.ValidTo;
                    if (expires != DateTime.MinValue)
                    {
                        authenticationProperties.ExpiresUtc = expires.ToUniversalTime();
                    }
                    authenticationProperties.AllowRefresh = false;
                }

                ClaimsIdentity identity = new ClaimsIdentity(principal.Claims, Scheme.Name);

                session.SessionIndex = !String.IsNullOrEmpty(session.SessionIndex) ? session.SessionIndex : assertion.ID;
                //get the session index from assertion so you can use it to logout later
                identity.AddClaim(new Claim(Saml2ClaimTypes.SessionIndex, session.SessionIndex));
                identity.AddClaim(new Claim(ClaimTypes.Name, principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value));

                string redirectUrl = !string.IsNullOrEmpty(authenticationProperties.RedirectUri) ? authenticationProperties.RedirectUri : Options.CallbackPath.ToString();
                Context.Response.Redirect(redirectUrl, true);
                Context.User = new ClaimsPrincipal(identity);
                await Context.SignInAsync(Options.SignInScheme, Context.User, authenticationProperties);

                return(HandleRequestResult.Success(new AuthenticationTicket(Context.User, authenticationProperties, Scheme.Name)));
            }
            catch (Exception exception)
            {
                return(HandleRequestResult.Fail(exception, authenticationProperties));
            }
        }
Exemple #2
0
        /// <summary>
        /// Handles the sign in.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="SecurityTokenException">No token validator was found for the given token.</exception>
        private async Task <HandleRequestResult> HandleSignIn()
        {
            if (Request.Method != HttpMethods.Post)
            {
                return(HandleRequestResult.Fail("Request method must be an HTTP-Post Method"));
            }

            var form = await Request.ReadFormAsync();

            var response   = form[Saml2Constants.Parameters.SamlResponse];
            var relayState = form[Saml2Constants.Parameters.RelayState].ToString()?.DeflateDecompress();

            AuthenticationProperties authenticationProperties = Options.StateDataFormat.Unprotect(relayState);

            try
            {
                if (authenticationProperties == null)
                {
                    if (!Options.AllowUnsolicitedLogins)
                    {
                        return(HandleRequestResult.Fail("Unsolicited logins are not allowed."));
                    }
                }

                if (authenticationProperties.Items.TryGetValue(CorrelationProperty, out string correlationId) &&
                    !ValidateCorrelationId(authenticationProperties))
                {
                    return(HandleRequestResult.Fail("Correlation failed.", authenticationProperties));
                }

                string       base64EncodedSamlResponse = response;
                ResponseType idpSamlResponseToken      = _saml2Service.GetSamlResponseToken(base64EncodedSamlResponse, Saml2Constants.ResponseTypes.AuthnResponse, Options);

                // Write To XML Doc `_xmlDoc` Object, This Step Is Critical For The ValidateToken Call Below.
                LoadXmlFromBase64(response);

                IRequestCookieCollection cookies = Request.Cookies;
                string originalSamlRequestId     = cookies[cookies.Keys.FirstOrDefault(key => key.StartsWith(Options.AuthenticationScheme))];

                _saml2Service.CheckIfReplayAttack(idpSamlResponseToken.InResponseTo, originalSamlRequestId);
                _saml2Service.CheckStatus(idpSamlResponseToken);

                string token = _saml2Service.GetAssertion(idpSamlResponseToken, Options);

                AssertionType assertion     = new AssertionType();
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(AssertionType));
                using (MemoryStream memStm = new MemoryStream(Encoding.UTF8.GetBytes(token)))
                {
                    assertion = (AssertionType)xmlSerializer.Deserialize(memStm);
                }

                if (Options.WantAssertionsSigned)
                {
                    var doc = new XmlDocument
                    {
                        XmlResolver        = null,
                        PreserveWhitespace = true
                    };
                    doc.LoadXml(token);

                    if (!_saml2Service.ValidateX509CertificateSignature(doc, Options))
                    {
                        throw new Exception("Assertion signature is not valid");
                    }
                }

                AuthnStatementType session = new AuthnStatementType();

                if (assertion.Items.Any(x => x.GetType() == typeof(AuthnStatementType)))
                {
                    session = (AuthnStatementType)assertion.Items.FirstOrDefault(x => x.GetType() == typeof(AuthnStatementType));
                }

                if (assertion.Subject.Items.Any(x => x.GetType() == typeof(NameIDType)))
                {
                    Options.NameIDType = (NameIDType)assertion.Subject.Items.FirstOrDefault(x => x.GetType() == typeof(NameIDType));
                }

                if (_configuration == null)
                {
                    _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
                }

                var             tvp         = Options.TokenValidationParameters.Clone();
                var             validator   = Options.Saml2SecurityTokenHandler;
                ClaimsPrincipal principal   = null;
                SecurityToken   parsedToken = null;

                var issuers = new[] { _configuration.Issuer };
                tvp.ValidateIssuerSigningKey = Options.WantAssertionsSigned;
                tvp.ValidateTokenReplay      = !Options.IsPassive;
                tvp.ValidateIssuer           = true;
                tvp.ValidateAudience         = true;
                tvp.ValidIssuers             = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers));
                tvp.IssuerSigningKeys        = (tvp.IssuerSigningKeys == null ? _configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(_configuration.SigningKeys));

                if (!Options.WantAssertionsSigned) // in case they aren't signed
                {
                    tvp.RequireSignedTokens = false;
                }

                if (Options.EnablePIILogging)
                {
                    IdentityModelEventSource.ShowPII = true;
                }

                if (validator.CanReadToken(token))
                {
                    // Our Token Is Valid, Now Check Signaures.

                    var doc = new XmlDocument
                    {
                        XmlResolver        = null,
                        PreserveWhitespace = true
                    };
                    doc.LoadXml(token);

                    // Pull Signatures.

                    XmlNodeList XMLSignatures = doc.GetElementsByTagName(Saml2Constants.Parameters.Signature, Saml2Constants.Namespaces.DsNamespace);

                    var signedXmlDoc = new SignedXml(doc);

                    signedXmlDoc.LoadXml((XmlElement)XMLSignatures[0]);

                    KeyInfoX509Data  x509data     = signedXmlDoc.Signature.KeyInfo.OfType <KeyInfoX509Data>().First();
                    X509Certificate2 cert         = (X509Certificate2)x509data.Certificates[0];
                    string           serialNumber = cert.SerialNumber;
                    X509Certificate2 _idpcert     = Options.Configuration.X509Certificate2.Where(c => c.SerialNumber == serialNumber).FirstOrDefault();

                    var _xmlNameSpaceManager = GetNamespaceManager(); // Manager For XPath Queries.

                    // Important: To Check Signatures We Pull The Signature XML Node.

                    // Critically This Check Differers From The ValidateToken Call Below, See Note.

                    XmlNodeList nodeList  = _xmlDoc.SelectNodes(".//ds:Signature", _xmlNameSpaceManager);
                    SignedXml   signedXml = new SignedXml(_xmlDoc);
                    signedXml.LoadXml((XmlElement)nodeList[0]);

                    var check_reference = ValidateSignatureReference(signedXml);
                    var check_signature = signedXml.CheckSignature(cert, true);

                    if (check_reference && check_signature)
                    {
                        // Tech Note: At This Point We've:

                        // 1. Verified The Token.
                        // 2. Verified The Signature Reference.
                        // 3. Verified The Signature For The Entire Document, And The Specific Assertion.

                        // The Final Step Is To Create Our Security Principal Via The ValidateToken() Call.

                        // The Big "Trick" Here Is This Call Is Known To Fail When The XML Source Is Not .NET.

                        // Specifically, The Signature Validation Process Requires The XML Byte Stream To Be Identical,
                        // And Unfortunately Our Token Call Above, Among Other Things, Strips XML Namespaces And Changes Line Feeds.

                        // To Address This We Pass This Call The "Original" XML From The LoadXmlFromBase64() Call Above.
                        try
                        {
                            XmlNodeList nodeListAssertion = _xmlDoc.SelectNodes(".//saml:Assertion", _xmlNameSpaceManager);

                            // This Call Maps Our SAML <saml2:AttributeStatement> Items To Our Identity As Claims.

                            principal = validator.ValidateToken(nodeListAssertion[0].OuterXml, tvp, out parsedToken);
                        }
                        catch (Exception e) { }
                    }
                }

                if (principal == null)
                {
                    throw new SecurityTokenException("No token validator was found for the given token.");
                }

                if (Options.UseTokenLifetime && parsedToken != null)
                {
                    // Override any session persistence to match the token lifetime.
                    var issued = parsedToken.ValidFrom;
                    if (issued != DateTime.MinValue)
                    {
                        authenticationProperties.IssuedUtc = issued.ToUniversalTime();
                    }
                    var expires = parsedToken.ValidTo;
                    if (expires != DateTime.MinValue)
                    {
                        authenticationProperties.ExpiresUtc = expires.ToUniversalTime();
                    }
                    authenticationProperties.AllowRefresh = false;
                }

                ClaimsIdentity identity = new ClaimsIdentity(principal.Claims, Scheme.Name);

                session.SessionIndex = !String.IsNullOrEmpty(session.SessionIndex) ? session.SessionIndex : assertion.ID;

                //get the session index from assertion so you can use it to logout later
                identity.AddClaim(new Claim(Saml2ClaimTypes.SessionIndex, session.SessionIndex));

                // Create Entry For User.Identity.Name

                if (principal.Claims.Any(c => c.Type == ClaimTypes.NameIdentifier))
                {
                    identity.AddClaim(new Claim(ClaimTypes.Name, principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value));
                }

                string redirectUrl = !string.IsNullOrEmpty(authenticationProperties.RedirectUri) ? authenticationProperties.RedirectUri : Options.CallbackPath.ToString();
                Context.Response.Redirect(redirectUrl, true);
                Context.User = new ClaimsPrincipal(identity);
                await Context.SignInAsync(Options.SignInScheme, Context.User, authenticationProperties);

                return(HandleRequestResult.Success(new AuthenticationTicket(Context.User, authenticationProperties, Scheme.Name)));
            }
            catch (Exception exception)
            {
                return(HandleRequestResult.Fail(exception, authenticationProperties));
            }
        }