Login to Umbraco BackOffice using IdentityServer4

This post will work through the details in setting up IdentityServer4 and Umbraco to enable the OWIN Identity features of the Umbraco BackOffice.

UloveI

Disclaimer: I have been working with content management systems for a very long time (Microsoft Content Management Server anyone 😊), but Umbraco was pretty new to me. These blog posts are my personal notes and reminders, but also shared for everyone to read. If you spot any errors or have feedback that will help me or others, please feel free to comment!

Goal: Login to Umbraco BackOffice using IdentityServer4 (or any other OpenID Connect or OAuth 2.0 Authentication Service).

My environment:

  • IdentityServer4 (1.3.1);
  • Umbraco 7 (7.5.11);
  • MSSQL databases for both Umbraco and IdentityServer4

Also in the mix:

IdentityServer4 is designed for flexibility and part of that is allowing you to use any database you want for your users and their profile data and passwords. Since I want to show you how we can extend the Umbraco BackOffice by working with roles and claims, I choose to start with ASP.NET Core Identity as the user store. The people from IdentityServer4 have provided excellent documentation on how to set this up, so I am not going to repeat the obvious parts:

https://identityserver4.readthedocs.io/en/release/quickstarts/6_aspnet_identity.html

Highlevel steps:

  1. Setup (install) IdentityServer4 through Nuget in Visual Studio;
  2. Follow the Quick Start mentioned above;
  3. Configure IdentityServer4 by adding Clients and Identity Resources;
  4. Configure Kestrel (ports);
  5. Seed Users (and roles, claims) for test/ development purposes;
  6. Setup Umbraco;
  7. Configure Umbraco BackOffice to support an external Identity Provider;
  8. Transform Claims to support Auto Link feature.

TLDR; version:

  1. Download the source code;
  2. Open in Visual Studio;
  3. Restore packages;
  4. Hit F5.

Setup IdentityServer

The first part is pretty easy and documented by the IdentityServer4 documentation. Just a couple of things we need for our setup to keep in mind:

  • Follow the steps described here: https://identityserver4.readthedocs.io/en/release/quickstarts/6_aspnet_identity.html
  • We use the Visual Studio template for ASP.NET Identity and several Nuget packages for IdentityServer4. They provide us with a lot of code we need for the ASP.NET Identity implementation. They also contain MVC Views and Controllers for application logic (Account Login, Registration, etc.). Make sure you review your actual requirements before taking this solution to production;
  • The following Nuget packages are needed: IdentityServer4, IdentityServer4.AspNetIdentity;
  • You can follow all the steps from the mentioned documentation/ quickstart. We will configure our specific client needs in the next steps;
  • The template creates a default connection string. Update the appsettings.json to point to the database of your choice. You need to do this before creating the user database with “dotnet ef database update”.

Configure IdentityServer

After finishing the initial setup we need to configure the IdentityServer.

  • Configure Clients;
  • Configure Identity Resources;
  • Configure Service Startup.

Configure Clients

For the purpose of this demo we create our clients through code. There is also documentation on the IdentityServer4 project site that enables configuration through Entity Framework databases. Check the links at the end of this article for more information.

We start with a separate class file to store our configuration. This file should contain the following client config:

public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientId = "u-client-bo",
                    ClientSecrets = new List<Secret>
                    {
                        new Secret("secret".Sha256()),
                    },
                    ClientName = "Umbraco Client",
                    AllowedGrantTypes = GrantTypes.Hybrid,
                    RequireConsent = false,
                    RedirectUris           = { "http://localhost:22673/Umbraco" },
                    PostLogoutRedirectUris = { "http://localhost:22673/Umbraco" },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.Email,
                        "application.profile",
                    },
                    AllowAccessTokensViaBrowser = true,
                    AlwaysIncludeUserClaimsInIdToken = false
                }
            };
        }

The important parts:

  • ClientId: this needs to be in sync with the Umbraco BackOffice client used later in this article.
  • AllowedGrantTypes: since this is a MVC server application, we can trust the client. If you want to keep the token away from the browser, you can use the “authorization code flow” or the “hybrid” flow.
  • Redirect- and PostLogoutRedirectUris: needed for the interactive part of the login flow.
  • AllowAccessTokensViaBrowser: if you need browser based clients accessing tokens.
  • AlwaysIncludeUserClaimsInIdToken: add additional claims to the ID token.
  • AllowedScopes: determines what Identity (and Resource) information we are allowed to access. The “application.profile” is a custom scope that includes all the roles/ claims we need for our Umbraco integration.

