Friday, December 04, 2015

IdentityServer : ASP.NET MVC application to idsrv3 to ADFS

This continues the series of blogs I've done on IdentityServer 3.

This scenario involves idsrv3 as both an IDP to an ASP.NET MVC application and as a RP to ADFS. Also I'm using the InMemory option.

So the authentication chain is:

RP --> IS --> ADFS

ADFS is ADFS ("AD FS") 3.0 and IS is version 3. Everything is via WS-Fed. This is being invoked by the Host.Web sample in idsrv3.

Refer to the documentation that has a WS-Federation section at the bottom.

You need two NuGet packages.

Microsoft.Owin.Security.WsFederation

IdentityServer3 - WS-Federation Plugin

Note that the Owin.Security.WsFederation plugin is implemented by Katana. It appears that the plugin only supports consuming metadata. It doesn't appear to support publishing it. This is for upstream federation.

The idsrv3 WS-Federation plugin is implemented by thinktecture. This does generate metadata. This is for downstream federation.

The MVC application is set up as per IdentityServer : Identity Server 3 as a WS-Federation IDP with an ASP.NET MVC application.

You need to change the following:
  • IdentityServerExtensions.cs in the Host.Configuration project

Here's the code for IdentityServerExtensions.cs:

using System.Collections.Generic;
using IdentityServer3.Core.Configuration;
using IdentityServer3.Core.Services;
using IdentityServer3.Host.Config;
using Microsoft.Owin.Security.Facebook;
using Microsoft.Owin.Security.Google;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.Owin.Security.Twitter;

using Microsoft.Owin.Security.WsFederation;

using IdentityServer3.WsFederation.Configuration;
using IdentityServer3.WsFederation.Models;
using IdentityServer3.WsFederation.Services;

namespace Owin
{
    public static class IdentityServerExtension
    {
        public static IAppBuilder UseIdentityServer(this IAppBuilder app)
        {
            // uncomment to enable HSTS headers for the host
            // see: https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
            //app.UseHsts();

            app.Map("/core", coreApp =>
            {
                var factory = new IdentityServerServiceFactory()
                    .UseInMemoryUsers(Users.Get())
                    .UseInMemoryClients(Clients.Get())
                    .UseInMemoryScopes(Scopes.Get());

                factory.CustomGrantValidators.Add(
                    new Registration<ICustomGrantValidator>(typeof(CustomGrantValidator)));
                factory.CustomGrantValidators.Add(
                    new Registration<ICustomGrantValidator>(typeof(AnotherCustomGrantValidator)));

                factory.ConfigureClientStoreCache();
                factory.ConfigureScopeStoreCache();
                factory.ConfigureUserServiceCache();

                var idsrvOptions = new IdentityServerOptions
                {
                    SiteName = "IdentityServer3 plus WsFed IDP / RP",
                    
                    Factory = factory,
                    SigningCertificate = Cert.Load(),

                    PluginConfiguration = ConfigurePlugins,

                    AuthenticationOptions = new AuthenticationOptions
                    {
                        IdentityProviders = ConfigureIdentityProviders,
                        //EnablePostSignOutAutoRedirect = true
                    },

                    //LoggingOptions = new LoggingOptions
                    //{
                    //    EnableKatanaLogging = true
                    //},

                    //EventsOptions = new EventsOptions
                    //{
                    //    RaiseFailureEvents = true,
                    //    RaiseInformationEvents = true,
                    //    RaiseSuccessEvents = true,
                    //    RaiseErrorEvents = true
                    //}

                };

                coreApp.UseIdentityServer(idsrvOptions);
            });

            return app;
        }

        private static void ConfigurePlugins(IAppBuilder pluginApp, IdentityServerOptions options)
        {
            var wsFedOptions = new WsFederationPluginOptions(options);

            // data sources for in-memory services
            wsFedOptions.Factory.Register(new Registration<IEnumerable<RelyingParty>>(RelyingParties.Get()));
            wsFedOptions.Factory.RelyingPartyService = new Registration<IRelyingPartyService>(typeof(InMemoryRelyingPartyService));

            pluginApp.UseWsFederationPlugin(wsFedOptions);
        }

        public static void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
        {
            var google = new GoogleOAuth2AuthenticationOptions
            {
                AuthenticationType = "Google",
                Caption = "Google",
                SignInAsAuthenticationType = signInAsType,

                ClientId = "767400843187-8boio83mb57ruogr9af9ut09fkg56b27.apps.googleusercontent.com",
                ClientSecret = "5fWcBT0udKY7_b6E3gEiJlze"
            };
            app.UseGoogleAuthentication(google);

            var fb = new FacebookAuthenticationOptions
            {
                AuthenticationType = "Facebook",
                Caption = "Facebook",
                SignInAsAuthenticationType = signInAsType,

                AppId = "676607329068058",
                AppSecret = "9d6ab75f921942e61fb43a9b1fc25c63"
            };
            app.UseFacebookAuthentication(fb);

            var twitter = new TwitterAuthenticationOptions
            {
                AuthenticationType = "Twitter",
                Caption = "Twitter",
                SignInAsAuthenticationType = signInAsType,

                ConsumerKey = "N8r8w7PIepwtZZwtH066kMlmq",
                ConsumerSecret = "df15L2x6kNI50E4PYcHS0ImBQlcGIt6huET8gQN41VFpUCwNjM"
            };
            app.UseTwitterAuthentication(twitter);

            var adfs = new WsFederationAuthenticationOptions
            {
                AuthenticationType = "adfs",
                Caption = "ADFS",
                SignInAsAuthenticationType = signInAsType,

                MetadataAddress = "https://adfs.local/federationmetadata/2007-06/federationmetadata.xml",
                Wtrealm = "urn:idsrv3rp"
            };
            app.UseWsFederationAuthentication(adfs);

            var aad = new OpenIdConnectAuthenticationOptions
            {
                AuthenticationType = "aad",
                Caption = "Azure AD",
                SignInAsAuthenticationType = signInAsType,

                Authority = "https://login.windows.net/4ca9cb4c-5e5f-4be9-b700-c532992a3705",
                ClientId = "65bbbda8-8b85-4c9d-81e9-1502330aacba",
                RedirectUri = "https://localhost:44333/core/aadcb"
            };

            app.UseOpenIdConnectAuthentication(aad);
        }
    }
}




For ADFS, you need to add idsrv3 as a RP.

You can get the metadata from https://localhost:44333/core/wsfed/metadata and import into ADFS in the normal fashion.

Once you have done this, you need to change the Endpoints tab in the RP from:

https://localhost:44333/core/wsfed

to

https://localhost:44333/core/external

and you need to add

urn:idsrv3rp

as an extra Identifier in the Identifier tab. This matches the Wtrealm in the code above

The reason you need to change the endpoint is because the two plugins are different (as discussed above). core/wsfed is for downstream, core/external is for upstream.

Now configure the following claims rules:


 


and


Then run up the MVC application. This should redirect to the idsrv3 Login screen. Click the ADFS button, Authenticate on ADFS. You should be redirected back to the application, click the Contacts tab and you should see the claims displayed.

Enjoy!





3 comments:

Doug said...

Can you explain where CustomGrantValidator and AnotherCustomGrantValidator are illustrated?

Doug said...

Can you point me to where CustomGrantValidator and AnotherCustomGrantValidator are illustrated?

nzpcmad said...

They are part of the core code of identityserver3.