10

Hello so I've been trying to achieve WS-Fed SSO on my MVC6 web application, I've read a bit on authentification and all to identify my requirements. I have to use WsFederationAuth so no oauth nor saml protocol would work for me.

Edit : After @Pinpoint suggestion I tried to use owin middleware to achieve the connection, but I will use the full framework DNX451 rather than DNXCore but it's something while waiting for ws-fed to be supported by Vnext.

Pinpoint adapter extension:

 public static class AppBuilderExtensions
    {
#if !DNXCORE50
        public static IApplicationBuilder UseOwinAppBuilder(this IApplicationBuilder app,
            Action<IAppBuilder> configuration)
        {
            if (app == null)
            {
                throw new ArgumentNullException(nameof(app));
            }

            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }


            return app.UseOwin(setup => setup(next =>
            {
                var builder = new AppBuilder();
                var lifetime = (IApplicationLifetime) app.ApplicationServices.GetService(typeof (IApplicationLifetime));

                var properties = new AppProperties(builder.Properties);
                properties.AppName = app.ApplicationServices.GetApplicationUniqueIdentifier();
                properties.OnAppDisposing = lifetime.ApplicationStopping;
                properties.DefaultApp = next;

                configuration(builder);

                return builder.Build<Func<IDictionary<string, object>, Task>>();
            }));
        }
#endif
    }

And in startup.cs :

    #if !DNXCORE50
            app.UseOwinAppBuilder(owin =>
            {
                owin.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
                {
                    MetadataAddress =
                        "https://mysite.accesscontrol.windows.net/FederationMetadata/2007-06/FederationMetadata.xml",
                    Wtrealm = "http://localhost:62569/",
                    SignInAsAuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
                    AuthenticationType = "adfs",
                    SecurityTokenHandlers = new SecurityTokenHandlerCollection
                    {
                        new EncryptedSecurityTokenHandler
                        {
                            Configuration = new SecurityTokenHandlerConfiguration
                            {
                                IssuerTokenResolver = new X509CertificateStoreTokenResolver(StoreName.My,
                                    StoreLocation.LocalMachine)
                            }
                        },
                        new Saml2SecurityTokenHandler
                        {
                            CertificateValidator = X509CertificateValidator.None,
                            
                        }
                    }
                });
            });
#endif

I can feel I'm getting closer to a solution but it's not yet done. I have trouble in handling the token (after the authentication against the adfs)

I get this error with afaiu the token:

SecurityTokenValidationException: IDX10201: None of the the SecurityTokenHandlers could read the 'securityToken':

