Reputation: 31
I am trying to set up a multi-tenant OpenId authentication with AzureAD on a web page built on the basic .NET Core 2.2 + React-project template from Visual Studio 2019. Core 2.2 because the authentication middleware on 3.0 did not fire at all with any configuration, this seems to be a common problem and the documentation on Core 3.x authentication is scarce and sometimes even contradictory. I think I have tried everything and I am at loss now.
Here the authentication middleware seems to kick in correctly according to the server output when the API is called:
From javascript:
// Tried also without custom headers and trying to make the middleware handle the thing by itself
fetch('api/SampleData/WeatherForecasts', {
method: "get",
headers: new Headers({
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "true"
})
})
Server output:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:44363/api/SampleData/WeatherForecasts
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint '********.Controllers.SampleDataController.WeatherForecasts (******)'
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
Route matched with {action = "WeatherForecasts", controller = "SampleData", area = "", page = ""}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[******.Controllers.SampleDataController+WeatherForecast] WeatherForecasts() on controller ******.Controllers.SampleDataController (*********).
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Executing ChallengeResult with authentication schemes ().
info: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[12]
AuthenticationScheme: AzureADOpenID was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action *****.Controllers.SampleDataController.WeatherForecasts (*******) in 439.0343ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint '******.Controllers.SampleDataController.WeatherForecasts (*******)'
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 464.9224ms 302
But i get always the cors error in the browser when returning a HTTP 200 response:
Access to fetch at 'https://login.microsoftonline.com/common/oauth2/authorize?client_id=**********************&redirect_uri=https%3A%2F%2Flocalhost%3A44363%2Fsignin-oidc&response_type=id_token&scope=openid%20profile&response_mode=form_post&nonce=**************************************************&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.3.0.0' (redirected from 'https://localhost:44363/api/SampleData/WeatherForecasts') from origin 'https://localhost:44363' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I can open the login page manually but the browser fails in redirection because of this. I even published the application as Azure Web App and still get the same CORS error. I think I've set up everything correctly in Startup.cs but nothing seems to work. Then I even set up the cors-policy from Azure Web App to allow * origins and then allow the relevant origins but the problem persists.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSpaStaticFiles(configuration => {
configuration.RootPath = "ClientApp/build";
});
services.Configure<CookiePolicyOptions>(options => {
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options)).AddCookie();
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = false,
};
options.Events = new OpenIdConnectEvents {
OnTicketReceived = context => {
return Task.CompletedTask;
},
OnAuthenticationFailed = context => {
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
}
};
});
/*
Tried also with this
services.AddCors(setup => {
setup.AddPolicy("corspolicy", policy => {
policy
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
*/
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
} else {
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication(); // Tried putting this and .UseCors to different places
app.UseStaticFiles();
app.UseSpaStaticFiles();
//app.UseCors("corspolicy");
app.UseCors(policy => {
policy
.AllowAnyOrigin() // Tried with hardcoded origins
.AllowAnyMethod()
.AllowCredentials() // Tried without this also
.AllowAnyHeader();
});
app.UseHttpsRedirection();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa => {
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment()) {
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
The controller:
[Authorize]
[EnableCors] // Tried with named policy and without EnableCors
[ApiController] // Tried without this
[Route("api/[controller]")]
public class SampleDataController : Controller
{
private static string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet("[action]")]
public IEnumerable<WeatherForecast> WeatherForecasts()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast {
DateFormatted = DateTime.Now.AddDays(index).ToString("d"),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
});
}
// Nonrelevant things omitted
}
Upvotes: 3
Views: 2317
Reputation: 41
After long investigation the short answer to your question is "you can't achieve it with ajax request". You actually need your browser to go to that controller where you are making you "challenge" request. This post here is explaining everything: https://www.blinkingcaret.com/2018/10/10/sign-in-with-an-external-login-provider-in-an-angular-application-served-by-asp-net-core/
Upvotes: 2