ASP.NET Core 5: OpenIDConnect breaking default/root route - asp.net-core

I have an ASP.NET Core 5 MVC app, with the default/root route set like this inside PageController:
[AllowAnonymous]
[Route("/")]
public IActionResult __Home(int? parent)
{
return View();
}
This worked fine until I added OpenIdConnect authentication. After that, the root (/) page no longer routes to __Home in the PageController, it just returns a blank page. All other pages route just fine.
When I comment out this:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C");
then / works again, so I know it's something to do with the authentication. As you can see, I have added [AllowAnonymous] to that action.
I have this in my startup:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
});
Any ideas on how to fix this? I know it's unconventional to have the default/root route in a weird controller/action like that, but there are reasons for it, so I'm hoping it can still work.
More Info:
I found that if I move app.UseEndpoints above app.UseAuthentication, then the home page shows. After logging in (with B2C), however, it goes into an infinite loop (i.e. the authentication token doesn't stick?).
EDIT: My Startup.cs class
using Blank.Models;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
namespace Blank
{
public class Startup
{
private readonly AppSettings appSettings = null;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
this.appSettings = new AppSettings();
this.Configuration.Bind(this.appSettings);
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C");
services.AddSession();
services.Configure<OpenIdConnectOptions>(Configuration.GetSection("AzureAdB2C"));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.Configure<AppSettings>(this.Configuration);
services.AddEntityFrameworkSqlServer().AddDbContext<BlankDBContext>(
Options => Options.UseSqlServer(Microsoft.Extensions.Configuration.ConfigurationExtensions.GetConnectionString(this.Configuration, "BlankDatabase"))
);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Page}/{action=Index}/{id?}");
});
}
}
}
Edit 2
I think that app.UseAuthentication() is breaking/returning the blank page, because when I put the following code before app.UseAuthentication() I get something on the home page, and if it's after then blank:
app.Use(async (context, next) =>
{
var endpoint = context.GetEndpoint();
if (endpoint != null)
{
await context.Response.WriteAsync("<html> Endpoint :" + endpoint.DisplayName + " <br>");
if (endpoint is RouteEndpoint routeEndpoint)
{
await context.Response.WriteAsync("RoutePattern :" + routeEndpoint.RoutePattern.RawText + " <br>");
}
}
else
{
await context.Response.WriteAsync("End point is null");
}
await context.Response.WriteAsync("</html>");
await next();
});
So perhaps it has to do with my authentication? Here's my appsettings.json:
"AzureAdB2C": {
"Instance": "https://abc.b2clogin.com",
"Domain": "abc.onmicrosoft.com",
"ClientId": "62...f1",
"TenantId": "7e...ae",
"SignUpSignInPolicyId": "B2C_1_SUSI",
"SignedOutCallbackPath": "/"
},

Turns out the problem was this in my appsettings.json:
"SignedOutCallbackPath": "/"
Removing this fixed the problem, and the home page now loads correctly.

Related

cors error in authentication type windows - visual studio 2019

I started a .NET CORE 5 project
And I chose windows type authentication
type authentication image
And this type of project gives me a CORS error on the client side (react)
But if instead of windows I choose none, I won't get an error
This is the call from the client side:
const res = await fetch(`https://localhost:44373/weatherforecast`)
I need this type of project because I want to use AD authentication
I tried adding it to the fetch call:
const res = await fetch(`https://localhost:44300/weatherforecast`,{credentials: 'include'})
and change the STARTUP:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebApplication3
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to
add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCors(options =>
{
options.AddPolicy("MyMyAllowCredentialsPolicy",
policy =>
{
policy.WithOrigins("https://localhost:44300")
.AllowCredentials();
});
});
}
// This method gets called by the runtime. Use this method to
configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseCors();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
I also created a new .net 6 web api project with windows authentication. I also had a front end project.
This is my code and it worked for me. In my Program.cs, I added Cors policy and others are generated by default.
using Microsoft.AspNetCore.Authentication.Negotiate;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("mypolicy",
policy =>
{
policy.WithOrigins("http://localhost:8848").AllowCredentials();
//.AllowCredentials();
});
});
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("mypolicy");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
And I used ajax to send the request, missing withCredentials will lead to 401 error:
$("#btn2").click(function(event) {
$.ajax({
url: "https://localhost:7272/WeatherForecast",
type: "get",
xhrFields: {
withCredentials: true
},
success: function(data) {
alert(data);
console.info(data);
}
})
});
Another point which needs to notice is that, when opening the client website in the private mode, it will still meet 401 while everything worked well when open the website in the normal window. That is because private mode doesn't contain auth information I think.
For Post request, still worked.
Post with parameters??

