dotnet core multiple auth schemes: authentication handler is not being triggered - authentication

Dotnet Core 3.1. I have two custom authentication schemes and handlers. When controller action has an [Authorize] attribute then both handlers get triggered. When there is no [Authorize] attribute then only the default scheme gets triggered. I cannot put Authorize attribute on the controller action, but still need to get claims set by the second Auth scheme. How can I ensure that not only the default auth handler is triggered?
Here is my simplified setup.cs:
services
.AddAuthentication("Auth1")
.AddScheme<AuthenticationSchemeOptions, Auth1>("Auth1", null)
.AddScheme<AuthenticationSchemeOptions, Auth2>("Auth2", null);
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("Auth1", "Auth2")
.Build();
});
Auth1 and Auth2 handlers are like this (also just for demo):
public class Auth1 : AuthenticationHandler<AuthenticationSchemeOptions>
{
....
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.NoResult());
}
}
public class Auth2 : AuthenticationHandler<AuthenticationSchemeOptions>
{
....
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.NoResult());
}
}

I took your code and reproduced that in ASP.NET Core 3.1. I think that you forgot to register authentication middleware, so it's only triggering in the case of authorization.
My reproduced app behaved the same way as you described, but as soon I added the line from below into the startup.cs, breakpoints in the default authentication scheme Auth1 started being caught while debugging.
app.UseAuthentication();
Remember, to place this before UseAuthorization and keep in mind that only Auth1 will be invoked since you set it as the default authentication scheme.

Related

Blazor server with web api controller authenticate issue

I have a Blazor server app that I want to add a web api controller to that can be accessed from Postman and eventually other apps. The Blazor app needs authentication, but not the web api. I tried adding AllowAnonymous, but I am getting an authentication error calling it from Postman:
HTTP Error 401.2 - Unauthorized
You are not authorized to view this page due to invalid authentication headers.
I suspect our security proxy is adding the headers:
Is it possible to host an unsecured (AllowAnonymous) web api inside an authenticated Blazor Server app?
Maybe I just need to craft my api call a certain way?
Controller:
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class ProfileController : ControllerBase
{
[HttpGet("{year}", Name = "GetProfileResults")]
public async Task<IActionResult> GetProfileResults(int year)
{
var profileResults = repo.GetResults(year);
return Ok(profileResults);
}
}
You have to add another http client with no tokens attached.
Program.cs
builder.Services.AddHttpClient(
name: "Anon.ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
RazorPage.razor.cs
[Inject]
public IHttpClientFactory HttpClientFactory { get; set; }
protected override async Task OnInitializedAsync()
{
http = HttpClientFactory.CreateClient("Anon.ServerAPI");
videos = await http.GetFromJsonAsync<VideoDto[]>("api/YoutubeVideos");
}
The key point to host a public API in a Blasor Server app is to ensure the API routing takes precedence over others.
In Program.cs (or Startup.cs):
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers(); // the order is important, this ensures API takes precedence.
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Alternatively for endpoint routing:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
app.MapControllers(); // the order is important, this ensures API takes precedence.
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
});
app.Run();
Next, the controller. In your example the code is completely correct. It must use [AllowAnonymous] at the controller level or at specific actions as usual.
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class ProfileController : ControllerBase
{
[HttpGet("{year}", Name = "GetProfileResults")]
public async Task<IActionResult> GetProfileResults(int year)
{
var profileResults = repo.GetResults(year);
return Ok(profileResults);
}
}
That should be enough to route the call to API before Blazor takes over the security.
Last but not the least is the default exception configuration handling code added to Blazor projects by default:
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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();
}
Please be aware that when this code is used any unhandled exceptions during an API call will be caught by the error handler which doesn't respect API [AllowAnonymous] settings and may trigger the authentication challenge configured for Blazor.

Authorize for all Gets on webApi NetCore

