I am trying to crack the code to use an Azure AD multi-tenant app to sign in to a ASP.NET Core app that uses ASP.NET Core Identity. It's a pretty standard setup but it doesn't seem to accept Microsoft identity. Nothing breaks or no errors, but the Authorize
attribute indicates the current user isn't authenticated.
void configureCookieAuthOptions(CookieAuthenticationOptions options)
{
options.Cookie.IsEssential = true;
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.SlidingExpiration = true;
options.LoginPath = "/Account/Login";
}
void configureJwtBearerOptions(JwtBearerOptions options)
{
options.TokenValidationParameters = new TokenValidationParameters
{
// snip
};
}
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationIdentityDbContext>()
.AddClaimsPrincipalFactory<ApplicationUserClaimsPrincipalFactory>()
.AddUserManager<ApplicationUserManager>()
.AddUserStore<ApplicationUserStore>()
.AddDefaultTokenProviders();
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(configureCookieAuthOptions)
.AddJwtBearer(configureJwtBearerOptions);
services.AddAuthorization();
When I add AddMicrosoftIdentityWebApp
to the mix, it all works great, except that the Authorize
attribute returns false when I attempt to access a protected resource. In fact, it looks like there is no identity in the context:
void configureMicrosoftIdentityOptions(MicrosoftIdentityOptions microsoftOptions)
{
microsoftOptions.TenantId = "common";
microsoftOptions.Instance = "https://login.microsoftonline.com/";
microsoftOptions.Domain = config["Authentication:Microsoft:Domain"];
microsoftOptions.ClientId = config["Authentication:Microsoft:ClientId"];
microsoftOptions.ClientSecret = config["Authentication:Microsoft:ClientSecret"];
//microsoftOptions.SignInScheme = IdentityConstants.ExternalScheme;
microsoftOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
microsoftOptions.Events = new OpenIdConnectEvents
{
OnTokenValidated = async context =>
{
var _userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
var _claimsIdentityFactory = context.HttpContext.RequestServices.GetRequiredService<IUserClaimsPrincipalFactory<ApplicationUser>>();
ClaimsIdentity claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
ApplicationUser user = await _userManager.FindByEmailAsync(claimsIdentity.Name);
if (user == null)
{
context.Fail("Not authorized.");
return;
}
var id = await _claimsIdentityFactory.CreateAsync(user);
context.Principal.AddIdentity(id.Identity as ClaimsIdentity);
claimsIdentity.AddClaims(id.Claims);
context.Success();
}
};
};
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(configureCookieAuthOptions)
.AddJwtBearer(configureJwtBearerOptions)
.AddMicrosoftIdentityWebApp(configureMicrosoftIdentityOptions, cookieScheme: null); // <-- Added this line
I've tried all sorts of combinations and variations, like changing the default schemes, using different schemes, configure the scheme to external, etc. to no avail.
For the sake of completeness, here's the code that is invoked when I call the external login button:
[AllowAnonymous]
public IActionResult SignInExternal([FromRoute] string scheme, [FromQuery] string redirectUri)
{
scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
string redirect = !string.IsNullOrEmpty(redirectUri) && Url.IsLocalUrl(redirectUri) ? redirectUri : Url.Content("Account/SignInSuccess");
return Challenge(new AuthenticationProperties { RedirectUri = redirect }, scheme);
}
As far as I can see, it's exactly the same story when I swap out AddMicrosoftIdentityWebApp
for AddOpenIdConnect
. It's a similar story when I use .AddGitHub("Github", options => {})
, so there's certainly something missing in my setup.
I am over my head here, so I was wondering what I am missing or understanding incorrectly here.
Not completely sure about the legitimacy of the fix, but I was able to get authenticated with Azure AD, GitHub, and others by setting the OpenIdConnectOptions
' SignInScheme
to IdentityConstants.ApplicationScheme
.
internal static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder authenticationBuilder, IConfiguration config)
{
void configureMicrosoftIdentityOptions(OpenIdConnectOptions options)
{
// ...
options.SignInScheme = IdentityConstants.ApplicationScheme;
// ...
return authenticationBuilder.AddOpenIdConnect("AzureAD", configureMicrosoftIdentityOptions);
}