Copied to clipboard

Flag this post as spam?

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


  • Ben Palmer 180 posts 866 karma points c-trib
    Feb 10, 2023 @ 16:27
    Ben Palmer
    0

    How to use Azure Active Directory claims to set User group

    I'm using Azure Active Directory to provide authentication to the Backoffice on my website running Umbraco version 11.0.

    This is working nicely and I can log in but I want to improve the experience by using app roles within Azure to manage the user's group within Umbraco.

    My Azure setup

    I've created an App Registration within Azure with the following configuration:

    • Added a Redirection URI:
      • URI: https://localhost:44391/umbraco-signin-microsoft/
      • Enabled Access tokens (used for implicit flows)
      • Enabled ID tokens (used for implicit and hybrid flows)
      • Supported account types: Accounts in this organizational directory only (Umble only - Single tenant)
      • Added App Roles
      • Administrator
      • Editor

    enter image description here

    In Enterprise Applications, I've also added the App Roles above to my users:

    enter image description here

    My code

    Login Provider

    namespace Example.Api.Features.Authentication.Extensions;
    
    public static class UmbracoBuilderExtensions
    {
        public static IUmbracoBuilder ConfigureAuthentication(this IUmbracoBuilder builder)
        {
            builder.Services.ConfigureOptions<OpenIdConnectBackOfficeExternalLoginProviderOptions>();
    
            builder.AddBackOfficeExternalLogins(logins =>
            {
                const string schema = MicrosoftAccountDefaults.AuthenticationScheme;
    
                logins.AddBackOfficeLogin(
                    backOfficeAuthenticationBuilder =>
                    {
                        backOfficeAuthenticationBuilder.AddMicrosoftAccount(
                            // the scheme must be set with this method to work for the back office
                            backOfficeAuthenticationBuilder.SchemeForBackOffice(OpenIdConnectBackOfficeExternalLoginProviderOptions.SchemeName) ?? string.Empty,
                            options =>
                            {
                                //By default this is '/signin-microsoft' but it needs to be changed to this
                                options.CallbackPath = "/umbraco-signin-microsoft/";
                                //Obtained from the AZURE AD B2C WEB APP
                                options.ClientId = "CLIENT_ID";
                                //Obtained from the AZURE AD B2C WEB APP
                                options.ClientSecret = "CLIENT_SECRET";
                                options.TokenEndpoint = $"https://login.microsoftonline.com/TENANT/oauth2/v2.0/token";
                                options.AuthorizationEndpoint = $"https://login.microsoftonline.com/TENANT/oauth2/v2.0/authorize";
                            });
                    });
            });
    
            return builder;
        }
    }
    

    Auto-linking accounts

    namespace Example.Api.Features.Configuration;
    
    public class OpenIdConnectBackOfficeExternalLoginProviderOptions : IConfigureNamedOptions<BackOfficeExternalLoginProviderOptions>
    {
        public const string SchemeName = "OpenIdConnect";
    
        public void Configure(string name, BackOfficeExternalLoginProviderOptions options)
        {
            if (name != "Umbraco." + SchemeName)
            {
                return;
            }
    
            Configure(options);
        }
    
        public void Configure(BackOfficeExternalLoginProviderOptions options)
        {
            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 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
    
                    autoLinkUser.IsApproved = true;
                },
                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 = true;
    
            // 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 = true;
        }
    }
    

    In this file, I'd ideally do as the comment says and i.e. Modify the user's groups based on the Claims returned in the externalLogin info.

    Also registered in my Startup file

    services.AddUmbraco(_env, _config)
                .AddBackOffice()
                .AddWebsite()
                .AddComposers()
                .ConfigureAuthentication()
                .Build();
    

    Current state of play is that I can login just fine but if I debug externalInfo, there's nothing in there about the users having either the Administrator or Editor App Role as configured above.

    My gut feeling is that I'm missing something with the Azure Active Directory setup but I've tried a few different configurations and can't seem to get the App Roles to come back.

    Thanks,

    Ben

  • Ben Palmer 180 posts 866 karma points c-trib
    Feb 16, 2023 @ 17:37
    Ben Palmer
    100

    To solve this, I ended up swapping out the AddMicrosoftAccount AuthenticationBuilder in favour of AddOpenIdConnect. This appears to respect the claims in the tokens.

    This is the code I am now using in the ConfigureAuthentication method.

    public static IUmbracoBuilder ConfigureAuthentication(this IUmbracoBuilder builder)
    {
        // Register OpenIdConnectBackOfficeExternalLoginProviderOptions here rather than require it in startup
        builder.Services.ConfigureOptions<OpenIdConnectBackOfficeExternalLoginProviderOptions>();
    
        builder.AddBackOfficeExternalLogins(logins =>
        {
            logins.AddBackOfficeLogin(
                backOfficeAuthenticationBuilder =>
                {
                    backOfficeAuthenticationBuilder.AddOpenIdConnect(
                        // The scheme must be set with this method to work for the back office
                        backOfficeAuthenticationBuilder.SchemeForBackOffice(OpenIdConnectBackOfficeExternalLoginProviderOptions.SchemeName),
                        options =>
                        {
                            options.CallbackPath = "/umbraco-signin-microsoft/";
                            // use cookies
                            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                            // pass configured options along
                            options.Authority = "https://login.microsoftonline.com/{tenantId}/v2.0";
                            options.ClientId = "{clientId}";
                            options.ClientSecret = "{clientSecret}";
                            // Use the authorization code flow
                            options.ResponseType = OpenIdConnectResponseType.Code;
                            options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
                            // map claims
                            options.TokenValidationParameters.NameClaimType = "name";
                            options.TokenValidationParameters.RoleClaimType = "role";
    
                            options.RequireHttpsMetadata = true;
                            options.GetClaimsFromUserInfoEndpoint = true;
                            options.SaveTokens = true;
                            options.UsePkce = true;
    
                            options.Scope.Add("email");
                        });
                });
        });
        return builder;
    }
    
  • HugoBecerra 2 posts 22 karma points
    Jun 08, 2023 @ 11:47
    HugoBecerra
    0

    Hi Ben,

    Thanks a lot for sharing your workaround. Can you perhaps show how it looks your OpenIdConnectBackOfficeExternalLoginProviderOptions class now? I would really love to see how you update the user groups in there.

    Thanks.

  • Chris Evans 137 posts 353 karma points c-trib
    Oct 16, 2023 @ 23:58
    Chris Evans
    0

    I would also love to see the code you've used to set the user groups based on their roles in the Active Directory B2C tenancy.

    I cannot find any examples online, in the Umbraco docs, or Skrift articles / blogs that actually show the code for this...

  • Thomas 319 posts 606 karma points c-trib
    Oct 03, 2024 @ 09:37
    Thomas
    0

    I do it like this :)

    I know it's a old post. But maybe someone can use it :=)

    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
        var name = loginInfo.Principal.Claims.Where(c => c.Type == "name").FirstOrDefault();
        var roles = loginInfo.Principal.Claims.Where(c => c.Type == ClaimTypes.Role).ToList();
        if (roles.Count > 0)
        {
            user.Roles.Clear();
            foreach (var role in roles)
            {
                var umbracoRole = GetUmbracoRoleWithMostPrivileges(role.Value);
                user.AddRole(umbracoRole);
            }
        }
        user.Name = name.Value;
        return true; //returns a boolean indicating if sign in should continue or not.
    }
    
  • Debbinson 2 posts 22 karma points
    Oct 21, 2024 @ 13:15
    Debbinson
    0

    Hello, I am trying to implement this solution but I get asked about authorization from the Administrator. Even if I did set up users inside Azure Group. How can I solve it? Is there a workaround to avoid it or should I request Administrator to disable Approval Required screen?

Please Sign in or register to post replies

Write your reply to:

Draft