Enabling CORS in ASP.NET Core 6

I have an ASP.NET Core 6 Web API that has a react front end.
I would like to use named policies to enable cors so I have in my startup
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("MyPolicy",
builder => builder.WithOrigins("http://localhost:3000/"));
});
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// Shows UseCors with named policy.
app.UseCors("MyPolicy");
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Now I can call it in the controller like this:
[EnableCors("MyPolicy")]
public class ProductsController : ControllerBase
This worked in .NET Core 3.1 but not in .NET 6.
What is the right way of doing this in an ASP.NET Core 6 Web API?
Changing the program CS to acomodate CORS policy still doesnt work
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using var scope = host.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<StoreContext>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
try
{
context.Database.Migrate();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
logger.LogError(ex, "Problem migrating data");
}
var builder = WebApplication.CreateBuilder(args);
var MyAllowSpecificOrigins = "AnotherPolicy";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<StoreContext>(options =>
{
options.UseSqlite(connectionString);
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
try
{
app.Run();
}
catch (Exception)
{
throw;
}
//host.Run();
}
This gives me the same error
Access to fetch at 'http://localhost:5000/api/Products' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
in program.cs file:
app.UseCors(
options => options.WithOrigins("*").AllowAnyMethod().AllowAnyHeader()
);
As noted in the documentation:
The specified URL must not contain a trailing slash (/). If the URL terminates with /, the comparison returns false and no header is returned.

Visual studio's ASP.NET core with angular template always returns 401

I'm using Microsoft Visual Studio Community 2019 Version 16.10.2. I created an ASP.net core 5 project from their template "ASP.net core with Angular" with Authentication checked. But Every time I request an API Method marked with the attribute [Authorize] I get 401.
Their template was supposed to work with no problem but I got some trouble logging in. I fixed them but, the only problem I can't figure out how to fix is the 401 code returned ASP.
I read the doc several times but I could not find any useful information.
The thing is: I can create accounts and login with no problem. When I login, the server returns the token. From the Angular app it shows the name of the logged-in user. But when to access an [Authorize] controller it returns 404.
Here is the link of the project I pushed to github for better debugging.
Here is the startup code:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(op =>
{
// I added these lines because I was getting the error "Error: Client Angular_identity_test is not allowed access to scope Angular."
op.Clients[0].AllowedScopes = new List<string> { "Angular", "identity", "testAPI" };
op.ApiScopes = new Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ApiScopeCollection(new List<ApiScope> { new ApiScope("Angular"), new ApiScope("identity"), new ApiScope("testAPI") });
op.ApiResources.AddApiResource("Angular", conf => conf.WithScopes(new string[] { "Angular", "identity", "testAPI" }));
op.ApiResources.AddApiResource("identity", conf => conf.WithScopes(new string[] { "Angular", "identity", "testAPI" }));
op.ApiResources.AddApiResource("testAPI", conf => conf.WithScopes(new string[] { "Angular", "identity", "testAPI" }));
});
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
// In public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
// I added this line because some people say that fixed their problems.
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
In the appsettings.json there is:
"IdentityServer": {
"Clients": {
"Angular_identity_test": {
"Profile": "IdentityServerSPA"
}
}
}
And the controller that keeps return 401:
[Authorize()]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
So you say, But when to access a [Authorize] controller it returns 404. use this below code, instead of [Authorize].
[Authorize(AuthenticationSchemes = "Bearer")]
It will resolve your issue.
UPDATE
Use this below code and i assume that your Token is place of appsettings.development.json file. like "TokenKey":"super secret key",
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters=new TokenValidationParameters
{
ValidateIssuerSigningKey=true,
IssuerSigningKey=new SymmetricSecurityKey( Encoding.UTF8.GetBytes(config["TokenKey"]) ),
ValidateIssuer=false,
ValidateAudience=false
};
});
Hope it will resolve your issue.
This will be a proxy issue. In your CLIENTAPP go to the proxy.conf.js and add your controllers route into the PROXY_CONFIG->context array (like "/weatherforecast" is). Then restart your application. You don't need to change .NET Core middleware or services.
UPDATE
I added this DI BASE_URL into my service constructor and it works fine now.
constructor(private http: HttpClient, #Inject('BASE_URL') private bUrl: string) {
this.baseUrl = bUrl + "api/v1/items"; // my endpoint
}