Configure IdentityResources

For the purpose of this demo we create our clients through code. There is also documentation on the IdentityServer4 project site that enables configuration through Entity Framework databases. Check the links at the end of this article for more information. In this case we add the following code to the previously used Config class:

public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            var customProfile = new IdentityResource(
                name: "application.profile",
                displayName: "Application profile",
                claimTypes: new[] { ClaimTypes.GivenName, ClaimTypes.Surname }
            );

            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
                customProfile
            };
        }

The important parts:

  • customProfile: this is our custom scope including custom claims (“role” and “permission”) and the claim(types) we need for the Umbraco Account Linking feature.
  • List of resources: contain the default resources + our custom profile.

Note: ASP.NET Identity does not support GivenName and Surname out of the box. There are several options to extend this and in this case I chose to store these values as Custom ASP.NET Identity User Claims. After seeding the users (next chapter), you will find these claims in the database table “AspNetUserClaims”. The Umbraco Auto Link External Account feature requires the claims GivenName, Surname and Email to contain values.

On the next page we are going to use these parts and fire up IdentityServer.

Startup

ASP.NET Identity uses Entity Framework by default to get hold of the users and profile data. The connection to this store is the first thing we need to setup. The connection string is stored in the appsettings.json and you can update this to point to another database. If you already did this during the IdentityServer setup, you are good to go. If not, you can update the setting and perform another Entity Framework migration (dotnet ef database update) to create the tables.

Update the connection string:

 // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Wire up ASP.NET Identity (should already be done by the Visual Studio template):


            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

Configure IdentityServer:

services.AddIdentityServer()
                .AddTemporarySigningCredential()
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddInMemoryClients(Config.GetClients())
                .AddAspNetIdentity<ApplicationUser>();

The important parts:

  • Adds the IdentityResources from the Config class created earlier.
  • Adds the Clients from the Config class created earlier.
  • Wire up ASP.NET Identity and IdentityServer

And update the Kestrel configuration in Program.cs:

public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseUrls("http://localhost:5000")
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .UseApplicationInsights()
                .Build();

            host.Run();
        }

Note: using SSL might be considered or recommended, although not implemented in the demo source code.

Note 2: In the sourcecode you will find a section that adds (“seeds”) several user accounts during service startup so we can start using our configuration.

Note 3: If you want to smoke test your server setup, you can navigate to http://localhost:5000 and try to login with one of the seeded user accounts.

Setup Umbraco

Since we are going to work with Umbraco and Visual Studio, we need to set it up manually. Umbraco has the steps worked out on the official documentation website: https://our.umbraco.org/documentation/Getting-Started/Setup/Install/install-umbraco-with-nuget

Just follow the steps mentioned there, but note the following:

  • Use a blank web application.
  • Visual Studio 2017 also works, although not mentioned on the website.
  • After Nuget installation, F5 runs Umbraco and the setup starts.
  • Create an empty database for Umbraco CMS.
  • You can create a local user, but we will configure IdentityServer support next.

By default, Umbraco uses a SQL Compact Edition, but I like to use a “real” one even for development. During the installation, you can customize the database info and provide a connectionstring.

Configure Umbraco BackOffice

Umbraco is built with ASP.NET and can easily be extended to support ASP.NET Identity. The first step is to install the required package: UmbracoCms.IdentityExtensions.

PM> Install-Package UmbracoCms.IdentityExtensions

This will install the basic files and classes we need. Next step is to install an Identity Provider we can use as an example to explore the options. This step is optional, but will also install documentation we can use to build our own Identity Extension. You choose from a couple of different providers, I choose Microsoft OAuth.

PM> Install-Package UmbracoCms.IdentityExtensions.Microsoft

Because we will be working with IdentityServer and OpenID Connect, we need to install the required package:

PM> Install-Package Microsoft.Owin.Security.OpenIdConnect

Configure OWIN startup class

The IdentityExtensions package deployed several files to App_Start folder. The main file to look for is the UmbracoCustomOwinStartup.cs. This file allows us to configure OWIN for Umbraco and open up BackOffice for third party Identity Providers (in our case IdentityServer).

