I am working on My SAAS based application where I am facing one problem related to
requirement , my Application should be open from authenticated system only ,and it should be based on IP address. I will give permission from my database which IP address is authenticated. and It will work accordingly .. I have not tried any code because I have no Idea about this.
You can do this with an attribute that implements the IAuthorizationFilter interface. This will get called during the authorization checks done on each request.
For instance:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class IPFilterAttribute : Attribute, IAuthorizationFilter
{
/// <summary>Invoked during authization checks for page load</summary>
/// <param name="filterContext">Context of call, contains request and so on</param>
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
var request = filterContext?.HttpContext?.Request;
if (request == null)
throw new ArgumentNullException(nameof(filterContext));
if (!CheckIPAddress(request.UserHostAddress))
// Setting the Result property on filterContext stops processing.
filterContext.Result = new HttpUnauthorizedResult("Address Forbidden");
}
/// <summary>Check if the supplied IP address is authorized to access this page</summary>
/// <param name="addr">Client address to test</param>
/// <returns>True if address is authorized, else false</returns>
private bool CheckIPAddress(string addr)
{
// sample, just check if it's the localhost address
return (addr == "127.0.0.1" || addr == "::1");
}
}
This will check if the client address is localhost (127.0.0.1 or ::1) and allow it through, blocking everything else. Adjust that as necessary.
In the OnAuthorization method, setting filterContext.Result will stop further processing. In this case I use it to show a 403 - Forbidden response. You could also use a RedirectResult or some other result object.
You can attach that to a specific method or onto your controller class:
// Put this here to apply to all pages in this controller
[IPFilter]
public class TestController : Controller
{
// Or here to only affect the index page
[IPFilter]
public ActionResult Index()
{
return View();
}
}
Related
I'm having trouble specifying two separate Authorization attributes on a class method: the user is to be allowed access if either of the two attributes are true.
The Athorization class looks like this:
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class AuthAttribute : AuthorizeAttribute {
. . .
and the action:
[Auth(Roles = AuthRole.SuperAdministrator)]
[Auth(Roles = AuthRole.Administrator, Module = ModuleID.SomeModule)]
public ActionResult Index() {
return View(GetIndexViewModel());
}
Is there a way to solve this or do I need to rethink my approach?
This is to be run in MVC2.
There is a better way to do this in later versions of asp.net you can do both OR and AND on roles. This is done through convention, listing multiple roles in a single Authorize will perform an OR where adding Multiple Authorize Attributes will perform AND.
OR example
[Authorize(Roles = "PowerUser,ControlPanelUser")]
AND Example
[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]
You can find more info on this at the following link
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles
Multiple AuthorizeAttribute instances are processed by MVC as if they were joined with AND. If you want an OR behaviour you will need to implement your own logic for checks. Preferably implement AuthAttribute to take multiple roles and perform an own check with OR logic.
Another solution is to use standard AuthorizeAttribute and implement custom IPrincipal that will implement bool IsInRole(string role) method to provide 'OR' behaviour.
An example is here:
https://stackoverflow.com/a/10754108/449906
I've been using this solution in production environment for awhile now, using .NET Core 3.0. I wanted the OR behavior between a custom attribute and the native AuthorizeAttribute. To do so, I implemented the IAuthorizationEvaluator interface, which gets called as soon as all authorizers evaluate theirs results.
/// <summary>
/// Responsible for evaluating if authorization was successful or not, after execution of
/// authorization handler pipelines.
/// This class was implemented because MVC default behavior is to apply an AND behavior
/// with the result of each authorization handler. But to allow our API to have multiple
/// authorization handlers, in which the final authorization result is if ANY handlers return
/// true, the class <cref name="IAuthorizationEvaluator" /> had to be extended to add this
/// OR behavior.
/// </summary>
public class CustomAuthorizationEvaluator : IAuthorizationEvaluator
{
/// <summary>
/// Evaluates the results of all authorization handlers called in the pipeline.
/// Will fail if: at least ONE authorization handler calls context.Fail() OR none of
/// authorization handlers call context.Success().
/// Will succeed if: at least one authorization handler calls context.Success().
/// </summary>
/// <param name="context">Shared context among handlers.</param>
/// <returns>Authorization result.</returns>
public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
{
// If context.Fail() got called in ANY of the authorization handlers:
if (context.HasFailed == true)
{
return AuthorizationResult.Failed(AuthorizationFailure.ExplicitFail());
}
// If none handler called context.Fail(), some of them could have called
// context.Success(). MVC treats the context.HasSucceeded with an AND behavior,
// meaning that if one of the custom authorization handlers have called
// context.Success() and others didn't, the property context.HasSucceeded will be
// false. Thus, this class is responsible for applying the OR behavior instead of
// the default AND.
bool success =
context.PendingRequirements.Count() < context.Requirements.Count();
return success == true
? AuthorizationResult.Success()
: AuthorizationResult.Failed(AuthorizationFailure.ExplicitFail());
}
}
This evaluator will only be called if added to .NET service collection (in your startup class) as follows:
services.AddSingleton<IAuthorizationEvaluator, CustomAuthorizationEvaluator>();
In the controller class, decorate each method with both attributes. In my case [Authorize] and [CustomAuthorize].
I'm not sure how others feel about this but I wanted an OR behavior too. In my AuthorizationHandlers I just called Succeed if any of them passed. Note this did NOT work with the built-in Authorize attribute that has no parameters.
public class LoggedInHandler : AuthorizationHandler<LoggedInAuthReq>
{
private readonly IHttpContextAccessor httpContextAccessor;
public LoggedInHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LoggedInAuthReq requirement)
{
var httpContext = httpContextAccessor.HttpContext;
if (httpContext != null && requirement.IsLoggedIn())
{
context.Succeed(requirement);
foreach (var req in context.Requirements)
{
context.Succeed(req);
}
}
return Task.CompletedTask;
}
}
Supply your own LoggedInAuthReq. In startup inject these in services with
services.AddAuthorization(o => {
o.AddPolicy("AadLoggedIn", policy => policy.AddRequirements(new LoggedInAuthReq()));
... more here
});
services.AddSingleton<IAuthorizationHandler, LoggedInHandler>();
... more here
And in your controller method
[Authorize("FacebookLoggedIn")]
[Authorize("MsaLoggedIn")]
[Authorize("AadLoggedIn")]
[HttpGet("anyuser")]
public JsonResult AnyUser()
{
return new JsonResult(new { I = "did it with Any User!" })
{
StatusCode = (int)HttpStatusCode.OK,
};
}
This could probably also be accomplished with a single attribute and a bunch of if statements. It works for me in this scenario. asp.net core 2.2 as of this writing.
I use JWT Bearer tokens for my application - simple chat on my site. Chat users can be logged in and not logged in. To use chat, you can be logged or not.
(Logged in users can receive private messages and so on).
This means that there may be users in my hub with or without an account (AspNetUser) in the system.
I correctly identify users using Context.Users. SignalR connects connectionId to userId (and other logged in user information). To make it happen, I decided to insert an Authorization filter. When I don't include this filter, the hub sees all users as anonymous.
I would like my hub was able to service all connections (logged in or anonymous users). Currently, due to the [Authorize] attribute, only logged in users can use my hub. Anonymous users receive a 401 error.
The below method should explain what I want to achieve:
[Authorize(AuthenticationSchemes = OAuthIntrospectionDefaults.AuthenticationScheme)]
public class NotificationHub : Hub
{
public override Task OnConnectedAsync()
{
string name = Context.User.Identity.Name;
if (!string.IsNullOrEmpty(Context.User.Identity.Name))
{
Console.WriteLine($"New Connection! It's a registered user: {name}!");
}
else
{
Console.WriteLine($"New Connection! It's an anonymous user!");
}
return base.OnConnectedAsync();
}
}
Why do I need it?
All users receive the same public messages. However, a private message can be sent to logged in users. I do it as follows (and only appropriate user receive this message):
Clients.User("userId").SendAsync("Message", "Something");
Is it possible for my hub to treat users like:
"Are you logged in? That's nice, I'll save your details."
"Are you not logged in? Don't worry, you're welcome too, but I'll save only a connection ID and nothing else about you).
You just have to add [AllowAnonymous] to your hub.
This code is working for me with ASP.NET Core SignalR:
[AllowAnonymous]
[Authorize(AuthenticationSchemes = "Bearer")]
public class ChatHub : Hub
{
In relation to your code, I would suggest you change:
if (!string.IsNullOrEmpty(Context.User.Identity.Name))
To:
if (Context.User.Identity.IsAuthenticated)
The MSDN documentation does not make it obvious (at a glance) but 'SignalR' has it's own (same-name) 'Authorize' attribute; so if you derive from it, you can override the following method (within your custom/derived version - e.g. "SignalRAuthorizeAttribute"):
public override bool AuthorizeHubMethodInvocation(
IHubIncomingInvokerContext hubIncomingInvokerContext,
bool appliesToMethod)
{
if (hubIncomingInvokerContext.MethodDescriptor.Attributes.OfType<SignalRAllowAnonymousAttribute>().Any() ||
hubIncomingInvokerContext.MethodDescriptor.Hub.HubType.GetCustomAttributes<SignalRAllowAnonymousAttribute>().Any())
{
return true;
}
return
base.AuthorizeHubMethodInvocation(
hubIncomingInvokerContext,
appliesToMethod);
}
And therefore you can have your hubs closed by default (so that the exceptional anonymous one/s have to be actively whitelisted with a custom 'SignalRAllowAnonymousAttribute' attribute):
// GlobalHost.HubPipeline.RequireAuthentication();
var authorizeAttribute =
new SignalRAuthorizeAttribute();
GlobalHost.HubPipeline.AddModule(
new AuthorizeModule(
globalConnectionAuthorizer: authorizeAttribute,
globalInvocationAuthorizer: authorizeAttribute));
// Marker class
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SignalRAllowAnonymousAttribute : Attribute
{
}
I am creating a session control on my project and I need help at the moment.
Basically, my HomeController inherits from CustomController.
HomeController manages the methods and CustomController runs before methods to check session info.
public class HomeController : CustomController
{
public ActionResult Index()
{
}
}
public class CustomController : Controller
{
public OnActionExecuting()
{
// Check session
}
}
My problem is, I do not want to check Session before HomeController/Index method. Is this possible?
You could do it with a custom attribute class as follows:
/// <summary>
/// Indicates whether session checking is enabled for an MVC action or controller.
/// </summary>
public class CheckSessionAttribute : Attribute
{
public CheckSessionAttribute(bool enabled)
{
this.Enabled = enabled;
}
public bool Enabled { get; }
}
Then annotate the action method you want to exclude session checking with [CheckSession(false)].
Lastly, include the following in the OnActionExecuting method of the base class:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check if the CheckSession attribute is present and skip the session
// check if [CheckSession(false)] was explicitly provided.
bool checkSession = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckSession), true)
.OfType<CheckSession>()
.Select(attr => attr.Enabled)
.DefaultIfEmpty(true)
.First();
if (checkSession)
{
// Check session
}
}
This checks for the presence of the [CheckSession(false)] attribute and disables the session check in that case. In this way, you can configure the methods that should not check the session info simply by annotating them with the new attribute. This also makes it immediately clear that the session is not checked for that specific action.
I've implemented the following action attribute in my MVC solution.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeADAttribute : AuthorizeAttribute
{
public string[] Groups { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (base.AuthorizeCore(httpContext))
{
/* Return true immediately if the authorization is not
locked down to any particular AD group */
if (Groups == null)
return true;
foreach (var group in Groups)
if (httpContext.User.IsInRole(group))
return true;
}
return false;
}
}
And invoked it like this:
public const string Admin = "MY_DOMAIN\\Admins";
public const string Users = "MY_DOMAIN\\Users";
public const string AddUser = "MY_DOMAIN\\AddUser";
[AuthorizeAD(Groups = new string[] { Admin, Users })]
public ActionResult GridData(...)
{ ... }
[AuthorizeAD(Groups = new string[] { Admin, Users, AddUser })]
public ActionResult Add(...)
{ ... }
It seemed like it was working fine so far (locally without a problem), until someone noticed (on another question I posted), that I've been receiving 401 errors on the deployed instance.
I think my AuthorizeADAttribute need to be reworked, unless anyone has an idea of what the issue could be on the host environment. The idea is that a user must be in the admin or user group on the active directory to access the site, and if he/she is assigned to the user role, they need to belong to one other group as well, eg: Add, Delete, Update, etc...
So far I'm pretty much stumped :/
It seemed like it was working fine so far (locally without a problem),
until someone noticed (on another question I posted), that I've been
receiving 401 errors on the deployed instance
That's perfectly normal and it is how NTLM authentication works. It's a challenge-response authentication protocol meaning that the server challenges the client by sending a 401 page to which the client responds, ... So the 401s you are seeing are parts of the challenge that the server sent to the client to authenticate himself. You see that in the end the client successfully responded to the challenge and was authenticated with a 200 success.
I don't think that you should be reworking anything with your custom authorize attribute. It's just that you probably don't need it as you could achieve similar functionality with the default Authorize attribute:
[Authorize(Roles = "MY_DOMAIN\\Admins,MY_DOMAIN\\Users" })]
public ActionResult GridData(...)
I've read thru many of the questions on ASP.NET MVC [RequireHttps] - but can't find the answer to this question:
How do you make the [RequireHttps] attribute switch the url to https if it was not https to start with?
I have this code:
public ActionResult DoSomething()
{
return View("AnotherAction");
}
[RequireHttps]
public ActionResult AnotherAction()
{
return View();
}
But I get an error saying: "The requested resource can only be accessed via SSL."
The MVC futures project has a similar attribute [RequireSsl(Redirect = true)]. But that is outdated now ... What is the equivalent in MVC 2?
When someone types in the URL http://example.com/home/dosomething OR the url http://example.com/home/anotheraction, I need them to be automatically redirected to the url https://example.com/home/anotheraction
EDIT this is the sequence of events:
The URL http://example.com/home/dosomething is called from another website. They redirect their users to this url (with a response.redirect or similar).
DoSomething() then tries to return AnotherAction(), but fails with the error message "The requested resource can only be accessed via SSL."
The RequiresHttps attribute does automatically attempt to redirect to https://your-url. I verified this behavior on a site I have that uses that attribute, and also looking at the code in Reflector:
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
Are you sure you have your site set up to accept secure connections? What happens if you try to browse to https://your-url directly?
[mvc 4] short answer:
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection)
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
longer answer:
to move from http to https you cant send a redirect to https after the first packet,
therefor you need to catch the packet using Application_BeginRequest,
from the Global.asax add the function and it will override the default,
the code should be something like so (Global.asax on the class level):
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection &&
!Request.Url.Host.Contains("localhost") &&
Request.Url.AbsolutePath.Contains("SGAccount/Login"))
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
i strongly suggest putting a breakpoints and inspecting the Request.Url object for any url related need.
or visit the msdn page confused about request.url absoluteuri vs originalstring?
so am i you can go to dotnetperls for examples.
this function enables you to develop on localhost and deploying your code as is.
now for every page you want to make a https redirect you need to specify it in the if condition.
to move from https to http you can use regular Response.Redirect like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}", Request.Url.Authority), true);
}
notice this also support working on the same code when developing on local host by not interrupting the original course of things pre the https addition.
also i recommend thinking about implementing some return url convention (if not already implemented) in that case you should go something like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}{1}", Request.Url.Authority, returnUrl), true);
}
this will redirect to the requested page post login.
naturally you should protect every page that shows user data, register, login and more.
Http HEAD requests do not appear to be redirected. When reviewing our error logs we see lots of this message, googling lands here, but after looking more at the details they have a few interesting "features"
Request_method: HEAD
User Agent: curl/7.35.0
In other words all of the failed attempts were not customer facing...
(100% credit to comment from #arserbin3 for making me realize they were all HEAD requests)
MVC4 does now redirect
but not how you would expect.
http://www.example.com:8080/alpha/bravo/charlie?q=quux
will be redirect the client's browser to
https://www.example.com/alpha/bravo/charlie?q=quux
Notice the lack of a port number.
http://aspnetwebstack.codeplex.com/SourceControl/latest#test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs
code test
[Fact]
public void OnAuthorizationRedirectsIfRequestIsNotSecureAndMethodIsGet()
confirms this is the desired behaviour.
If you would like to write a custom attribute that does include the PORT ... you can base your code on:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/RequireHttpsAttribute.cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public RequireHttpsAttribute()
: this(permanent: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequireHttpsAttribute"/> class.
/// </summary>
/// <param name="permanent">Whether the redirect to HTTPS should be a permanent redirect.</param>
public RequireHttpsAttribute(bool permanent)
{
this.Permanent = permanent;
}
/// <summary>
/// Gets a value indicating whether the redirect to HTTPS should be a permanent redirect.
/// </summary>
public bool Permanent { get; private set; }
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.Request.IsSecureConnection)
{
HandleNonHttpsRequest(filterContext);
}
}
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
// redirect to HTTPS version of page
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url, this.Permanent);
}
}
To supplement the answer already given, this is the code from the MVC 5 implementation of HandleNonHttpsRequest
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
...
}