Umbraco backoffice SSO with OpenID Connect

Scott Brady
Scott Brady
Umbraco

A common Umbraco use case is to use your existing SSO solution to log into the Umbraco backoffice. For example, this could be using your custom IdentityServer solution or your company’s Azure AD. Thankfully, Umbraco 9 has significant improvements for its backoffice that enable you to use existing ASP.NET Core authentication handlers for backoffice users.

In this article, you’ll see how to use any OpenID Connect identity provider, such as IdentityServer, Azure AD, Auth0, or Okta, to authenticate users in Umbraco’s backoffice.

Getting started with Umbraco

To get started, you’ll need a working instance of Umbraco 9 or above, running on ASP.NET Core. The simplest way to do this is to use the Umbraco templates. I found it easiest to use a template with SQL CE, which will allow you to dotnet run out of the box:

dotnet new umbraco --SqlCe

Give the site a run, set up your admin user, and allow Umbraco to complete installation.

The Umbraco backoffice login screen showing a username and password field

Configuring an OpenID Connect provider for the Umbraco backoffice

Let’s start by installing your required dependency, Microsoft’s OpenID Connect authentication handler:

dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect

In your Startup file, you’ll see Umbraco has its own set of registrations. By default, this will set up Umbraco’s backoffice, the backoffice user store, and the backoffice authentication handlers (a cookie). You’ll need to build on this to add an external identity provider.

Thankfully, Umbraco comes with an extension method to add new ASP.NET Core authentication handlers straight onto the Umbraco backoffice with AddBackOfficeExternalLogins.

AddBackOfficeExternalLogins gives you access to BackOfficeAuthenticationBuilder, an extended version of ASP.NET’s AuthenticationBuilder. This means that you get access to your usual authentication handler registrations such as AddOpenIdConnect, AddGoogle, AddFacebook, and AddMicrosoftAccount, just like any other ASP.NET Core website.


// "Umbraco.oidc"
var scheme = $"{Constants.Security.BackOfficeExternalAuthenticationTypePrefix}oidc";

services.AddUmbraco(_env, _config)
    .AddBackOffice()
    .AddBackOfficeExternalLogins(loginsBuilder =>
        loginsBuilder.AddBackOfficeLogin(authBuilder =>
            authBuilder.AddOpenIdConnect(scheme, "OpenID Connect", options =>
            {
                // using IdentityServer's demo installation
                options.Authority = "https://demo.identityserver.io";
                options.ClientId = "interactive.confidential";
                options.ClientSecret = "secret";

                options.CallbackPath = "/signin-oidc";                
                options.ResponseType = "code";
                options.ResponseMode = "query";
                options.UsePkce = true;

                // get user identity
                options.Scope.Add("email");
                options.GetClaimsFromUserInfoEndpoint = true;
            })))
    .AddWebsite()
    .AddComposers()
    .Build();

Above is the ideal OAuth 2.1/OpenID Connect 1.0 configuration using the authorization code flow and PKCE.

Note that the authentication scheme must start with “Umbraco.”, otherwise you’ll get the following exception:

InvalidOperationException: The authenticationScheme is not prefixed with Umbraco. The scheme must be created with a call to the method SchemeForBackOffice

The above registrations will result in a new login option on Umbraco’s backoffice login screen and the ability for backoffice admins to link their existing local account to an account in the external identity provider.

The Umbraco backoffice login screen now showing a button saying 'Sign In with OpenID Connect' and also username and password field The Umbraco backoffice user self-service sidebar showing a new section called 'External login providers' with a new button saying 'Link your OpenID Connect account'

To change the icon and styling of the external auth button, you can pass in another action into AddBackOfficeLogin, this time configuring BackOfficeExternalLoginProviderOptions.

services.AddUmbraco(_env, _config)
    .AddBackOffice()
    .AddBackOfficeExternalLogins(loginsBuilder =>
        loginsBuilder.AddBackOfficeLogin(authBuilder =>
            authBuilder.AddOpenIdConnect("Umbraco.oidc", "OpenID Connect", options =>
            {
                // existing config
            }),
            providerOptions => providerOptions.Icon = "fa fa-openid"))
    .AddWebsite()
    .AddComposers()
    .Build();

You can also use these options to enable and configure the auto-linking of accounts or even disable local username & password authentication altogether. For more information on auto-linking, check out the Umbraco documentation; for disabling local authentication, keep scrolling.

IdentityServer & Umbraco

If you’re integrating with IdentityServer, here’s the client configuration for the above Umbraco website. Check out the sample code for a working implementation.

new Client
{
    ClientId = "umbraco-backoffice",
    ClientSecrets = { new Secret("secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Code,
    RequirePkce = true,
    RedirectUris = { "https://localhost:44362/signin-oidc" },
    AllowedScopes = { "openid", "profile", "email" }
}

Azure AD & Umbraco (or any other authentication scheme)

Since Umbraco exposes the default ASP.NET Core authentication builder, you can use this approach with any supported ASP.NET Core remote authentication handler. For instance, you can replicate the above code to work with Azure AD, like so:

.AddBackOfficeExternalLogins(loginsBuilder =>
    loginsBuilder.AddBackOfficeLogin(authBuilder =>
            authBuilder.AddOpenIdConnect(scheme, "Azure AD", options =>
            {
                // your Azure AD tenant
                options.Authority = "https://login.microsoftonline.com/ff191596-4ffd-4c77-93e7-6167a5756569/v2.0";
                options.ClientId = "bf5597e9-593f-4d90-8fab-bcfa2fda581c";
                options.ClientSecret = "W2O7Q~ECiZGGgODaCj2O1o94iyn0-VFIaZFHM";
    
                options.CallbackPath = "/signin-oidc";
                options.ResponseType = "code";
                options.ResponseMode = "query";
                options.UsePkce = true;
                
                // get user identity
                options.Scope.Add("email");
                options.GetClaimsFromUserInfoEndpoint = true;
            })))

I’m trying to stress that there are no further Umbraco specifics to adding an external identity provider. So, if you keep your Googling to just “ASP.NET Core”, you’ll find a lot more articles and samples for your authentication method of choice. It would even work out of the box with SAML!

Disabling backoffice local login

Now that you’ve integrated Umbraco’s backoffice with your external identity provider, you’ll likely want to disable the default local username and password authentication. After all, if you’ve enabled SSO with your corporate directory that enforces strong MFA, I doubt you’ll want to leave an authentication bypass hanging around. Thankfully, this is also available out of the box with Umbraco 9 onwards.

To disable local login, you can set the DenyLocalLogin option on BackOfficeExternalLoginProviderOptions to true:

providerOptions => providerOptions.DenyLocalLogin = true

You can even take things a step further by disabling the login page entirely, redirecting directly to the identity provider:

providerOptions =>
{
    providerOptions.DenyLocalLogin = true;
    providerOptions.AutoRedirectLoginToExternalProvider = true;
}

Source Code

You can find completed sample code in my GitHub samples repository. This sample includes a demo IdentityServer implementation for you to use as your identity provider. Just remember to keep an eye on those redirect URIs (they’re case, and therefore port, sensitive)!

Content is licensed under CC BY 4.0. Remember, don't copy and paste code written by strangers on the internet.