Authorise the localhost in ASP.NET Core - asp.net-core

I am newbie in ASP.NET Core, and I have a controller I need to authorise it only on my machine, for the test purposes, however, deny on other...
I have the following config:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.DateFormatString= "yyyy-MM-ddTHH:mm:ssZ";
});
services.AddAuthentication("Cookie")
.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>("Cookie", null);
services.AddLogging(builder => { builder.AddSerilog(dispose: true); });
And on the test controlled I enabled the [Authorise] attrubute
[Authorize]
public class OrderController : Controller
Is there a way to allow my local machine to be autorised to acces the controller's actions? Something like [Authorize(Allow=localhost)]

You can create an action filter like so:
public class LocalhostAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var ip = context.HttpContext.Connection.RemoteIpAddress;
if (!IPAddress.IsLoopback(ip)) {
context.Result = new UnauthorizedResult();
return;
}
base.OnActionExecuting(context);
}
}
And then use the tag Localhost:
//[Authorize]
[Localhost]
public class OrderController : Controller
I believe this will work, restricting the access to the machine where it's executed.

This is more whitelisting than authorization. Authorization means checking whether a user has permission to do something. To do that, the user must be identified first, ie authenticated.
The article Client IP Safelist in the docs shows how you can implement IP safelists through middleware, an action filter or a Razor Pages filter.
App-wide Middleware
The middleware option applies to the entire application. The sample code retrieves the request's endpoint IP, checks it against a list of safe IDs and allows the call to proceed only if it comes from a "safe" list. Otherwise it returns a predetermined error code, in this case 401:
public async Task Invoke(HttpContext context)
{
if (context.Request.Method != "GET")
{
var remoteIp = context.Connection.RemoteIpAddress;
_logger.LogDebug("Request from Remote IP address: {RemoteIp}", remoteIp);
string[] ip = _adminSafeList.Split(';');
var bytes = remoteIp.GetAddressBytes();
var badIp = true;
foreach (var address in ip)
{
var testIp = IPAddress.Parse(address);
if(testIp.GetAddressBytes().SequenceEqual(bytes))
{
badIp = false;
break;
}
}
if(badIp)
{
_logger.LogInformation(
"Forbidden Request from Remote IP address: {RemoteIp}", remoteIp);
context.Response.StatusCode = 401;
return;
}
}
await _next.Invoke(context);
}
The article shows registering it before UseMvc() which means the request will be rejected before reaching the MVC middleware :
app.UseMiddleware<AdminSafeListMiddleware>(Configuration["AdminSafeList"]);
app.UseMvc();
This way we don't waste CPU time routing and processing a request that's going to be rejected anyway. The middleware option is a good choice for implementing a blacklist too.
Action Filter
The filtering code is essentially the same, this time defined in a class derived from ActionFilterAttribute. The filter is defined as a scoped service :
services.AddScoped<ClientIpCheckFilter>();
services.AddMvc(options =>
{
options.Filters.Add
(new ClientIpCheckPageFilter
(_loggerFactory, Configuration));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
In this case the request will reach the MVC infrastructure before it's accepted or rejected.
Razor Pages Filter
The code is once more the same, this time deriving from IPageFilter

Related

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();

Authorization: How to handle mutiple (dozen or more) requirements

I have a set of tables in our database with users, permissions, and a join that maps which users have what permissions.
Looking at the docs, the following is an example of how policies and the requirement(s) are set up on Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
}
And here is an example of a handler for multiple requirements:
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class PermissionHandler : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is ReadPermission)
{
if (IsOwner(context.User, context.Resource) ||
IsSponsor(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
else if (requirement is EditPermission ||
requirement is DeletePermission)
{
if (IsOwner(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsOwner(ClaimsPrincipal user, object resource)
{
// Code omitted for brevity
return true;
}
private bool IsSponsor(ClaimsPrincipal user, object resource)
{
// Code omitted for brevity
return true;
}
}
My intention is to check my database tables within the handler to validate that the user has a setting that corresponds to the policy. To check if a user can upload files, the policy might look like this:
services.AddAuthorization(config =>
{
config.AddPolicy("CanUploadFiles", policy => policy.Requirements.Add(new CanDoRequirement("CanUploadFiles")));
});
Using an [Authorize] attribute for a given policy, I can check that within the handler. I have that much working.
Question: Given that I might have 10-20 separate "CanDo…" permissions in our table, is there a better way to set these up rather than have separate lines in AddAuthorization()?
Well, I'm not aware of any shortcuts when configuring the 20-ish requirements and policies that would remove the separate lines in startup, but you could consider implementing a sort of custom resource based authorization rather than a policy based one, policy-based being a declarative one. Declarative meaning the policy is pre-configured. Like so: [Authorize("policy")].
By using imperative authorization, rather than declarative, you would remove the need for x amount of policies to be configured. Instead of saying "Authorize this method", you let the framework take care of the authorization itself.
Consider the following requirements
A user must be authenticated.
That user can only upload a file if they satisfy the CanUploadFiles which is a boolean on the user's record in the database.
Now consider the following example
You have created your own ICustomAuthorizationHandler, somewhat similar to the the ASP.NET Core's IAuthorizationHandler, with the exception that you won't be satisfying a policy, but instead you will feed it a 'CanDoPermission' and it will return true or false if that user has that specific 'flag'.
public class FileController : Controller
{
private ICustomAuthorizationService _authService
public FileController(ICustomAuthorizationService authService)
{
_authService = authService;
}
[Authorize]
public async Task<IActionResult> Upload(IFormFile file)
{
var authResult = await _authService.AuthorizeAsync(User, "CanDoUpload");
if (!authResult.Succeeded)
{
return new ForbidResult();
}
// Process upload
return View();
}
}
This way, there wouldn't have to be policies nor requirements configured for checking if the user can upload a file. But, you would need to take care of a lot of the stuff that you get for 'free' by simply going for policies and configuring them in AddAuthorization.

AspNetCore.Session-Distributed Cache keeps timing out on distributed servers

I have an AspNetCore (core 2.1) web appl that works fine in any single server environment, but times out after a few seconds in the environment with 2 load-balanced web servers.
Here are my startup.cs and other classes, and a screenshot of my AppSessionState table. I hope someone can point me to the right path. I've spent 2 days on this and can't find anything else that needs settings or what's wrong with what I'm doing.
Some explanation of below code:
As seen, I've followed the steps to configure the app to use Distributed SQL Server caching and have a helper static class HttpSessionService which handles adding/getting values from the Session State. Also, I have a Session-Timeout attribute that I annotate each of my controllers to control the session timeouts. And after a few seconds or clicks in the app, as each controller action makes this call
HttpSessionService.Redirect()
this Redirect() method gets a NULL user session from this line, which causes the app to timeout.
var userSession = GetValues<UserIdentityView>(SessionKeys.User);
I've attached two VS debuggers to both servers and I've noticed that even when all sessions coming to one of the debugger instance (one server) the AspNet Session still returned NULL for the above userSession value.
Again, this ONLY happens on a distributed environment, i.e. if I stop one of the sites on one of the web servers everything works fine.
I have looked and implemented the session state distributed caching with SQLServer as explained (the same) in different pages, here are few.
https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-3.0
https://www.c-sharpcorner.com/article/configure-sql-server-session-state-in-asp-net-core/
And I do see sessions being written to my created AppSessionState table, yet the app continues to timeout in the environment with 2 load-balanced servers.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Session State distributed cache configuration against SQLServer.
var aspStateConnStr = ConfigurationManager.ConnectionStrings["ASPState"].ConnectionString;
var aspSessionStateSchemaName = _config.GetValue<string>("AppSettings:AspSessionStateSchemaName");
var aspSessionStateTbl = _config.GetValue<string>("AppSettings:AspSessionStateTable");
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = aspStateConnStr;
options.SchemaName = aspSessionStateSchemaName;
options.TableName = aspSessionStateTbl;
});
....
services.AddSession(options =>
{
options.IdleTimeout = 1200;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
...
services.AddMvc().AddJsonOptions(opt => opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime, IDistributedCache distCache)
{
var distCacheOptions = new DistributedCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5));
// Session State distributed cache configuration.
lifetime.ApplicationStarted.Register(() =>
{
var currentTimeUTC = DateTime.UtcNow.ToString();
byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
distCache.Set("cachedTimeUTC", encodedCurrentTimeUTC, distCacheOptions);
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession(); // This must be called before the app.UseMvc()
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
HttpSessionService.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>(), distCache, distCacheOptions);
}
HttpSessionService (helper class):
public class HttpSessionService
{
private static IHttpContextAccessor _httpContextAccessor;
private static IDistributedCache _distributedCache;
private static ISession _session => _httpContextAccessor.HttpContext.Session;
public static void Configure(IHttpContextAccessor httpContextAccessor, IDistributedCache distCache)
{
_httpContextAccessor = httpContextAccessor;
_distributedCache = distCache;
}
public static void SetValues<T>(string key, T value)
{
_session.Set<T>(key, value);
}
public static T GetValues<T>(string key)
{
var sessionValue = _session.Get<T>(key);
return sessionValue == null ? default(T) : sessionValue;
}
public static bool Redirect()
{
var result = false;
var userSession = GetValues<UserIdentityView>(SessionKeys.User);
if (userSession == null || userSession?.IsAuthenticated == false)
{
result = true;
}
return result;
}
}
SessionTimeoutAttribute:
public class SessionTimeoutAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var redirect = HttpSessionService.Redirect();
if (redirect)
{
context.Result = new RedirectResult("~/Account/SessionTimeOut");
return;
}
base.OnActionExecuting(context);
}
}
MyController
[SessionTimeout]
public class MyController : Controller
{
// Every action in this and any other controller time out and I get redirected by SessionTimeoutAttribute to "~/Account/SessionTimeOut"
}
Sorry for the late reply on this. I've changed my original implementation, by injecting IDistributedCache interface to all of my controllers and using this setting in the Statusup.cs class in ConfigureServices() function.
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = aspStateConnStr;
options.SchemaName = aspSessionStateSchemaName;
options.TableName = aspSessionStateTbl;
options.ExpiredItemsDeletionInterval = null;
});
That made it work in a web farm.
As you can see I'm setting the ExpiredItemsDeletionInterval to null to prevent some basic cache entries from clearing out of cache, but with doing so I ran into another problem that when I attempt to get them I still get null back even if the entry is in the database table. So, that's another thing I'm trying to figure out.
It looks like you're capturing the Session value from HttpContext in your static HttpSessionService instance. That value is per-request so it's definitely going to randomly fail if you capture it like that. You need to go through the IHttpContextAccessor every time you want to access an HttpContext value, if you want to get the latest value.
Also, I'd suggest you pass an HttpContext in to your helper methods rather than using IHttpContextAccessor. It has performance implications and should generally only be used if you absolutely can't pass an HttpContext through. The places you show here seem to have an HttpContext available, so I'd recommend using that instead of the accessor.