I don't know if it's possible, but, could I make something like a default policy for all my GET actions in my webApi, so they require a especific role, one for "read", "write" and "delete".
Not putting a [Authorize()] in each action of a component, more like a policy in the AddAuthorization of the StartUp.
Thanks.
I don't know if it's possible, but, could I make something like a
default policy for all my GET actions in my webApi, so they require a
especific role, one for "read", "write" and "delete".
Yes, you can use the Policy-based authorization with Role-based authorization.
Not putting a [Authorize()] in each action of a component, more like a
policy in the AddAuthorization of the StartUp.
The services.AddAuthorization() method in the Startup.ConfigureServices method is used to define the policies, after that the policies are applied to controllers by using the [Authorize] attribute with the policy name.
Here is a sample about using the Policy-based authorization with Role-based authorization in Asp.net Core MVC, you could refer it.
Create an ASP.NET MVC Core Application (using Individual User Accounts Authentication).
Using migration to generate the Identity User tables.
Click on the project and select add, then add a scaffolding item and select Identity, select Override all files, and Add the Identity Template pages.
Create a view and controller for the Roles:
public class RoleController : Controller
{
RoleManager<IdentityRole> roleManager;
public RoleController(RoleManager<IdentityRole> roleManager)
{
this.roleManager = roleManager;
}
public IActionResult Index()
{
var roles = roleManager.Roles.ToList();
return View(roles);
}
public IActionResult Create()
{
return View(new IdentityRole());
}
[HttpPost]
public async Task<IActionResult> Create(IdentityRole role)
{
await roleManager.CreateAsync(role);
return RedirectToAction("Index");
}
}
using RoleManager to manage roles, add related views.
modify the Startup.cs files, as shown below:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddControllersWithViews();
services.AddRazorPages();
In the Register page, you could add a property to let use select the Role, and use the UserManager.AddToRoleAsync() method to add roles for user.
After that, we could create roles suing the RoleController, like this:
Configure Authentication: define he following policies in ConfigureServices:
services.AddAuthorization(options => {
options.AddPolicy("readpolicy",
builder => builder.RequireRole("Admin", "Manager", "User"));
options.AddPolicy("writepolicy",
builder => builder.RequireRole("Admin", "Manager"));
});
Then, you could apply these policies to the Controller or Action method.
[Authorize(Policy = "readpolicy")]
public IActionResult Index()
{
var roles = roleManager.Roles.ToList();
return View(roles);
}
//[Authorize(Policy = "writepolicy")]
[Authorize(Roles ="User")]
public IActionResult Create()
{
return View(new IdentityRole());
}
After that, if current user role doesn't have access to the related action method, it will show the "Access denied":
Reference:
Adding Role Authorization to a ASP.NET MVC Core Application
Policy-Based And Role-Based Authorization In ASP.NET Core 3.0 Using Custom Handler
Policy-based authorization in ASP.NET Core

allow anonymous access if request from the specific URL or the same site asp.net core 3

