Home > Back-end >  Custom parameter with Microsoft Identity Platform and Azure AD B2C - how to add information using th
Custom parameter with Microsoft Identity Platform and Azure AD B2C - how to add information using th

Time:03-16

I'm following this tutorial: https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-web-app-sign-user-overview?tabs=aspnetcore

According to other docs, I can use the 'state' parameter to pass in custom data and this will be returned back to the app once the user is logged in However, OIDC also uses this state param to add its own encoded data to prevent xsite hacking - I cant seem to find the correct place in the middleware to hook into this and add my custom data

There's a similar discussion on this thread: Custom parameter with Microsoft.Owin.Security.OpenIdConnect and AzureAD v 2.0 endpoint but I'm using AddMicrosoftIdentityWebApp whereas they're using UseOpenIdConnectAuthentication and I don't know how to hook into the right place in the middleware to add my custom data then retrieve it when on the return.

I'd like to be able to do something like in the code below - when I set break points state is null outgoing and incoming, however the querystring that redirects the user to Azure has a state param that is filled in by the middleware, but if i do it like this, then I get an infinite redirect loop

public static class ServicesExtensions
    {
        public static void AddMicrosoftIdentityPlatformAuthentication(this IServiceCollection services, IConfigurationSection azureAdConfig)
        {
            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(options =>
                {
                    options.ClientId = azureAdConfig["ClientId"];
                    options.Domain = azureAdConfig["Domain"];
                    options.Instance = azureAdConfig["Instance"];
                    options.CallbackPath = azureAdConfig["CallbackPath"];
                    options.SignUpSignInPolicyId = azureAdConfig["SignUpSignInPolicyId"];
                    options.Events.OnRedirectToIdentityProvider = context =>
                    {
                        //todo- ideally we want to be able to add a returnurl to the state parameter and read it back
                        //however the state param is maintained auto and used to prevent xsite attacks so we can just add our info
                        //as we get an infinite loop back to az b2 - see https://blogs.aaddevsup.xyz/2019/11/state-parameter-in-mvc-application/

                        //save the url of the page that prompted the login request
                        //var queryString = context.HttpContext.Request.QueryString.HasValue
                        //    ? context.HttpContext.Request.QueryString.Value
                        //    : string.Empty;
                        //if (queryString == null) return Task.CompletedTask;
                        //var queryStringParameters = HttpUtility.ParseQueryString(queryString);
                        //context.ProtocolMessage.State = queryStringParameters["returnUrl"]?.Replace("~", "");

                        return Task.CompletedTask;
                    };
                    options.Events.OnMessageReceived = context =>
                    {
                        //todo read returnurl from state
                        //redirect to the stored url returned
                        //var returnUrl = context.ProtocolMessage.State;
                        //context.HandleResponse();
                        //context.Response.Redirect(returnUrl);

                        return Task.CompletedTask;
                    };
                    options.Events.OnSignedOutCallbackRedirect = context =>
                    {
                        context.HttpContext.Response.Redirect(context.Options.SignedOutRedirectUri);
                        context.HandleResponse();
                        return Task.CompletedTask;
                    };
                });
        }
    }

CodePudding user response:

Use AAD B2C docs

https://docs.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application-options#support-advanced-scenarios

Then follow this https://docs.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application-options#pass-an-id-token-hint

Just change context.ProtocolMessage.IdTokenHint to context.ProtocolMessage.State.

CodePudding user response:

Ok, I've got it to work Couple of things I discovered, but not sure why - I managed to pass a guid in state and get it back without getting that infinite loop, so I thought I'd try the url again but base64 encode, which worked. I did have some further issues which was solved by doing the following:

        public static void AddMicrosoftIdentityPlatformAuthentication(this IServiceCollection services, IConfigurationSection azureAdConfig)
        {
            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(options =>
                {
                    options.ClientId = azureAdConfig["ClientId"];
                    options.Domain = azureAdConfig["Domain"];
                    options.Instance = azureAdConfig["Instance"];
                    options.CallbackPath = azureAdConfig["CallbackPath"];
                    options.SignUpSignInPolicyId = azureAdConfig["SignUpSignInPolicyId"];
                    options.Events.OnRedirectToIdentityProvider = context =>
                    {
                        var queryString = context.HttpContext.Request.QueryString.HasValue
                            ? context.HttpContext.Request.QueryString.Value
                            : string.Empty;
                        if (queryString == null) return Task.CompletedTask;
                        var queryStringParameters = HttpUtility.ParseQueryString(queryString);
                        var encodedData = queryStringParameters["returnUrl"]?.Replace("~", "").Base64Encode();
                        context.ProtocolMessage.State = encodedData;
                        return Task.CompletedTask;
                    };
                    options.Events.OnTokenValidated = context =>
                    {
                        var url = context.ProtocolMessage.State.Base64Decode();
                        var claims = new List<Claim> { new Claim("returnUrl", url) };
                        var appIdentity = new ClaimsIdentity(claims);
                        context.Principal?.AddIdentity(appIdentity);
                        return Task.CompletedTask;
                    };
                    options.Events.OnTicketReceived  = context =>
                    {
                        if (context.Principal == null) return Task.CompletedTask;
                        var url = context.Principal.FindFirst("returnUrl")?.Value;
                        context.ReturnUri = url;
                        return Task.CompletedTask;
                    };
                    options.Events.OnSignedOutCallbackRedirect = context =>
                    {
                        context.HttpContext.Response.Redirect(context.Options.SignedOutRedirectUri);
                        context.HandleResponse();
                        return Task.CompletedTask;
                    };
                });
        }

so now it all works nicely - the user can hit a protected route, get bumped to the login then redirected on return

Maybe not the most elegant soln and I'm not 100% sure of the how or why, but it works

  • Related