The files contains plenty comments to make sure you understand all the options. For our purposes we need the following:

 var identityOptions = new OpenIdConnectAuthenticationOptions
            {
                ClientId = "u-client-bo",
                SignInAsAuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType,
                Authority = "http://localhost:5000",
                RedirectUri = "http://localhost:22673/Umbraco",
                ResponseType = "code id_token token",
                Scope = "openid profile application.profile",
                PostLogoutRedirectUri = "http://localhost:22673/Umbraco"
            };

            // Configure BackOffice Account Link button and style
            identityOptions.ForUmbracoBackOffice("btn-microsoft", "fa-windows");
            identityOptions.Caption = "OpenId Connect";

            // Fix Authentication Type
            identityOptions.AuthenticationType = "http://localhost:5000";

            // Configure AutoLinking
            identityOptions.SetExternalSignInAutoLinkOptions(
                new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true));

            identityOptions.Notifications = new OpenIdConnectAuthenticationNotifications
            {
                SecurityTokenValidated = ClaimsTransformer.GenerateUserIdentityAsync
            };

            app.UseOpenIdConnectAuthentication(identityOptions);

There is a lot going on here:

  • identityOptions: this needs to match with our IdentityServer Clients Config. If you mess this up, IdentityServer will provide error messages if configured correctly (see “Running in Kestrel” at the end of this article);
  • ForUmbracoBackOffice: this sets classes and style for the link/connect buttons in the Umbraco BackOffice UI;
  • SetExternalSignInAutoLinkOptions: auto link Umbraco and External account;
  • Notifications: we need to transform some of the claims to enable AutoLink. This is done using a ClaimsTransformer, see next topic.

Transform Claims

ASP.NET Identity by default does things a little bit different: the “name” claim contains the email address and the email address claim is empty. But we need a regular email claim (and a couple of others) to enable Umbraco Auto Link so we construct them ourselves.

Create a separate class file for the transform: ClaimsTransformer.

public class ClaimsTransformer
    {
        public static async Task GenerateUserIdentityAsync(
            SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
        {
            var identityUser = new ClaimsIdentity(
                notification.AuthenticationTicket.Identity.Claims,
                notification.AuthenticationTicket.Identity.AuthenticationType,
                ClaimTypes.Name,
                ClaimTypes.Role);

            var newIdentityUser = new ClaimsIdentity(identityUser.AuthenticationType,
                ClaimTypes.GivenName, ClaimTypes.Role);

            newIdentityUser.AddClaim(identityUser.FindFirst(ClaimTypes.NameIdentifier));

This method takes care of construction the new ClaimsIdentity containing the first two required claims:

  • NameIdentifier
  • EmailAddress

But we need a couple more claims:

  • GivenName
  • Surname

These claims are available through the UserInfo Endpoint (see http://docs.identityserver.io/en/release/endpoints/userinfo.html for more information). In order to use the UserInfoClient, we need to install the required Nuget Package:

PM> Install-Package IdentityModel

//Optionally add other claims
            var userInfoClient = new UserInfoClient(
                new Uri(notification.Options.Authority + "/connect/userinfo").ToString());

            var userInfo = await userInfoClient.GetAsync(notification.ProtocolMessage.AccessToken);
            newIdentityUser.AddClaims(userInfo.Claims.Select(t => new Claim(t.Type, t.Value)));

            notification.AuthenticationTicket = new AuthenticationTicket(newIdentityUser,
                notification.AuthenticationTicket.Properties);

Pffff finally, we should now have everything in place to correctly integrate both platforms! Only one more step needed 😉

OWIN Startup

To have Umbraco BackOffice pickup our custom OWIN Startup Class, we edit the web.config:

UmbracoBO4

Change the appSetting value in the web.config called “owin:appStartup” to be “UmbracoCustomOwinStartup”.

That’s it, time to test!

Test Umbraco BackOffice Login

This part is the fun part. Just build and run both projects and you should  be presented with Umbraco BackOffice at “/Umbraco”:

UmbracoBO1

If all worked out, you should see the third party login button (perfectly styled of course). Go for the “Sign in with OpenId Connect” and you will be redirected to the IdentityServer.

UmbracoBO2

Just sign in and you should be directed back to the Umbraco BackOffice! And if you click your profile button, you will see the linked account options (basically just one: Unlink):

UmbracoBO3

Thats it! Source code after the jump.

Share and Enjoy!

/Y.

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s