<Assertion ID="_851fc402-2e9c-4ff8-a743-7d65612255b9" IssueInstant="2015-06-22T16:16:03.852Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
    <Issuer>https://mysite.accesscontrol.windows.net/</Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
            <ds:Reference URI="#_851fc402-2e9c-4ff8-a743-7d65612255b9">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
                <ds:DigestValue>xZdzOnNIG5Ia***********t0feMWIZMLnY=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>KmuScnZBdxyaAJrfLgB9AYheUdR*****************************Xs4o8R+eMCPdWNsDjhLu500UlCgitYerjpLTTyRRdwvFo8T7LlsXO2yjv3dx83Yn+GthE+FswNRH07yIHF5wo5+/TAwiVzg+9SDbK+Nj84PdLMxwIfALAebf4/ECdpqkWvt2ligzFoQckEgZMRepOcAVfBxfELyJSUDAjnpfJCrlCQip0nykC+5R37X00flIlB563p48veeLIt0JaUdG4bwtQ8OCMg1KeD5gYix9p4E3TQ7QovN0HDoWTurLK/0H01IS73fIe6/k6DBA==</ds:SignatureValue>
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <X509Data>
                <X509Certificate>MIIDSjCCAjagAwIBAgIQrcBhMtovuJ**********************MDExLzAtBgNVBAMTJm1hcmdvY29uc2VpbC5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE1MDYxNjA4MTYzOFoXDTIwMDUzMTIyMDAwMFowMTEvMC0GA1UEAxMmbWFyZ29jb25zZWlsLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpeZseXX1IYTABUOPr7nfIAXc7cXAI0k+vR3qEbvy0UxEhYAkAocQR2qUTPQ8D1v4lPp59tnHKBGJ0eHt9DYm/SyKkfHsWfqsysZx5NHXSJIhy/SgHwpd8b2q1NKxqBRLrdJKyAua+WWza4p/HMFjEVoN/upZtngSqxUKO/oYqy6m7smkz8vwjzpJR8tyqN881XBQzvryiF/i3ZPFQqlCT9263TMcAGPpym9uvxHzFPPx3u8IDz3AQydyHeViaJhiXGic0VEcm6LMn3JLOYqAzJnv8z89NdpsL4ynv1ekytt/8yyza3CnsU1E4tFDj1HU3785aFZ1xm6wr1MUK9VOTAgMBAAGjZjBkMGIGA1UdAQRbMFmAEN1alzwM3lJSHdh4LFl7uxmhMzAxMS8wLQYDVQQDEyZtYXJnb2NvbnNlaWwuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQrcBhMtovuJ9MilbEjMjS7TAJBgUrDgMCHQUAA4IBAQAsQ5jNKvS2fLtqs9oB6DGTXdI5nAli5UyvZUQlnfnMvgYjJKrZu79iMe9iu88cmtwZVifG14SRbVdTjUOzngIWAJ5KAQk5t//wSkwgAS+U6AFYI/mee9NLEvOEhrRbpGUP0oL504OZ9zTDeXmGu2FybRB2TvdTKLaeVsBvwqgP33QFkcuPK50fCGC1l3SecIeyWL5fsiw/2+GuTKHjCaeRqnYBgDTINptc9PGayLPBTjs4UPzbccmaYyuanmTAMZGU0iRoGJYet2uAasT52QvWZqD0NUZbWyR1N8CBf5EIW2S/TrpoOBYNgZQU5n9PRJjTBhESHXjfa8RipC8RXU9o</X509Certificate>
            </X509Data>
        </KeyInfo>
    </ds:Signature>
    <Subject>
        <NameID>***********</NameID>
        <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
    </Subject>
    <Conditions NotBefore="2015-06-22T16:16:03.836Z" NotOnOrAfter="2015-06-22T17:16:03.836Z">
        <AudienceRestriction>
            <Audience>http://localhost:62569/</Audience>
        </AudienceRestriction>
    </Conditions>
    <AttributeStatement>
        <Attribute Name="http://schemas.microsoft.com/identity/claims/tenantid">
            <AttributeValue>********************</AttributeValue>
        </Attribute>
        <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
            <AttributeValue>************</AttributeValue>
        </Attribute>
        <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname">
            <AttributeValue>G****l</AttributeValue>
        </Attribute>
        <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
            <AttributeValue>L****s</AttributeValue>
        </Attribute>
        <Attribute Name="http://schemas.microsoft.com/identity/claims/identityprovider">
            <AttributeValue>https://sts.windows.net/7102feaa-34af-4756-85ce-b0f69766d78d/</AttributeValue>
        </Attribute>
        <Attribute Name="http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider">
            <AttributeValue>https://sts.windows.net/7102feaa-34af-4756-85ce-b0f69766d78d/</AttributeValue>
        </Attribute>
    </AttributeStatement>
    <AuthnStatement AuthnInstant="2015-06-22T14:26:14.020Z">
        <AuthnContext>
            <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
        </AuthnContext>
    </AuthnStatement>
</Assertion>
Community
  • 1
  • 1
Lomithrani
  • 2,033
  • 3
  • 18
  • 24

2 Answers2

3

As you already figured out, the WS-Federation middleware has not been ported to ASP.NET 5 yet, but don't panic, it will definitely be: https://twitter.com/blowdart/status/610526268908535808

