microsoft-identity-web icon indicating copy to clipboard operation
microsoft-identity-web copied to clipboard

ConsentHandler HandleException ignores scheme and no option to pass scheme

Open LockTar opened this issue 1 year ago • 2 comments

Microsoft.Identity.Web Library

Microsoft.Identity.Web

Microsoft.Identity.Web version

2.10.0

Web app

Sign-in users and call web APIs

Web API

Protected web APIs (validating scopes/roles)

Token cache serialization

In-memory caches

Description

We want to create a multiple auth webapp (Azure AD and Azure B2C) but we found that the bug is also in a single auth webapp. I've created a sample repository for reference. When you create a new web application with a downstream API (default Visual Studio 2022 templates) and you pass the OpenIdConnectScheme parameter with a custom value everything is working fine for logging an user and calling a downstream API (or mulitple in my test repo). But when you restart the application, the memory cache is empty so you will get a MsalUiRequiredException as described here.

The code ConsentHandler.HandleException(ex); will challenge the user as you can see here.

protected override async Task OnInitializedAsync()
    {
        try
        {
            await CallApi();
            await CallApi2();
        }
        catch (Exception ex)
        {
            ConsentHandler.HandleException(ex);
        }
    }

The problem is that the ConsentHandler.HandleException(ex); will call the ChallengeUser method without a scheme (that parameter option is missing). So the url is missing the scheme. In Microsoft Identity Web UI, the Challenge Action in AccountController has a route parameter for the scheme. This parameter is always empty so will be set to the default OpenIdConnect on line 85.

In my opinion there are several things missing/wrong.

  1. HandleException should have an overload for passing the scheme just like you have for CallApiForUserAsync. There you have options.AcquireTokenOptions.AuthenticationOptionsName. See here in sample repo.
  2. AccountController in MIW UI is ignoring the default scheme from the Program.cs but always uses OpenIdConnectDefaults.AuthenticationScheme.
  3. Maybe a bit off topic but it could solve a lot of these scheme issues. Why isn't the .AuthScheme property added to the ClaimsIdentity during Signing in and you use that claim everywhere? See my Multi Auth sample where I store it in the ClaimsIdentity and use it to call the API's. I also use it create the correct Signin and Signout urls in LoginDisplay.razor. I store the claim in the Program.cs.

Reproduction steps

Use VS 2022 latest version.

  1. Create new Web API project and secure with Azure AD
  2. Create new Blazor Server Web App and secure with Azure AD. Use the option to add downstream API.
  3. Upgrade all package to latest version.
  4. Migrate the code following the documentation for calling downstream api as described here and here.
  5. Change openIdConnectScheme parameter in AddMicrosoftIdentityWebApp to a custom value.
public const string AadOpenIdConnectScheme = "OpenIdConnectAad"; // Custom scheme name gives problem

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddAuthentication(Program.AadOpenIdConnectScheme) // Set the default scheme name
            .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"), Program.AadOpenIdConnectScheme) // Use a custom scheme name 
            .EnableTokenAcquisitionToCallDownstreamApi()
            .AddDownstreamApi("DownstreamApi1", builder.Configuration.GetSection("DownstreamApi1"))
                .AddInMemoryTokenCaches();

        builder.Services.AddControllersWithViews()
            .AddMicrosoftIdentityUI();
  1. Start both projects and login.
  2. Go to page that calls the web api. Everything is working!
  3. Don't signout and close the browser or stop debugging.
  4. Start the app again. The user should be still signed in.
  5. Navigate to page that calls the web api. TokenCache is empty so ConsentHandler.HandleException(ex) will trigger.
  6. Error page will be displayed saying that no scheme of OpenIdConnect exists in the app. That's correct because we changed that name in Program.cs. It ignores the default. Also no option to pass the scheme in ConsentHandler.HandleException(ex).

Error message