I have web APIs hosted in a web application and consumed by the same site frontend by ajax requests. I need to allow anonymous access to these APIs if the request from the same web application frontend APIs host in, but if the request from an external requester its must be authorized. I use identity server 4 Bearer to secure the APIs and asp.net core 3.
You have to do two things:
Add the default (non-whitelisted) authentication as usual
Add a custom authorization policy that check the client IP
I assume you got number 1 covered. Here's how you handle number 2:
Add an authorization policy, and make it the default:
services.AddAuthorization(options =>
{
options.AddPolicy("AllowedIpPolicy", config =>
{
config.AddRequirements(new AllowedIpRequirement());
});
options.DefaultPolicy = options.GetPolicy("AllowedIpPolicy");
});
Add an authorization requirement AllowedIpRequirement, which is just an empty class:
public class AllowedIpRequirement : IAuthorizationRequirement { }
Create a handler for this requirement:
public class AllowedIpRequirementHandler : AuthorizationHandler<AllowedIpRequirement>
{
private readonly IHttpContextAccessor _contextAccessor;
public AllowedIpRequirementHandler(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
AllowedIpRequirement requirement)
{
var httpContext = _contextAccessor.HttpContext;
if (IsAllowedIp(httpContext.Connection.RemoteIpAddress) ||
context.User.Identity.IsAuthenticated)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool IsAllowedIp(IPAddress connectionRemoteIpAddress)
{
// ...check if allowed ip...
}
}
And finally register the handler and the required IHttpContextAccessor service:
services.AddSingleton<IAuthorizationHandler, AllowedIpRequirementHandler>();
services.AddHttpContextAccessor();

Multiple authentication methods in asp.Net core 2.2

Is there a way to use JWT bearer authentication AND a custom authentication method in .net core? I want all actions to default to JWT, except in a few cases where I want to use a custom authentication header.
I finally figured out how to do it. This example uses JWT authentication by default and custom authentication in certain rare cases. Please note, from what I've read, Microsoft seems to discourage writing your own auth. Please use at your own risk.
First, add this code to the startup.cs ConfigureServices method to ensure that authentication gets applied globally.
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
Then, add this to configure the schemes you wish to use (in our case JWT and Custom).
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
// Jwt Authentication
.AddJwtBearer(options =>
{
options.Audience = ".......";
options.Authority = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_...";
})
// Custom auth
.AddScheme<CustomAuthOptions,
CustomAuthHandler>(CustomAuthOptions.DefaultScheme, options => { });
Next create a class to hold your custom authentication options:
public class CustomAuthOptions : AuthenticationSchemeOptions
{
public const string Scheme = "custom auth";
public const string CustomAuthType = "custom auth type";
}
Finally, add an authentication handler to implement the custom authentication logic.
public class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
public CustomAuthHandler(
IOptionsMonitor<CustomAuthOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Auth logic goes here
if (!Request.Headers....)
{
return Task.FromResult(AuthenticateResult.Fail("Authentication Failed."));
}
// Create authenticated user
ClaimsPrincipal principal = .... ;
List<ClaimsIdentity> identities =
new List<ClaimsIdentity> {
new ClaimsIdentity(CustomAuthOptions.CustomAuthType)};
AuthenticationTicket ticket =
new AuthenticationTicket(
new ClaimsPrincipal(identities), CustomAuthOptions.Scheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
Finally, to tie it all together, add an authorize attribute to the actions you wish to use custom authorization on.
[HttpGet]
[Authorize(AuthenticationSchemes = CustomAuthOptions.Scheme)]
public HttpResponseMessage Get()
{
....
}
Now JWT authentication will automatically get applied to all actions, and custom authentication will get added to only the actions with the Authorize attribute set to the custom scheme.
I hope this helps someone.

How to add token validation only for protected actions in ASP.NET 5 (ASP.NET Core)

I have added a JWT middleware to my application:
app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true;} )
Now if my token does not validate (e.g. expired), I still get an error that lifetime validation did not pass. Is there a way to make the middleware validate the token only for protected resources? And if not, then how and where should I call what middleware does myself (reading the token into HttpContext.User)?
P.S This is how I add protection:
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
And this is how I allow public access:
[HttpGet]
[AllowAnonymous]
public string Get(int id)
{
}
To clarify: without the token this will work, but if the token is not valid (e.g. expired) even the public resource won't be accessible and 500 will be thrown (due to some internal bug cause 401 should be there really).
First, you need to disable automatic authentication by setting AutomaticAuthentication to false in your JWT bearer options.
To ensure the JWT bearer middleware is called for specific actions, you can create your own authorization policy using AddAuthenticationSchemes:
public void ConfigureServices(IServiceCollection services) {
services.AddAuthorization(options => {
options.AddPolicy("API", policy => {
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
});
});
}
Then, decorate your controller actions with the Authorize attribute:
[Authorize(Policy = "API")]
[HttpGet("your-action")]
public IActionResult Action() {
...
}