In the meantime, you can use the OWIN/Katana 3 WS-Federation middleware in an ASP.NET 5 application with a tiny IAppBuilder/IApplicationBuilder adapter (like this one: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/vNext/samples/Mvc/Mvc.Server/Extensions/AppBuilderExtensions.cs#L50), but of course, it won't be compatible with dnxcore50.

If you have a recent ADFS version, you could also consider switching to OAuth2.

Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • 1
    In this case, would ADFS be the identity provider? – Shaun Luttin Jun 20 '15 at 01:23
  • Yep, though "identity provider" is rather an OpenID Connect term, which is not supported by ADFS (unlike its cloud counterpart: AAD). "authorization server" would be the exact name in this case. – Kévin Chalet Jun 20 '15 at 08:56
  • Thanks a LOT for this @Pinpoint , I will look into that monday (I'm a very motivated intern but there are limits to my motivation :D). I definitly would like to use OAuth2, but my boss doesn't want to do any changed on the ADFS part, I have to adapt everything to the existing part, hence using ws-federation. I tried to tell him that IMO ws-fed was old tech and as he was insisting on using .NET5 and all the new stuff so that wasn't logical to stick to an old tech for just one aspect of the project. But he kinda just told me I was lazy and just wanted to do a workaround... – Lomithrani Jun 20 '15 at 13:20
  • @Pinpoint , I still have to understand how to handle my tokens, buts it's going great so far. Do you know where I could look into that, I get "The key needed to verify the signature could not be resolved from the following security key identifier ". I've tried a couple of thing but I don't find somewhere where all this is explained in order to not just try stuff. – Lomithrani Jun 22 '15 at 15:18
  • Out of curiosity, which `Saml2SecurityTokenHandler` are you using exactly? The one from `System.IdentityModel` (shipped with the .NET framework) or the one from `Microsoft.IdentityModel.Tokens`, which supports TokenValidationParameters? https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/master/src/Microsoft.IdentityModel.Protocol.Extensions/Saml2SecurityTokenHandler.cs – Kévin Chalet Jun 24 '15 at 19:10
  • @Pinpoint I think I actually tried both. (I tried a bunch of stuff) but I went back to mvc 5 which seem more coherent for a prod environment. I have to go for SOA, and had issues with wcf aswell in asp.net 5. But I have the exact same error in mvc5 (but again I use the same middleware so I guess it's quite normal) – Lomithrani Jun 29 '15 at 08:01
  • Turned out it was because it's not adfs it was ACS I was using. So the metadata had no sufficient information, I had to build my own token handler and it seems to work fine. – Lomithrani Aug 24 '15 at 09:12
  • @Lomithrani care to gist your final solution? – fiat Mar 18 '16 at 02:21
  • @fiat I have finished this internship so unfortunately I can't. Point is the above solution is mostly done the trick is just to understand and create your own token handler. – Lomithrani Mar 30 '16 at 10:15
1

I found out how to get claims from the http request coming back from ADFS using the prior ASP.NET classes in a way that doesn't touch web.config.

Hopefully this code is useful to someone:

using Microsoft.AspNet.Http;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Specialized;
using System.IdentityModel.Configuration;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Selectors;
using System.IdentityModel.Services;
using System.IdentityModel.Tokens;
using System.IO;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Security;
using System.Text;
using System.Xml;

public class FederationHelper
{
    public ClaimsIdentity GetClaimsIdentityFromResponse(IConfigurationRoot configurationRoot, HttpContext context)
    {
        var absoluteUri = string.Concat(
                    context.Request.Scheme,
                    "://",
                    context.Request.Host.ToUriComponent(),
                    context.Request.PathBase.ToUriComponent(),
                    context.Request.Path.ToUriComponent(),
                    context.Request.QueryString.ToUriComponent());

        var values = new NameValueCollection();

        foreach (var item in context.Request.Query)
        {
            values.Add(item.Key, item.Value);
        }

        foreach (var item in context.Request.Form)
        {
            values.Add(item.Key, item.Value);
        }

        var federation = new FederationSettings
        {
            AudienceUri = "http://contoso",
            Endpoint = "http://sts/ls",
            IssuerName = "http://sts/trust",
            IssuerThumbprint = "[thumbprint]",
            Realm = "https://myapp"
        };

        var identityConfiguration = new IdentityConfiguration(false);
        identityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(federation.AudienceUri));

        var issuers = new ConfigurationBasedIssuerNameRegistry();
        issuers.ConfiguredTrustedIssuers.Add(federation.IssuerThumbprint, federation.IssuerName);
        identityConfiguration.IssuerNameRegistry = issuers;

        var tokenHandler = new SamlSecurityTokenHandler();

        tokenHandler.Configuration = new SecurityTokenHandlerConfiguration();
        tokenHandler.Configuration.AudienceRestriction.AudienceMode = AudienceUriMode.Always;
        tokenHandler.Configuration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(federation.AudienceUri));
        tokenHandler.Configuration.IssuerNameRegistry = identityConfiguration.IssuerNameRegistry;
        tokenHandler.Configuration.CertificateValidationMode = X509CertificateValidationMode.None;
        tokenHandler.Configuration.RevocationMode = X509RevocationMode.NoCheck;
        tokenHandler.Configuration.CertificateValidator = X509CertificateValidator.None;

        var message = (SignInResponseMessage)WSFederationMessage.CreateFromNameValueCollection(new Uri(absoluteUri), values);
        var token = CreateSecurityToken(tokenHandler, identityConfiguration, message);
        var claims = tokenHandler.ValidateToken(token);

        return claims[0];
    }

    public SecurityToken CreateSecurityToken(SamlSecurityTokenHandler handler, IdentityConfiguration configuration, SignInResponseMessage message)
    {
        var quotas = new XmlDictionaryReaderQuotas();

        using (var reader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(message.Result), quotas))
        {
            var serializer = new WSFederationSerializer(reader);
            var context = new WSTrustSerializationContext(configuration.SecurityTokenHandlerCollectionManager);

            var xml = serializer.CreateResponse(message, context).RequestedSecurityToken.SecurityTokenXml.OuterXml;

            return ReadToken(handler, xml, quotas);
        }
    }

    SecurityToken ReadToken(SamlSecurityTokenHandler handler, string xml, XmlDictionaryReaderQuotas quotas)
    {
        using (var reader = new StringReader(xml))
        {
            using (var xmlReader = XmlReader.Create(reader))
            {
                xmlReader.MoveToContent();

                return handler.ReadToken(xmlReader);
            }
        }
    }
}
ttg
  • 359
  • 1
  • 10