Setting Up Social Authentication in ASP.NET Core 2.0

I'm setting up social login in an ASP.NET Core 2.0 application without using Identity.
I simply want to authenticate the user through Facebook, Google and LinkedIn and receive their info. I handle storing user info myself.
Here's what I've done so far which is giving me the following error:
No authentication handler is configured to handle the scheme: facebook
Here's the Startup.cs file changes:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Added these lines for cookie and Facebook authentication
services.AddAuthentication("MyCookieAuthenticationScheme")
.AddCookie(options => {
options.AccessDeniedPath = "/Account/Forbidden/";
options.LoginPath = "/Account/Login/";
})
.AddFacebook(facebookOptions =>
{
facebookOptions.AppId = "1234567890";
facebookOptions.AppSecret = "1234567890";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// Added this line
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
I then have this action method where I send the user to determine the provider we're using for authenticaiton e.g. Facebook, Google, etc. This code came from my ASP.NET Core 1.1 app which is working fine.
[AllowAnonymous]
public async Task ExternalLogin(string provider, string returnUrl)
{
var properties = new AuthenticationProperties
{
RedirectUri = "Login/Callback"
};
// Add returnUrl to properties -- if applicable
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
properties.Items.Add("returnUrl", returnUrl);
// The ASP.NET Core 1.1 version of this line was
// await HttpContext.Authentication.ChallengeAsync(provider, properties);
await HttpContext.ChallengeAsync(provider, properties);
return;
}
I'm getting the error message when I hit the ChallangeAsync line.
What am I doing wrong?
No authentication handler is configured to handle the scheme: facebook
Scheme names are case-sensitive. Use provider=Facebook instead of provider=facebook and it should work.

.Net Core 2.0 Web API OpenIddict Authorization: redirecting to index instead of returning json data

So, the problem is that when I use the AuthorizeAttribute on top of my api controller, it stops working the expected way.
When I call a getAllUsers action, instead of returning the users in json format, the Identity somehow redirects to index.html and then I get a json parser error in my Angular client app, because html is not valid json data that can be parsed.
This started to happen after upgrading to Asp.Net Core 2.0.
I think that perhaps I have to change something in my Startup.cs or Program.cs. But I can't figure out what.
I have followed the Refresh Token Sample on OpenIddict for the new Core 2.0, and everything seems to be ok.
So here is my code...
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => {
options.UseSqlServer(Configuration.GetConnectionString("LocalDB"))
.UseOpenIddict();
});
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
services.AddScoped<IManadRepository, ManadRepository>();
services.AddScoped<IManadRubricaRepository, ManadRubricaRepository>();
services.AddScoped<IManadSistemaRepository, ManadSistemaRepository>();
services.AddScoped<IRestituicaoRepository, RestituicaoRepository>();
services.AddTransient<ApplicationDbSeedData>();
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.User.RequireUniqueEmail = true;
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/connect/token");
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
if (!_env.IsProduction())
options.DisableHttpsRequirement();
});
// Add framework services.
services.AddMvc();
services.AddAuthentication()
.AddOAuthValidation();
services.AddAuthorization();
services.AddTransient<IMailSender, MailjetSender>();
services.AddScoped<IManadParser, ManadParser>();
}
public void Configure(IApplicationBuilder app, ApplicationDbSeedData dbDataSeeder)
{
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
Mapper.Initialize(cfg =>
{
cfg.AddProfile<AutoMapperProfile>();
});
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
dbDataSeeder.EnsureSeedData().Wait();
}
UsersController.cs
[Route("api/[controller]")]
[Authorize]
public class UsersController : Controller
{
[HttpGet]
[Authorize(Roles = "Administrador")]
public IActionResult GetAllUsers()
{
try
{
var result = _repository.GetAllUsers();
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError($"Failed to get all users: {ex}");
return BadRequest(ex.Message);
}
}
}
If I put a breakpoint in the GetAllUsers method, it never gets hitted. Somehow because of authorization, the application redirects to index.html before.
Program.cs
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
By the way, authentication is working. I am able to get the tokens, but unable to authorize the controller access.
Solved it. Just needed some bit of configuration just like I thought. Just add DefaultAuthenticateScheme option like this:
services.AddAuthentication(options => options.DefaultAuthenticateScheme = OAuthValidationDefaults.AuthenticationScheme)
.AddOAuthValidation();
After adding this, the controller started to work correctly, resulting json data and not index.html.