IdentityServer4 How can I redirect after login to the revious url page without registering all routes at IdP

As recommend I would have register the authorize callback url/redirect_url at IdP, which it works.
But what if a client using MVC app tries to access a page with an unauthorized state, will be redirect to idsrv login page.
The redirect_url is always (Home page entry point) as configured.
To change this behavior I would have to register all possible routes at IdP.
That can not a be solution!
On idsrv Login method I have tried:
Login(string returnUrl)
checking the value from returnUrl it gives /connect/authorize/callback?client_id=...
Shouldn't returnUrl have the url of the previous page? Like in a normal mvc app has..
I have tried to get Referer store it on session and then redirect..
if (!string.IsNullOrEmpty(Request.Headers["Referer"].ToString()))
{
this.httpContextAccessor.HttpContext.Session.SetString("Referer", Request.Headers["Referer"].ToString());
}
But that doesn't work Referer comes null...
I have checked what's coming on context from interation services
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
context.RedirectUri
And returns /signin-oidc/ this is the automated way for returning (Home page entry point).
Any chance to get the previous url, so that the user can be redirect?
So what can I do else?
I'm using Hybrid flow to manage the following clients : mvc-app, classic-asp, web api
Here's an example of implementation allowing you to achieve what you want. Keep in mind that there's other ways of doing it.
All the code goes on your client, the server never knows anything about the end url.
First, you want to create a custom attribute that will be decorating all your actions/controllers that you want to protect:
using System;
using System.Web.Mvc;
namespace MyApp
{
internal class MyCustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult)
{
filterContext.RequestContext.HttpContext.Session["oidc-returnUrl"] = filterContext.RequestContext.HttpContext.Request.UrlReferrer?.PathAndQuery;
}
}
}
}
And then you are going to create a login route/action that will handle all your authorize requests:
using System.Web.Mvc;
namespace MyApp
{
public class AccountController : Controller
{
[MyCustomAuthorize]
public ActionResult Login()
{
returnUrl = Session["oidc-returnUrl"]?.ToString();
// clean up
Session["oidc-returnUrl"] = null;
return Redirect(returnUrl ?? "/");
}
}
}
The login path can be changed in your startup code:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = "/my-login"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
// setting up your client
});
}
}

