Copied to clipboard

Flag this post as spam?

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


  • Ed Parry 64 posts 252 karma points c-trib
    Mar 12, 2018 @ 16:32
    Ed Parry
    0

    Azure AD for user login - unable to link existing accounts

    Hi all,

    I'm looking to integrate Azure AD for user login to the back-office (v76.1). I've followed the instructions in this blog post and I'm using the IdentityExtensions and IdentityExtensions.AzureActiveDirectory packages.

    I'm able to see that I connect through to Azure AD and it returns some data back to Umbraco. It also creates a new user if there isn't one there already, but it always then fails with A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider.

    When I then login normally and press the Link your Active Directory account, it goes off to Azure AD and returns back, but the accounts aren't linked. The setup is following the guidance here so I'm not sure what's missing?

    Nothing ever gets written to the umbracoExternalLogin table either - I presume that should be used here?

  • Ed Parry 64 posts 252 karma points c-trib
    Mar 12, 2018 @ 16:33
    Ed Parry
    0

    For reference, here's my current ConfigureBackOfficeAzureActiveDirectoryAuth method:

            public static void ConfigureBackOfficeAzureActiveDirectoryAuth(this IAppBuilder app,
            string tenant, string clientId, string postLoginRedirectUri, Guid issuerId,
            string caption = "Active Directory", string style = "btn-microsoft", string icon = "fa-windows") {
            var authority = string.Format(
                CultureInfo.InvariantCulture,
                "https://login.windows.net/{0}",
                tenant);
    
            var adOptions = new OpenIdConnectAuthenticationOptions {
                SignInAsAuthenticationType = Umbraco.Core.Constants.Security.BackOfficeExternalAuthenticationType,
                ClientId = clientId,
                Authority = authority,
                RedirectUri = postLoginRedirectUri,
                AuthenticationMode = AuthenticationMode.Passive,
                Notifications = new OpenIdConnectAuthenticationNotifications {
                    AuthorizationCodeReceived = async context =>
                    {
                        var userService = ApplicationContext.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);
    
                        if (user == null) {
                            var writerUserType = userService.GetUserTypeByName("writer");
                            user = userService.CreateUserWithIdentity(email, email, writerUserType);
                        }
    
                        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);
                        }
                    }
                }
            };
    
            adOptions.ForUmbracoBackOffice(style, icon);
            adOptions.Caption = caption;
            adOptions.SetExternalSignInAutoLinkOptions(new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true));
    
            //Need to set the auth type as the issuer path
            adOptions.AuthenticationType = string.Format(
                CultureInfo.InvariantCulture,
                "https://sts.windows.net/{0}/",
                issuerId);
    
            app.UseOpenIdConnectAuthentication(adOptions);
        }
    
  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Mar 19, 2018 @ 23:16
    Shannon Deminick
    1

    You'll need to first diagnose the simpler case. Before adding any auto-linking options and custom auto-linking code, you need to first ensure that you can actually link your OAuth provider to an existing account.

    So first thing to do is:

    • Remove the callback AuthorizationCodeReceived
    • Remove the call to SetExternalSignInAutoLinkOptions

    So that you have the simplest code possible (least moving parts). You need to make sure this works before doing any auto-linking.

    So now create a new back office user and log in as them, then try to link their account to your AD OAuth provider in the dialog when you click on your Avatar in the back office.

    If this doesn't work then some debugging needs to happen. Because Azure AD is a bit odd, it uses the 'issuer path' as the auth type ... though it's been a very long time since I last tested this, I'm unsure if this has changed. I suspect that if this is not working that perhaps the auth-type isn't matching. In which case, you can have a look at the DB table contents: umbracoExternalLogin to see what information was recorded and take note of the data that was entered for the user you just tried to link your AD too, that might yield why this isn't working.

  • Ed Parry 64 posts 252 karma points c-trib
    Mar 20, 2018 @ 09:50
    Ed Parry
    1

    Hi Shannon,

    Thanks for this. I've stripped the code back to the basics, but we're getting the same result. If I log in as a local user and then try to link accounts, I just get redirected back to /umbraco with the option still there to link. If I then log out and try to log in with Azure AD, I get the message The requested provider has not been linked to an account. At no point is anything written to the umbracoExternalLogin table either.

    We can pick up a response from Azure AD with the expected results, but this then seems to disappear within Umbraco. There is an empty UMB_EXTLOGIN cookie being set, and there's a second cookie that is only there for a split second before the login redirect to Microsoft occurs, but neither persist with any data.

    I've also tried changing the AuthenticationType to Cookie and Bearer with no luck (same responses), and the current type is using the https://sts.windows.net/{tenantId}. Where would you recommend I look from here?

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Mar 21, 2018 @ 01:01
    Shannon Deminick
    1

    I'm not sure why you are trying to change AuthenticationType to cookie or bearer, that is definitely not going to work ;)

    What information are you passing into this method for

    In many cases people have just forgotten an ending '/' on the login redirect Url or other character mistakes with the above since they need to be exact.

    The next step is that you'll need to debug the callbacks. Sorry but this is the only way to know what data is coming and going into your app which is something that you'll need to do to try to figure out where the problem lies. To do that you can use the Notifications on the OpenIdConnectAuthenticationOptions, then set callbacks for each available callback method and put a breakpoint there to see what is happening.

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Mar 21, 2018 @ 03:15
    Shannon Deminick
    0

    Also make sure you check your logs! I think I've just discovered an issue with the linking when the auth type doesn't match, when this happens you should get a warning in your log file like: Could not find external authentication provider registered... but when that happens the code should terminate, instead it continues which will result in a ysod.

    Also make sure you have your chome dev tools open when doing this to see if you are getting any 500 responses in your network tab

  • Ed Parry 64 posts 252 karma points c-trib
    Mar 21, 2018 @ 10:04
    Ed Parry
    0

    Thanks again, Shannon!

    I've double checked the values to make sure they match. If one of them doesn't, we pick up errors and/or the Microsoft callbacks don't complete.

    Checking the logs, we only see one message: WARN Umbraco.Core.Logging.OwinLogger - Event Id: 0, state: The state field is missing or invalid. I'm trying to trace this back but I can't pick up where it's directly coming from - I get the impression state isn't directly related to Azure AD, but I'm not 100% sure?

    For the callbacks, they get hit in this order at the moment, and don't appear to list any errors (and do include my details back from Azure AD): MessageReceived, RedirectToIdentityProvider, MessageReceived, SecurityTokenReceived, SecurityTokenValidated, AuthorizationCodeReceived. Is there something specific I should look for in one of these that can further point me to the issue?

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Mar 26, 2018 @ 02:40
    Shannon Deminick
    0

    That warning message is interesting. The string "The state field is missing or invalid" doesn't come from Umbraco, but it does come from Microsoft's OpenId middleware, see http://sourcebrowser.io/Browse/aspnet/AspNetKatana/src/Microsoft.Owin.Security.OpenIdConnect/OpenidConnectAuthenticationHandler.cs#245

    Based on this code, you should be able to see what information is missing. Just above this line of code you can see a call back is executed that you can bind to MessageReceived which gets passed the messageReceivedNotification which contains the ProtocolMessage which contains a State property which is the thing that is checked in the call to GetPropertiesFromState. That method is here: http://sourcebrowser.io/Browse/aspnet/AspNetKatana/src/Microsoft.Owin.Security.OpenIdConnect/OpenidConnectAuthenticationHandler.cs#547

    ... should give you a place to start looking.

    As a side note, almost all of the ASP.NET Identity implementation and debugging I've done has been by reading through source code. This source code site is nice, but otherwise you can go directly to https://github.com/aspnet/AspNetKatana

  • Kristian Andersen 8 posts 99 karma points
    May 11, 2018 @ 12:08
    Kristian Andersen
    0

    Hi Ed,

    Did you manage to resolve this issue? Im getting the same error...

  • Wayne 15 posts 72 karma points
    Aug 02, 2018 @ 10:52
    Wayne
    0

    Ive been battling various issues getting User authentication via AD on 7.11 for a few days.

    I also had the same error in the logs and thanks to this post Ive now got it working.

    I was using the default Configuration from UmbracoStandardOwinStartup + SetExternalSignInAutoLinkOptions (to attempt to get the auto linking).

    public static void ConfigureBackOfficeAzureActiveDirectoryAuth(this IAppBuilder app,
            string tenant, string clientId, string postLoginRedirectUri, Guid issuerId,
            string caption = "Active Directory", string style = "btn-microsoft", string icon = "fa-windows")
        {
            var authority = string.Format(
                CultureInfo.InvariantCulture, "https://login.windows.net/{0}", tenant);
    
            var adOptions = new OpenIdConnectAuthenticationOptions
            {
                SignInAsAuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType,
                ClientId = clientId,
                Authority = authority,
            };
            adOptions.SetExternalSignInAutoLinkOptions(new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true, defaultUserGroups: null));
            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}/", issuerId);
            app.UseOpenIdConnectAuthentication(adOptions);
        }
    

    I then tried adding the following, which was detailed in this post

    RedirectUri = postLoginRedirectUri,
                AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
                Notifications = new OpenIdConnectAuthenticationNotifications .....
    

    and I managed to get to the point of it failing on obtaining the email addr. In another post somewhere on the web, I read that the email was in a prop called 'upn'

    var email = context.JwtSecurityToken.Claims.First(x => x.Type == "upn").Value;
    

    So i now have AD auth and auto linking, so thanks for the help - although this does not solve reason behind

    Umbraco.Core.Logging.OwinLogger - Event Id: 0, state: The state field is missing or invalid.

  • Rivki Aizen 1 post 71 karma points
    Aug 08, 2018 @ 15:08
    Rivki Aizen
    0

    you can add these rows and it will solve your problem:

          var adOptions = new OpenIdConnectAuthenticationOptions
          {
            SignInAsAuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType,
            ClientId = clientId,
            Authority = authority,
            AuthenticationMode = AuthenticationMode.Passive,
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
              SecurityTokenValidated = async notification =>
              {
                var id = notification.AuthenticationTicket.Identity;
    
                // we want to keep first name, last name, subject and roles
                var givenName = id.FindFirst(ClaimTypes.GivenName);
                if(givenName==null) givenName= id.FindFirst("name");
                var familyName = id.FindFirst(ClaimTypes.Surname);
                if (familyName == null) familyName= id.FindFirst("name");
                var email = id.FindFirst(ClaimTypes.Email);
                if (email == null) email= id.FindFirst(ClaimTypes.Upn);
                var roles = id.FindAll(ClaimTypes.Role);
    
                // create new identity and set name and role claim type
                var nid = new ClaimsIdentity(
                  id.AuthenticationType,
                  ClaimTypes.GivenName,
                  ClaimTypes.Role);
    
                nid.AddClaim(givenName);
                nid.AddClaim(familyName);
                nid.AddClaims(roles);
                nid.AddClaim(id.FindFirst(ClaimTypes.NameIdentifier));
                var emailclaim = new Claim(ClaimTypes.Email, email.Value);
                nid.AddClaim(emailclaim);
    
                notification.AuthenticationTicket = new AuthenticationTicket(
                  nid,
                  notification.AuthenticationTicket.Properties);
              },
            }
          };
    
    

    another way:

     
    
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
              AuthorizationCodeReceived = async context =>
              {
                var userService = ApplicationContext.Current.Services.UserService;
    
                var email = context.JwtSecurityToken.Claims.First(x => x.Type == "upn" || 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
    

    Rivki Aizen

Please Sign in or register to post replies

Write your reply to:

Draft