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(...)
Related
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 writing a ABAC system in which I will decide if a user can access to certain data based on some roles/atributes/etc. However, there is a special kind of user (something like superadministrator) who should be able to access everything, everywhere, always. I don't want to go through all policies, controllers, actions and methods and add a check on this specific role. Is there a way to do it in a more centralized way? (for example: in the startup).
If it is not possible to add it to a global place, I was thinking on adding it at least globally on a controller level: I was looking here and I saw that the decorator [Authorize(Roles = "Administrator")] lets you restrict the access to a certain method/class just to Administrator users. However, I want kind of "the opposite". I mean something like an AuthorizeAlways that had the following behaviour:
[AuthorizeAlways(Roles = "SuperAdministrator")]
public class ControlPanelController : Controller
{
[Authorize(Roles = "SetterUser")]
public ActionResult SetTime()
{
}
[Authorize(Roles = "PowerUser")]
[MinimumAgeAuthorize(50)]
public ActionResult ShutDown()
{
}
}
In this case I'd like that SuperAdministrator (even if they are 49 years old) has access to everywhere. A SetterUser has access only to SetTime and only a PowerUser who is older than 50 years old can access ShutDown.
I don't know if this makes much sense. Is it possible? Where could I do it? Thanks!
This blog post provides a good tutorial for how to implement custom authorization:
https://seanspaniel.wordpress.com/2019/12/13/custom-authorization-in-asp-net-core-3/
From that tutorial, in the CustomAuthorizationMiddleware class you could check for the "SuperAdministrator" role and grant access to every endpoint.
public static class CustomAuthorizationMiddleware
{
public static async Task Authorize(HttpContext httpContext, Func next)
{
var endpointMetaData = httpContext.GetEndpoint().Metadata;
bool hasCustomAuthorizeAttribute = endpointMetaData.Any(x => x is CustomAuthorizeAttribute);
if (!hasCustomAuthorizeAttribute)
{
await next.Invoke();
return;
}
CustomAuthorizeAttribute customAuthorizeAttribute = endpointMetaData
.FirstOrDefault(x => x is CustomAuthorizeAttribute) as CustomAuthorizeAttribute;
// Check if user has allowed role or super administrator role
bool isAuthorized = customAuthorizeAttribute.AllowedUserRoles
.Any(allowedRole => httpContext.User.IsInRole(allowedRole))
|| httpContext.User.IsInRole("SuperAdministrator");
if (isAuthorized)
{
await next.Invoke();
return;
}
httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await httpContext.Response.WriteAsync("unauthorized");
}
}
We are developing an application with Windows Authentication that is used internally at a company. We have looked at ADFS but at the moment this is not an option. The problem is our test servers are entirely cloud based on Azure. I have been trying to find a way to activate a user but have not found a good solution.
My first idea was to turn off authentication completely. This works good but we have some resources that checks for user roles so I had to abandon that idea.
<system.web>
<authentication mode="None" />
</system.web>
Example method that returns 401 Unauthorized with authentication mode="None", obviously:
[Authorize(Roles = "Administrator")]
[HttpGet]
[Route("TestMethod")]
public IHttpActionResult TestMethod()
{
return Ok("It works!");
}
My second thought was to edit the WebApiConfig and try to add authentication headers in every request server side. However when I started looking at the NTLM Authentication Scheme for HTTP and the 4-way handshake I realized this would probably be impossible.
NTLM Authentication Scheme for HTTP
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Other code for WebAPI registerations here
config.MessageHandlers.Add(new AuthenticationHandler());
}
}
class AuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add authentication to every request...
return base.SendAsync(request, cancellationToken);
}
}
Since there is no Owin (Katana) I can not edit the standard App_Start -> Startup.Auth.cs -> public void ConfigureAuth(IAppBuilder app) and try something there. I don't know how I would build up the "user object" anyway.
Is there anything we can do about this or do we have to test everything locally? If we could impersonate one user to be logged in for every request this would be fine in the test environment.
In terms of faking the authentication and authorisation you should be able to set a generic user principal with the appropriate roles using a FilterAttribute.
public class TestIdentityFilter : FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
filterContext.Principal = new GenericPrincipal(
new GenericIdentity(),
new string [] {"Administrator"});
}
}
You will need to set <authentication mode="None" /> as you did previously otherwise this code will never be hit in your test environment.
Adding this as a Global filter will override any other existing authentication system (for example if you deploy it to an authenticated environment by mistake). Obviously you will need to be very careful about only using this in your test system.
This example is based on MVC, I think there are some very small differences with WebApi but the basic principal applies.
Big thanks to #ste-fu for pointing me in the right direction. Complete code:
public class AppSettingsDynamicRolesAuthorizeAttribute : AuthorizeAttribute
{
public AppSettingsDynamicRolesAuthorizeAttribute(params string[] roleKeys)
{
List<string> roles = new List<string>(roleKeys.Length);
foreach (var roleKey in roleKeys)
{
roles.Add(WebConfigurationManager.AppSettings[roleKey]);
}
this.Roles = string.Join(",", roles);
}
public override void OnAuthorization(HttpActionContext filterContext)
{
if (Convert.ToBoolean(WebConfigurationManager.AppSettings["IsTestEnvironment"]))
{
filterContext.RequestContext.Principal = new GenericPrincipal(
new GenericIdentity("Spoofed-Oscar"),
new string[] { WebConfigurationManager.AppSettings[Role.Administrator] });
}
base.OnAuthorization(filterContext);
}
}
public static class Role
{
public const string Administrator = "Administrator";
public const string OtherRole = "OtherRole";
}
Can then be used like this:
[AppSettingsDynamicRolesAuthorize(Role.Administrator, Role.OtherRole)]
[HttpGet]
[Route("Test")]
public IHttpActionResult Get()
{
var userName = RequestContext.Principal.Identity.Name;
var user = HttpContext.Current.User.Identity;
return Ok("It works!");
}
So I've found bits and pieces that have enlightened me some on the [Authorize] tag, but nothing that solves my problem.
My scenario is that I have Web Api methods that I want to hit with integration tests using RestSharp. However RestSharp is getting my login page, instead of the results of the call.
[Authorize]
public Item GetItem([FromBody] int id)
{
return service.GetItem(id);
}
The product uses a custom login system, and what I would REALLY like would be a way to disable the [Authorize] badge only for integration tests. However I read that you can allow anonymous users and it would 'disable' the badge, so in the solution, I have an integration tests project, and in that project I have an App.config file. In that file I put:
<location>
<system.web>
<authorization>
<allow users="?"/>
</authorization>
</system.web>
</location>
But this doesn't appear to be working either. Any explanation as to what's going on, why it's not working and what can be done to get this working would be greatly appreciated.
I have attempted to set a Thread.CurrentPrincipal but that didn't work (maybe I did it wrong - can you set "anything" to be authorized in the code?). Authentication is handled in an httpmodule if that helps at all.
I realise that this question is about firing 'real' requests from RestSharp at the webapi endpoints so this suggestion is not immediately applicable to the OPs scenario.. BUT:
I'm using in-memory Web Api tests using HttpConfiguration, HttpServer and HttpMessageInvoker (much like Badri's suggestion I believe). In this way, I don't need listeners or ports open since I can test the full stack (end to end test) in memory - really handy on a build server, Heroku instance, etc.
Using in-memory tests, here is how you could set the Thread.CurrentPrincipal.. I have a helper on my test base class like this:
protected void AuthentateRequest()
{
Thread.CurrentPrincipal = new AuthenticatedPrincipal(Thread.CurrentPrincipal);
}
Which uses this:
public class AuthenticatedPrincipal : IPrincipal
{
private readonly IPrincipal _principalToWrap;
private readonly IIdentity _identityToWrap;
public AuthenticatedPrincipal(IPrincipal principalToWrap)
{
_principalToWrap = principalToWrap;
_identityToWrap = new AuthenticatedIdentity(principalToWrap.Identity);
}
public bool IsInRole(string role)
{ return _principalToWrap.IsInRole(role); }
public IIdentity Identity
{
get { return _identityToWrap; }
private set { throw new NotSupportedException(); }
}
}
public class AuthenticatedIdentity : IIdentity
{
private readonly IIdentity _identityToWrap;
public AuthenticatedIdentity(IIdentity identityToWrap)
{
_identityToWrap = identityToWrap;
}
public string Name
{
get { return _identityToWrap.Name; }
private set { throw new NotSupportedException(); }
}
public string AuthenticationType
{
get { return _identityToWrap.AuthenticationType; }
private set { throw new NotSupportedException(); }
}
public bool IsAuthenticated
{
get { return true; }
private set { throw new NotSupportedException(); }
}
}
It may seem like overkill to stub the IPrincipal manually but I tried with my mocking framework and it blew up in some of my test runners (Resharper and TeamCity, but not NCrunch - something about serialising over AppDomains I think).
This will set Thread.CurrentPrincipal inside the ApiController action method and therefore fool the AuthorizeAttribute into believing you are authenticated.
Here is how you should set the Thread.CurrentPrincipal. Add a message handler like this to your Web API project and add the handler in the Register method of WebApiConfig.cs like so: config.MessageHandlers.Add(new MyTestHandler());.
public class MyTestHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var local = request.Properties["MS_IsLocal"] as Lazy<bool>;
bool isLocal = local != null && local.Value;
if (isLocal)
{
if (request.Headers.GetValues("X-Testing").First().Equals("true"))
{
var dummyPrincipal = new GenericPrincipal(
new GenericIdentity("dummy", "dummy"),
new[] { "myrole1" });
Thread.CurrentPrincipal = dummyPrincipal;
if (HttpContext.Current != null)
HttpContext.Current.User = dummyPrincipal;
}
}
return await base.SendAsync(request, cancellationToken);
}
}
This handler sets an authenticated principal to make all your [Authorize] happy. There is an element of risk with this approach. Only for testing, you should plug this handler into the Web API pipeline. If you plug this handler in to the pipeline (intentional or otherwise) in your production code, it basically defeats your authentication mechanism. To mitigate the risk to some extent (hoping API is not accessed locally), I check to ensure the access is local and that there is a header X-Testing with a value of true.
From RestSharp, add the custom header.
var request = new RestRequest(...);
request.AddHeader("X-Testing", "true");
BTW, for integration testing, I'd much rather use in-memory hosting, instead of web-hosting. That way, Web API runs in the same testing project and you can do whatever you want with it, without the fear of breaking something in production. For more info on in-memory hosting, see this and this.
Set the authenticator for your RestClient:
RestClient.Authenticator = new HttpBasicAuthenticator(username, password);
Using the authenticator that your custom login system actually accepts ... Basic, NTLM, OAuth, Simple ...
It is kind of documented in the second line of the example at http://restsharp.org/
I have a couple of table on my database that specify witch users ( Depending on your AD Username) can actually use the current ASP.NET MVC 2 app I'm building.
My question is how ( or more likely where and where do I put it? On the master page?? ) do i write a method that gets the AD user out of the HTTP context and validates it against the database to see if you can actually use the app? If you can... the idea it's to write a couple of keys in the Session object with the information I need ( Role, Full Name, etc ).
I'm quite confused regarding how I should accomplish this and if it's actually the right way... Keep in mind that I have an admin section and non-admin section in my app.
Any thoughts?
Edit: Keep in mind that I do not care to authenticate the user through a form. All I want to check is if according to my database and your AD username you can use my app. If you can write to session in order to perish the information I need. Otherwise just throw an error page.
This is what I've implemented so far, is this the way to go?
What's the second method for? ( I'm sorry I'm kind of new to c#) What I want to do it's actually throw a view if yo're not authorized...
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
var canUse = this._userRepo.CanUserUseApp(httpContext.User.Identity.Name);
if (!canUse)
{
isAuthorized = false;
}
}
return isAuthorized;
}
You could activate and use Windows (NTLM) authentication and then write a custom [Authorize] attribute where you could fetch the currently connected AD user and perform the additional check of whether he is authorized or not to use the application against your data store. Then you would decorate controllers/actions that require authorization with this custom attribute.
UPDATE:
Here's an example of how such custom attribute might look like:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
// The user is authorized so far => check his credentials against
// the custom data store
return IsUserAllowedAccess(httpContext.User.Identity.Name);
}
return isAuthorized;
}
private bool IsUserAllowedAccess(string username)
{
throw new NotImplementedException();
}
}
and then:
[MyAuthorize]
public class FooController: Controller
{
public ActionResult Index()
{
...
}
}
Create a class called AdminAttribute with this code
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AdminsAttribute : AuthorizeAttribute
{
public AdminsAttribute()
{
this.Roles = "MSH\\GRP_Level1,MSH\\Grp_Level2";
}
}
public class HomeController : Controller
{
[Admins]
public ActionResult Level1()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}