IP based authorization policy with Attributes

I'm trying to secure a REST API based on the client IP.
Imagine a blog application with these request examples:
/post/list // Everyone should see the posts
/post/create // Only Authors should create a post
/post/update/42 // Only Authors should update a post
/post/delete/42 // Only Admins should delete a post
/comment/42/list // Everyone should see a post's comments
/comment/42/create // Everyone should create a comment
/comment/42/delete/1337 // Only Admins should delete a comment
IP whitelists defined in appsettings.json:
"IpSecurity": {
"Author": "123.456.789.43,123.456.789.44",
"Admin": "123.456.789.42"
}
Here are action examples with the according RequireRole attributes I'd like to implement:
[HttpGet("post/list")]
public List<Post> List()
// ...
[RequireRole("Author")]
[HttpGet("post/create")]
public StandardResponse Create([FromBody]Post post)
// ...
[RequireRole("Admin")]
[HttpGet("post/delete/{id}")]
public StandardResponse Delete(int id)
// ...
Defined injectable from Startup
var IpSecurity = Configuration.GetSection("IpSecurity");
services.Configure<IpSecurityConfig>(IpSecurity);
Does it sound like a good idea ?
Should I do a custom authorization policy, a middleware and/or a filter for that ?
How would I implement the RequireRole attribute ?
This gives an idea of how to implement an IP whitelist but since a middleware does not have access to the contextual action, I can't use attributes to define my requirements.
Yes, that looks good not least because it looks easy understand at a glance.
One comment I would offer is that using the term "Role" for this might confuse your successors. Call it "MachineRole" instead? (And, for the same reason, don't use the [Authorize(Roles="..."])
Implementation in AspNetCore looks to me a little more complex that it was under MVC4, something like this in the usual methods in Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
//after services.AddMvc() :
services.AddAuthorization(o => { o.AddPolicy(MachineRole.AuthorMachine, p => p.RequireClaim(nameof(MachineRole), MachineRole.AuthorMachine)); });
services.AddAuthorization(o => { o.AddPolicy(MachineRole.AdminMachine, p => p.RequireClaim(nameof(MachineRole), MachineRole.AdminMachine)); });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// ...
app.UseClaimsTransformation( AddMachineRoleClaims );
// app.UseMvc( ... );
// ...etc...
}
public Task<ClaimsPrincipal> AddMachineRoleClaims(ClaimsTransformationContext ctx)
{
var connectionRemoteIpAddress = ctx.Context.Connection.RemoteIpAddress.MapToIPv4();
if (Configuration.GetSection("IpSecurity")["Author"].Contains(connectionRemoteIpAddress.ToString()))
{
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim(nameof(MachineRole), MachineRole.AuthorMachine) }));
}
if (Configuration.GetSection("IpSecurity")["Admin"].Contains(connectionRemoteIpAddress.ToString()))
{
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim( nameof(MachineRole), MachineRole.AdminMachine) }));
}
return Task.FromResult(ctx.Principal);
}
public static class MachineRole
{
public const string AuthorMachine = "AuthorMachine";
public const string AdminMachine = "AdminMachine";
}
and then you can use
[Authorize(Policy = MachineRole.AdminMachine)]
I was sufficiently irritated by the fact that this is not simple, and in particular not a simple as it was in MVC4 that I've done https://github.com/chrisfcarroll/RequireClaimAttributeAspNetCore to make it possible to write:
[RequireClaim("ClaimType",Value = "RequiredValue")]
public IActionResult Action(){}
Assuming you've thought about the implications of IP based authorization -- such that they can be spoofed, and requests make it very deep into your stack before being rejected...
I'd suggest creating a middleware that assigns claims, or at the very least sets the identity (so the user is authenticated). And then use either claims (which you've assigned to the identity in the middleware) or authorization policies (https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies). You could then reject each request based on the IPs associated with a policy:
[Authorize(Policy="AuthorIp")]
[HttpGet("post/create")]
public StandardResponse Create([FromBody]Post post)