Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • kevinewig 3 posts 75 karma points
    Nov 05, 2021 @ 03:07
    kevinewig
    0

    Backoffice Azure AD Login: Unable to Compile Startup.cs

    Hi

    I am trying to have users log into the Umbraco 9 BackOffice with Azure AD. I tried to follow the post () however I have been unable to compile the code there.

    This is the error I am getting:

    Error CS1061 'BackOfficeExternalLoginProviderOptions' does not contain a definition for 'SchemeForBackOffice' and no accessible extension method 'SchemeForBackOffice' accepting a first argument of type 'BackOfficeExternalLoginProviderOptions' could be found (are you missing a using directive or an assembly reference?)

    It seems to have problems compiling auth.SchemeForBackOffice

    I have .Net Core 5.

    The libraries in my project are:

    • Microsoft.AspNetCore.Authentication.Abstractions, Version="2.2.0"
    • Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="5.0.11
    • Microsoft.AspNetCore.Authentication.Core" Version="2.2.0"
    • Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.11"
    • Microsoft.ICU.ICU4C.Runtime" Version="68.2.0.6"
    • Microsoft.Identity.Web" Version="1.19.0"

    My Startup.cs file looks like this:

    using System;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Umbraco.Cms.Core.DependencyInjection;
    using Umbraco.Cms.Web.BackOffice.Security;
    using Umbraco.Extensions;
    using Microsoft.Identity.Web;
    
    namespace MyProject
    {
        public class Startup
        {
            private readonly IWebHostEnvironment _env;
            private readonly IConfiguration _config;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="Startup" /> class.
            /// </summary>
            /// <param name="webHostEnvironment">The web hosting environment.</param>
            /// <param name="config">The configuration.</param>
            /// <remarks>
            /// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337
            /// </remarks>
            public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config)
            {
                _env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
                _config = config ?? throw new ArgumentNullException(nameof(config));
            }
    
            /// <summary>
            /// Configures the services.
            /// </summary>
            /// <param name="services">The services.</param>
            /// <remarks>
            /// This method gets called by the runtime. Use this method to add services to the container.
            /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
            /// </remarks>
            public void ConfigureServices(IServiceCollection services)
            {
    #pragma warning disable IDE0022 // Use expression body for methods
                services.AddUmbraco(_env, _config)
                    .AddBackOffice()
                    .AddWebsite()
                    .AddComposers()
                    .AddBackOfficeExternalLogins(extLoginBuilder =>
                    {
                        var extLoginOpts = new ExternalSignInAutoLinkOptions(
                            autoLinkExternalAccount: true,
                            defaultUserGroups: new[] { "admin" },
                            defaultCulture: "en-US",
                            allowManualLinking: true)
                        {
                            OnExternalLogin = (user, loginInfo) =>
                            {
                                return true;
                            },
                        };
    
                        var loginProviderOptions = new BackOfficeExternalLoginProviderOptions(
                            "btn-microsoft",
                            "fa-windows",
                            extLoginOpts,
                            autoRedirectLoginToExternalProvider: false);
    
                        extLoginBuilder.AddBackOfficeLogin(
                            loginProviderOptions,
                            auth =>
                            {
                                var azAdConfig = _config.GetSection("AzureAd");
    
                                auth
                    // https://github.com/umbraco/Umbraco-CMS/pull/9470
                    .AddMicrosoftIdentityWebApp(options =>
                                {
                                    options.CallbackPath = "/umbraco-signin-oidc";
                                    options.Instance = "https://login.microsoftonline.com/";
                                    options.TenantId = azAdConfig["TenantId"];
                                    options.ClientId = azAdConfig["ClientId"];
                                    options.SignedOutRedirectUri = "/umbraco";
    
                    // https://github.com/AzureAD/microsoft-identity-web/issues/749
                    //options.ClaimActions.MapJsonKey(ClaimTypes.Email, ClaimConstants.PreferredUserName);
    
                    // Preferred over IClaimsTransformation which runs for every AuthenticateAsync
                    options.Events.OnTokenValidated = ctx =>
                                    {
                                        var username = ctx.Principal?.Claims.FirstOrDefault(c => c.Type == ClaimConstants.PreferredUserName);
                                        if (username != null && ctx.Principal?.Identity is ClaimsIdentity claimsIdentity)
                                        {
                                            claimsIdentity.AddClaim(
                                                new Claim(
                                                    ClaimTypes.Email,
                                                    username.Value
                                                )
                                            );
                                        }
    
                                        return Task.CompletedTask;
                                    };
                                },
                    openIdConnectScheme: auth.SchemeForBackOffice(Constants.AzureAd),
                    cookieScheme: "Fake")
                    ;
                            });
                    })
                    .Build();
    #pragma warning restore IDE0022 // Use expression body for methods
    
            }
    
            /// <summary>
            /// Configures the application.
            /// </summary>
            /// <param name="app">The application builder.</param>
            /// <param name="env">The web hosting environment.</param>
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseUmbraco()
                    .WithMiddleware(u =>
                    {
                        u.UseBackOffice();
                        u.UseWebsite();
                    })
                    .WithEndpoints(u =>
                    {
                        u.UseInstallerEndpoints();
                        u.UseBackOfficeEndpoints();
                        u.UseWebsiteEndpoints();
                    });
            }
        }
    }
    

    I would appreciate any help anyone could provide for me.

    Thank you!

    Regards, Kevin

  • Thomas 319 posts 606 karma points c-trib
    Jan 07, 2022 @ 19:54
    Thomas
    0

    Did you get it to work?

  • kevinewig 3 posts 75 karma points
    Jan 15, 2022 @ 00:41
    kevinewig
    0

    No. I was not able to get it to work.

    I need help authenticating Umbraco 9 with Azure AD. Any examples would be greatly appreciated.

  • Thomas 319 posts 606 karma points c-trib
    Feb 18, 2022 @ 09:26
    Thomas
    0

    Stuck at the same as you..

  • kevinewig 3 posts 75 karma points
    May 03, 2022 @ 14:52
    kevinewig
    2

    I finally got it working.

    • Umbraco version: 9.4.3
    • Framework: .Net 6

    Nuget packages:

    • Microsoft.AspNetCore.Authentication.CopenidConnect (6.0.4)
    • Microsoft.ICU.ICU4.Runtime (68.2.0.9)
    • Umbraco.Cms (9.4.3)

    Here's what I did:

    Step 1: Create a file called OpenIdConnectBackOfficeExternalLoginProviderOptions: (refer to https://our.umbraco.com/documentation/reference/security/auto-linking/)

    using Microsoft.Extensions.Options;
    using Umbraco.Cms.Core;
    using Umbraco.Cms.Web.BackOffice.Security;
    
    namespace MyUmbraco93
    {
        public class OpenIdConnectBackOfficeExternalLoginProviderOptions : IConfigureNamedOptions<BackOfficeExternalLoginProviderOptions>
        {
            public const string SchemeName = $"{Constants.Security.BackOfficeExternalAuthenticationTypePrefix}oidc";
    
            public void Configure(string name, BackOfficeExternalLoginProviderOptions options)
            {
                if (name != SchemeName)
                {
                    return;
                }
    
                Configure(options);
            }
    
            public void Configure(BackOfficeExternalLoginProviderOptions options)
            {
                options.ButtonStyle = "btn-primary";
                options.Icon = "fa fa-cloud";
    
                options.AutoLinkOptions = new ExternalSignInAutoLinkOptions(
                    // must be true for auto-linking to be enabled
                    autoLinkExternalAccount: true,
    
                    // Optionally specify default user group, else
                    // assign in the OnAutoLinking callback
                    // (default is editor)
                    defaultUserGroups: new[] { Constants.Security.EditorGroupAlias },
    
                    // Optionally specify the default culture to create
                    // the user as. If null it will use the default
                    // culture defined in the web.config, or it can
                    // be dynamically assigned in the OnAutoLinking
                    // callback.
    
                    defaultCulture: null,
                    // Optionally you can disable the ability to link/unlink
                    // manually from within the back office. Set this to false
                    // if you don't want the user to unlink from this external
                    // provider.
                    allowManualLinking: false
                )
                {
                    // Optional callback
                    OnAutoLinking = (autoLinkUser, loginInfo) =>
                    {
                        // You can customize the user before it's linked.
                        // i.e. Modify the user's groups based on the Claims returned
                        // in the externalLogin info
                    },
                    OnExternalLogin = (user, loginInfo) =>
                    {
                        // You can customize the user before it's saved whenever they have
                        // logged in with the external provider.
                        // i.e. Sync the user's name based on the Claims returned
                        // in the externalLogin info
    
                        return true; //returns a boolean indicating if sign in should continue or not.
                    }
                };
    
                // Optionally you can disable the ability for users
                // to login with a username/password. If this is set
                // to true, it will disable username/password login
                // even if there are other external login providers installed.
                options.DenyLocalLogin = false;
    
                // Optionally choose to automatically redirect to the
                // external login provider so the user doesn't have
                // to click the login button. This is
                options.AutoRedirectLoginToExternalProvider = false;
            }
        }
    }
    

    Step 2: Create a file called Extensions.cs (refer to https://www.scottbrady91.com/umbraco/backoffice-sso-openid-connect)

    using Microsoft.Extensions.DependencyInjection;
    using Umbraco.Cms.Core;
    using Umbraco.Cms.Core.DependencyInjection;
    using Umbraco.Extensions;
    
    namespace MyUmbraco93
    {
        public static class Extensions
        {
            public static IUmbracoBuilder AddAzureADBackofficeAuthentication(this IUmbracoBuilder builder)
            {
                // Register OpenIdConnectBackOfficeExternalLoginProviderOptions here rather than require it in startup
                builder.Services.ConfigureOptions<OpenIdConnectBackOfficeExternalLoginProviderOptions>();
    
                // Source: https://www.scottbrady91.com/umbraco/backoffice-sso-openid-connect
                var scheme = $"{Constants.Security.BackOfficeExternalAuthenticationTypePrefix}oidc";
                var clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
                var clientSecret = "sssssssssssssssssssssssssssssssssssssssssss=";
    
                builder.AddBackOfficeExternalLogins(loginsBuilder =>
                    loginsBuilder.AddBackOfficeLogin(authBuilder =>
                        authBuilder.AddOpenIdConnect(scheme, "Azure AD", options =>
                        {
                            // Configure Azure AD
                            options.Authority = "https://login.microsoftonline.com/qqqqqqqq-qqqq-qqqq-qqqq-qqqqqqqqqqqq/v2.0";
                            options.ClientId = clientId;
                            options.ClientSecret = clientSecret;
    
                            options.CallbackPath = "/signin-oidc";
                            options.ResponseType = "code";
                            options.ResponseMode = "query";
                            options.UsePkce = true;
    
                            // Get user identity
                            options.Scope.Add("email");
                            options.GetClaimsFromUserInfoEndpoint = true;
                        }))
                    );
                return builder;
            }
        }
    }
    

    Step 3: Configure the ConfigureServices method in your startup.cs file:

    using System;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Umbraco.Cms.Core;
    using Umbraco.Cms.Core.DependencyInjection;
    using Umbraco.Extensions;
    
    namespace MyUmbraco93
    {
        public class Startup
        {
            private readonly IWebHostEnvironment _env;
            private readonly IConfiguration _config;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="Startup" /> class.
            /// </summary>
            /// <param name="webHostEnvironment">The web hosting environment.</param>
            /// <param name="config">The configuration.</param>
            /// <remarks>
            /// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337
            /// </remarks>
            public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config)
            {
                _env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
                _config = config ?? throw new ArgumentNullException(nameof(config));
            }
    
            /// <summary>
            /// Configures the services.
            /// </summary>
            /// <param name="services">The services.</param>
            /// <remarks>
            /// This method gets called by the runtime. Use this method to add services to the container.
            /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
            /// </remarks>
            public void ConfigureServices(IServiceCollection services)
            {
    #pragma warning disable IDE0022 // Use expression body for methods
                services.AddUmbraco(_env, _config)
                .AddBackOffice()
                .AddAzureADBackofficeAuthentication()
                .AddWebsite()
                .AddComposers()
                .Build();
    #pragma warning restore IDE0022 // Use expression body for methods
            }
    
            /// <summary>
            /// Configures the application.
            /// </summary>
            /// <param name="app">The application builder.</param>
            /// <param name="env">The web hosting environment.</param>
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseUmbraco()
                    .WithMiddleware(u =>
                    {
                        u.UseBackOffice();
                        u.UseWebsite();
                    })
                    .WithEndpoints(u =>
                    {
                        u.UseInstallerEndpoints();
                        u.UseBackOfficeEndpoints();
                        u.UseWebsiteEndpoints();
                    });
            }
        }
    }
    

    Notes:

    Autolinking means that your Umbraco login is connected to your Azure AD login. You don't need to type in your user name and password to log in to Umbraco. - When you first set up Umbraco for the first time you might need to disable auto-linking, set everything up and then enable auto-linking.

    I hope this helps someone.

  • This forum is in read-only mode while we transition to the new forum.

    You can continue this topic on the new forum by tapping the "Continue discussion" link below.

Please Sign in or register to post replies