Copied to clipboard

Flag this post as spam?

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


  • James 6 posts 76 karma points
    Feb 25, 2021 @ 12:16
    James
    0

    Double Log in screen when logging in to Umbraco using Azure AD

    Hi All

    We've implemented Login using Azure AD and autolink users with an organisation AD account to their Umbraco users. Also implemented the callback OnExternalLogin where we assign the users to groups depending on the role claim received from Azure. This all works fine.

    One thing I am having trouble with is the following.

    Scenario:

    User does not exist in umbraco database

    User is already logged in to Azure AD (via the Azure Portal) so cookies and session data are present on the machine

    User enters umbraco url. Prior to being presented with the log in screen, the user is auto linked and the user is created in the db.

    User is presented with the log in screen

    Sign in with Active Directory

    User clicks, and OnExternalLogin callback is triggered assigning the user to groups depending on the role claims in Azure.

    User is redirected to the following url: /umbraco/#/login/false?returnPath=%252Fcontent and presented with the log in screen again

    Clicking the sign in button again signs them in and are able to access the content page !!

    This only happens if the user has not been previously autolinked and created in the umbraco db. If the user follows the above procedure and is autolinked, closes the browser and was to clear all sessions, cookies etc and retry the process, the user is logged in after signing in with their AD account on the first attempt.

    Could someone shed any light as to why this "double log in" might be happening for first time users?

    Thanks in advance

  • Dave Woestenborghs 3504 posts 12133 karma points MVP 8x admin c-trib
    Feb 25, 2021 @ 15:10
    Dave Woestenborghs
    0

    Hi James,

    I have almost the same working on Umbraco 8.8

    What version of Umbraco are you working on and could you show some code ?

    Dave

  • James 6 posts 76 karma points
    Feb 26, 2021 @ 09:57
    James
    0

    Hi Dave....thanks for reaching out...we're running version 8.11.1

    this is some of our code snippets Im currently running tests locally which is why the redirect is pointing to localhost:

    UmbracoStandardOwinStartup:

    public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
    {
        /// <summary>
        /// Configures the back office authentication for Umbraco
        /// </summary>
        /// <param name="app"></param>
        protected override void ConfigureUmbracoAuthentication(IAppBuilder app)
        {
            // Must call the base implementation to configure the default back office authentication config.
            base.ConfigureUmbracoAuthentication(app);
    
    
            var adConfig = ServiceLocator.ServiceProvider.GetService<IConfiguration>().GetSection(CatalogueServiceAd.ConfigPath).Get<CatalogueServiceAd>();
            if (adConfig.Enabled)
            {
                app.ConfigureBackOfficeAzureActiveDirectoryAuth(adConfig);
            }
            app.UseUmbracoBackOfficeTokenAuth(new BackOfficeAuthServerProviderOptions
            {
                CorsPolicy = new CorsPolicy
                {
                    AllowAnyHeader = true,
                    AllowAnyMethod = true,
                    AllowAnyOrigin = true
                }
            });
        }
    }
    

    and our UmbracoADAuthExtension class:

    using KLP.Catalogue.Core.Configurations;
    using Microsoft.AspNet.Identity.Owin;
    using Microsoft.Owin.Security.OpenIdConnect;
    using Owin;
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using KLP.Core;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using Umbraco.Core;
    using Umbraco.Core.Composing;
    using Umbraco.Core.Models.Identity;
    using Umbraco.Core.Models.Membership;
    using Umbraco.Web.Security;
    
    
    namespace KLP.Catalogue.Umbraco
    {
       public static class UmbracoADAuthExtensions
       {
    
          public static void ConfigureBackOfficeAzureActiveDirectoryAuth(this IAppBuilder app, CatalogueServiceAd adConfig,
            string caption = "Active Directory", string style = "btn-microsoft", string icon = "fa-windows")
        {
            var adOptions = new OpenIdConnectAuthenticationOptions
            {
                SignInAsAuthenticationType = "UmbracoExternalCookie",
                ClientId = adConfig.ClientId.GetTenantValueOrDefault(),
                Authority = adConfig.Authority.GetTenantValueOrDefault(),
                RedirectUri = "https://localhost:44317/umbraco/"
            };
    
            adOptions.ForUmbracoBackOffice(style, icon);
            adOptions.Caption = caption;
            //Need to set the auth tyep as the issuer path
            adOptions.AuthenticationType = string.Format(
                CultureInfo.InvariantCulture,
                "https://sts.windows.net/{0}/",
                adConfig.TenantId.GetTenantValueOrDefault());
    
            app.UseOpenIdConnectAuthentication(adOptions);
    
            adOptions.SetBackOfficeExternalLoginProviderOptions(
                new BackOfficeExternalLoginProviderOptions
                {
                    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[] { "" }.ToArray(),
    
                        // 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 = (_user, _externalLogin) =>
                        //{
                        //    // 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
                        //},
    
                        // Optional callback
                        OnExternalLogin = (_user, _externalLogin) =>
                        {
                            // 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 SyncAzureADRolesToUmbracoGroups(_user, _externalLogin);
                        }
                    },
    
                    // 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.
                    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
                    AutoRedirectLoginToExternalProvider = true
                });
        }
    
        private static bool SyncAzureADRolesToUmbracoGroups(BackOfficeIdentityUser backOfficeIdentityUser, ExternalLoginInfo externalLogin)
        {
            // flow the external claim to the umbraco
            // back office identity cookie authentication
            var extClaims = externalLogin
                .ExternalIdentity
                .Claims
                .Where(x => x.Type == externalLogin.ExternalIdentity.RoleClaimType)
                .Select(x => x.Value)
                .ToHashSet(StringComparer.InvariantCultureIgnoreCase);
    
            var umbracoGroupsToDelete = new HashSet<string>(); // Groups users should no longer be assigned to
    
            // Identify group access that should be deleted or that should remain
            foreach (var userGroup in backOfficeIdentityUser.Groups)
            {
                if (extClaims.Contains(userGroup.Alias))
                {
                    extClaims.Remove(userGroup.Alias);
                }
                else
                {
                    umbracoGroupsToDelete.Add(userGroup.Alias);
                }
            }
            var userService = Current.Services.UserService;
            var user = userService.GetUsersById(backOfficeIdentityUser.Id).FirstOrDefault();
            if (user == null)
            {
                throw new Exception($"User can login but can't find the user in Umbraco. User Id: {backOfficeIdentityUser.Id}, Email: {backOfficeIdentityUser.Email}");
            }
    
            var logger = ServiceLocator.ServiceProvider.GetService<ILogger<Startup>>();
    
            // Remove user from invalid user group
            foreach (var groupToDelete in umbracoGroupsToDelete)
            {
                user.RemoveGroup(groupToDelete);
            }
    
            // Add roles
            foreach (var groupToAdd in extClaims)
            {
                if (userService.GetUserGroupByAlias(groupToAdd) is IReadOnlyUserGroup userGroup)
                {
                    user.AddGroup(userGroup);
                }
                else
                {
                    logger.LogWarning($"Group '{groupToAdd}' couldn't be found in Umbraco");
                }
            }
    
            // Commit changes to user
            userService.Save(user);
            return true;
        }
    
      }
    }
    
  • Mehmet Avcı 55 posts 240 karma points
    Feb 26, 2021 @ 13:27
    Mehmet Avcı
    0

    Hi,

    I believe when your user info gets overridden in your case. You might want to add following setting in your web.config/appSettings section.

    <add key="WEBSITE_AUTH_DISABLE_IDENTITY_FLOW" value="true" />
    

    There is more info regarding this one at shazwazza's blog.

  • James 6 posts 76 karma points
    Feb 26, 2021 @ 14:27
    James
    0

    Hi, thanks for posting Mehmet

    I have already come across that post and tried it out.

    I have added the app setting and also added the configuration in the startup file as suggested in the post, however still no success

  • James 6 posts 76 karma points
    Apr 13, 2021 @ 09:21
    James
    0

    finally managed to solve this issue. Using the above code, in our UmbracoADAuthExtension class, I modified the OpenIdConnectAuthenticationOptions as follows:

    var adOptions = new OpenIdConnectAuthenticationOptions
            {
                SignInAsAuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType,
                ClientId = adConfig.ClientId.GetTenantValueOrDefault(),
                Authority = adConfig.Authority.GetTenantValueOrDefault(),
                RedirectUri = adConfig.RedirectUrl,
                AuthenticationMode = AuthenticationMode.Passive,
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    AuthorizationCodeReceived = async context =>
                    {
                        var userService = Current.Services.UserService;
    
                        var email = context.JwtSecurityToken.Claims.First(x => x.Type == "email").Value;
                        var issuer = context.JwtSecurityToken.Claims.First(x => x.Type == "iss").Value;
                        var providerKey = context.JwtSecurityToken.Claims.First(x => x.Type == "sub").Value;
                        var name = context.JwtSecurityToken.Claims.First(x => x.Type == "name").Value;
                        var userManager = context.OwinContext.GetUserManager<BackOfficeUserManager>();
    
                        var user = userService.GetByEmail(email) ?? userService.CreateUserWithIdentity(email, email);
    
                        var identity = await userManager.FindByEmailAsync(email);
                        if (identity.Logins.All(x => x.ProviderKey != providerKey))
                        {
                            identity.Logins.Add(new IdentityUserLogin(issuer, providerKey, user.Id));
                            identity.Name = name;
                            await userManager.UpdateAsync(identity);
                        }
                    }
                }
            };
    

    Hope this helps out anyone that encounters the same issue

Please Sign in or register to post replies

Write your reply to:

Draft