Copied to clipboard

Flag this post as spam?

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


  • Rob 4 posts 74 karma points
    Mar 01, 2022 @ 11:43
    Rob
    0

    Persist and refresh Access Token after external Microsoft B2C login

    Hi,

    https://our.umbraco.com/documentation/reference/security/external-login-providers/ https://our.umbraco.com/documentation/reference/security/auto-linking/

    I've adapted the documentation here to work with Azure AD B2C. I can login successfully, and the auto linking works as intended. However, my website requires the use of a Azure AD protected API. This means that I need to use the Azure AD access token (that I get from the callback from Microsoft AD) as a Bearer for a subsequent REST API call.

    How do I keep hold of the access token? And, if the token expires after ~15 mins, how would I refresh that token? Thanks

  • Paul Johnson 18 posts 109 karma points
    Mar 03, 2022 @ 11:47
    Paul Johnson
    0

    Hey Rob,

    Couple of questions

    • Is this for backoffice users or for members?
    • Are you intending to call the protected API on behalf of each "user" or is it the application itself which needs to communicate with the protected API.
  • Paul Johnson 18 posts 109 karma points
    Mar 03, 2022 @ 11:55
    Paul Johnson
    0

    The user/member tokens can be retieved on behalf of a user/member with IExternalLoginWithKeyService.GetExternalLoginTokens (although this may not be working correctly for members just yet).

    They are stored in the database in dbo.umbracoExternalLoginToken, if you check that table do you have access token and refresh token records?

  • Paul Johnson 18 posts 109 karma points
    Mar 03, 2022 @ 16:52
    Paul Johnson
    0

    I have been playing around with this today to ensure it's working for members as well as users, so I have code snippets if it's useful (sorry it's for Auth0 instead of Azure AD but setup should be moderatey similar, possibly a little more verbose, I opted for quick and easy).

    public class DemoComposer : IComposer
    {
        public void Compose(IUmbracoBuilder builder)
        {
            builder.AddBackOfficeExternalLogins(logins =>
            {
                logins.AddBackOfficeLogin(
                    cfg =>
                    {
                        cfg.AddAuth0WebAppAuthentication("Umbraco.Auth0",
                                opt =>
                                {
                                    opt.CallbackPath = "/umbraco-auth0-signin";
                                    opt.Domain = builder.Config["Auth0:Domain"];
                                    opt.ClientId = builder.Config["Auth0:ClientId"];
                                    opt.ClientSecret = builder.Config["Auth0:ClientSecret"];
                                    opt.Scope = "openid profile email offline_access";
                                })
                            .WithAccessToken(opt =>
                            {
                                opt.Audience = builder.Config["Auth0:DemoProtectedApi"];
                            });
                    });
            });
        }
    }
    
    public class ExposeMyTokensController : Controller
    {
        private readonly IUserService _userService;
        private readonly IExternalLoginWithKeyService _externalLogins;
    
        public ExposeMyTokensController(IUserService userService, IExternalLoginWithKeyService externalLogins)
        {
            _userService = userService;
            _externalLogins = externalLogins;
        }
    
        [HttpGet("demo")]
        public IActionResult BadIdea()
        {
            var me = _userService.GetUserById(-1);
            var myTokens = _externalLogins.GetExternalLoginTokens(me.Key);
            return Ok(myTokens);
        }
    }
    

    If I link my backoffice user with an external account and hit the demo endpoint above I can see all my tokens rendered

    enter image description here

  • Rob 4 posts 74 karma points
    Mar 04, 2022 @ 16:21
    Rob
    0

    Hi Paul.

    Thanks for your help. I'm trying the equivalent for members, but getting nothing here:

    var me = _memberService.GetById(int.Parse(User.Identity.GetUserId()));
    var myTokens = _externalLogins.GetExternalLoginTokens(me.Key);
    

    'myTokens' is null.

    I'm not sure if this is Members vs Users thing, or it's something specific to Azure AD.

    I noticed in the Umbraco external login examples for Azure AD, there is a 'options.SaveTokens = true'. This throws an NotImplemented exception if I set this to true, however. Might be a red herring, but it sounds related.. the Umbraco source makes it looks it's been omitted by design, but I can't think why..

  • Paul Johnson 18 posts 109 karma points
    Mar 11, 2022 @ 15:49
    Paul Johnson
    0

    If you're interested the rc for 9.4 is out and this should hopefully work there if you're up for being a tester.

    https://www.nuget.org/packages/Umbraco.Cms/9.4.0-rc

  • Paul Johnson 18 posts 109 karma points
    Mar 04, 2022 @ 20:58
    Paul Johnson
    0

    Yep this won't work for members just yet, fixed for 9.4 in https://github.com/umbraco/Umbraco-CMS/pull/12093

  • Rob 4 posts 74 karma points
    Mar 25, 2022 @ 09:49
    Rob
    0

    Hi Paul,

    Thanks for adding that fix! Adding 'saveTokens = true' no longer throws an exception. I think I'm still doing something wrong, however.

    _externalLogins.GetExternalLoginTokens(me.Key);
    

    is now an empty list rather than null. The 'umbracoExternalLoginToken' table is empty in the db.

    Here's my config:

    public static class MicrosoftMemberAuthenticationExtensions
    {
        public static IUmbracoBuilder AddMicrosoftB2CMemberAuthentication(this IUmbracoBuilder builder)
        {
            builder.Services.ConfigureOptions<MicrosoftB2CMemberExternalLoginProviderOptions>();
    
            builder.AddMemberExternalLogins(logins =>
            {
                logins.AddMemberLogin(
                    memberAuthenticationBuilder =>
                    {
                        memberAuthenticationBuilder.AddOpenIdConnect(
                            // The scheme must be set with this method to work for the back office
                            memberAuthenticationBuilder.SchemeForMembers(MicrosoftB2CMemberExternalLoginProviderOptions.SchemeName),
                            options =>
                            {
                                options.Events = new OpenIdConnectEvents
                                {
                                    OnTokenValidated = context =>
                                    {
                                        //Mapping 'emails' to the required 'email'
                                        var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
                                        claimsIdentity.AddClaim(new Claim("email", context.Principal.FindFirst("emails").Value));
                                        return Task.FromResult(0);
                                    }
                                };
    
                                // use cookies
                                options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    
                                // pass configured options along
                                options.Authority = "xxxxxxxxxxxxxx";
                                options.ClientId = "xxxxxxxxxxxxxx";
                                options.ClientSecret = "xxxxxxxxxxxxxx";
                                options.CallbackPath = "/signin-oidc";
    
                                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                                {
                                    ValidateIssuer = false,
                                    SaveSigninToken = true
                                };
    
                                // Use the authorization code flow
                                options.ResponseType = OpenIdConnectResponseType.Code;
                                options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
    
                                options.RequireHttpsMetadata = true;
                                options.GetClaimsFromUserInfoEndpoint = true;
    
                                options.Scope.Add("xxxxxxxxxxxxxx");
                                options.UsePkce = true;
    
                                options.SaveTokens = true;  //now allowed in 9.4
                            });
                    });
            });
            return builder;
        }
    }
    

    Logging in and autolinking works perfectly still.. It's just the retrieval of the external login token afterwards that's still alluding me. Any ideas?

    Thanks

  • Rob 4 posts 74 karma points
    Mar 29, 2022 @ 10:41
    Rob
    0

    Ah - ignore me. It's working (sort of!) if I removed all the members pre-9.4 then re-registered.

    The issue I'm having now is that the 'access token' only gets set on the INITIAL auto-link of the member. Any access token used to login subsequently is not getting saved into the 'umbracoExternalLoginTokens' table. I'm not sure if there is a setting I'd need to change somewhere, or if this a bug. Paul - I don't have a 'refresh token' at all, like you do in your screenshot - not sure if that's something to do with it.

    enter image description here

    Is there a mechanism for manually refreshing access tokens built into Umbraco's external login providers?

  • Paul Johnson 18 posts 109 karma points
    Mar 29, 2022 @ 16:29
    Paul Johnson
    0

    You won't get a refresh token unless the scope offline_access is requested.

    Once you have that you should be able to exchange the refresh token for a new access token.

Please Sign in or register to post replies

Write your reply to:

Draft