microsoft-identity-web
microsoft-identity-web copied to clipboard
ConsentHandler HandleException ignores scheme and no option to pass scheme
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.
-
HandleException should have an overload for passing the scheme just like you have for
CallApiForUserAsync
. There you haveoptions.AcquireTokenOptions.AuthenticationOptionsName
. See here in sample repo. -
AccountController in MIW UI is ignoring the default scheme from the Program.cs but always uses
OpenIdConnectDefaults.AuthenticationScheme
. - Maybe a bit off topic but it could solve a lot of these scheme issues. Why isn't the
.AuthScheme
property added to theClaimsIdentity
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.
- Create new Web API project and secure with Azure AD
- Create new Blazor Server Web App and secure with Azure AD. Use the option to add downstream API.
- Upgrade all package to latest version.
- Migrate the code following the documentation for calling downstream api as described here and here.
- Change
openIdConnectScheme
parameter inAddMicrosoftIdentityWebApp
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();
- Start both projects and login.
- Go to page that calls the web api. Everything is working!
- Don't signout and close the browser or stop debugging.
- Start the app again. The user should be still signed in.
- Navigate to page that calls the web api. TokenCache is empty so
ConsentHandler.HandleException(ex)
will trigger. - 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
- Scheme option should be added to
ConsentHandler.HandleException(ex); // Missing option to pass authScheme....
for multi auth. - Challenge in Accountroller should use default values from Program.cs.
- Scheme of signin in should be added to ClaimsIdentity so we can use it for Multi auth.
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").
@jmprieur thoughts on when to take this one?