InvalidOperationException: No authentication handler is registered for the scheme 'OpenIdConnect'. The registered schemes are: Cookies, OpenIdConnectAad. Did you forget to call AddAuthentication().AddSomeAuthHandler?

Id Web logs

No response

Relevant code snippets

See sample repo: https://github.com/LockTar/Authn/tree/main/src.

Single auth:

public const string AadOpenIdConnectScheme = "OpenIdConnectAad"; // Custom scheme name gives problem

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddAuthentication(Program.AadOpenIdConnectScheme) // Set the default scheme name
            .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"), Program.AadOpenIdConnectScheme) // Use a custom scheme name 
            .EnableTokenAcquisitionToCallDownstreamApi()
            .AddDownstreamApi("DownstreamApi1", builder.Configuration.GetSection("DownstreamApi1"))
            .AddDownstreamApi("DownstreamApi2", builder.Configuration.GetSection("DownstreamApi2"))
                .AddInMemoryTokenCaches();

        builder.Services.AddControllersWithViews()
            .AddMicrosoftIdentityUI();


Multi auth:

public const string B2cOpenIdConnectScheme = "OpenIdConnectB2c";
    public const string AadOpenIdConnectScheme = "OpenIdConnectAad";

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // As described here: https://github.com/AzureAD/microsoft-identity-web/wiki/multiple-authentication-schemes#cookie-schemes
        var authenticationBuilder = builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

        authenticationBuilder.AddCookie(options =>
        {
            ////options.ExpireTimeSpan = new TimeSpan(7, 0, 0, 0);

            // Default login path to customers because for employees we get a hidden login page
            options.LoginPath = "/MicrosoftIdentity/Account/SignIn/" + B2cOpenIdConnectScheme;

            options.Events = new CookieAuthenticationEvents()
            {
                OnSigningIn = async context =>
                {
                    var claimsIdentity = context.Principal.Identity as ClaimsIdentity;

                    var authScheme = context.Properties.Items.Where(k => k.Key == ".AuthScheme").Single();
                    var authSchemeClaim = new Claim(authScheme.Key, authScheme.Value);
                    claimsIdentity.AddClaim(authSchemeClaim);

                    await Task.CompletedTask;
                }
            };
        });

        builder.Services.AddAuthentication()
            .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"), Program.AadOpenIdConnectScheme, null)
            .EnableTokenAcquisitionToCallDownstreamApi()
            .AddDownstreamApi("DownstreamApi1", builder.Configuration.GetSection("DownstreamApi1"))
            .AddDownstreamApi("DownstreamApi2", builder.Configuration.GetSection("DownstreamApi2"))
                .AddInMemoryTokenCaches();

        builder.Services.AddAuthentication()
            .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C"), Program.B2cOpenIdConnectScheme, null)
            .EnableTokenAcquisitionToCallDownstreamApi()
            .AddDownstreamApi("DownstreamApi1", builder.Configuration.GetSection("DownstreamApi1"))
            .AddDownstreamApi("DownstreamApi2", builder.Configuration.GetSection("DownstreamApi2"))
                .AddInMemoryTokenCaches();

        builder.Services.AddControllersWithViews()
            .AddMicrosoftIdentityUI();

Regression

No response

Expected behavior

  1. Scheme option should be added to ConsentHandler.HandleException(ex); // Missing option to pass authScheme.... for multi auth.
  2. Challenge in Accountroller should use default values from Program.cs.
  3. Scheme of signin in should be added to ClaimsIdentity so we can use it for Multi auth.

LockTar avatar May 09 '23 10:05 LockTar

I've been digging into this a bit more this week. I would love for the ConsentHandler to actually do a Challenge (with AuthenticationProperties) instead of composing a redirect URI. For example, I'd like to remove the prompt (since I have it defaulted to "select_user").

DaleyKD avatar Sep 22 '23 14:09 DaleyKD

@jmprieur thoughts on when to take this one?

jennyf19 avatar Mar 28 '24 02:03 jennyf19