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
{
}
Related
my controller method consumes a JWT token which was enabled in ConfigureServices method in Startup.cs
.AddJwtBearer(options => { // some code }; });
The CreateUser() action in UserController consumes this token
[HttpPost, Authorize("JWT")]
public SaveResponse CreateUser(IUnitOfWork uow, UserRequest request) {
return new UserRepository().Create(uow, request);
}
The problem is as follows: A few methods deeper upon creating a new user, there's a method HasPermission() that checks logged in user's Administration permissions. However, in this particular case using JWT, there's no logged in user. The presence of valid JWT suffices. I am going to modify this HasPermission() in a way, that it also accepts JWT.
At CreateUser method level, the JWT is present inside HttpRequest's 'Authorization' header.
The question is - How can I deliver this JWT token to like a 8th method in a chain of methods executed by UserRepository().Create(uow, request) ? Is there a way to pull this off without modifying parameters of these methods?
thank you
If you use DI to instantiate service dependecies you can register IHttpContextAccessor via services.AddHttpContextAccessor() and use it to get information about request:
public SomeService(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public void SomeServiceMethod()
{
var auth = _contextAccessor.HttpContext.Request.Headers[HeaderNames.Authorization].ToString(); // possibly will need to remove scheme from the header
}
This particular case using JWT, there's no logged in user. The presence of valid JWT suffices.
Assuming you have the auth middleware enabled, if the request is able to reach CreateUser action, then [Authorize] attribute makes sure that the token is valid. So you don't need to do another validation.
Second, you shouldn't flow the token down to the repository. Keep HTTP and data retrieval concerns separate.
The solution to not "passing a parameter down 8 level" is to use dependency injection throughout your application and let it keep track of dependencies.
To access the current user inside your repo, create an interface that exposes the user:
interface IPrincipalAccessor {
ClaimsPrincipal? Principal { get; }
}
then implement this with IHttpContextAccessor
private class HttpPrincipalAccessor : IPrincipalAccessor
{
private IHttpContextAccessor _httpContextAccessor;
public HttpPrincipalAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ClaimsPrincipal? Principal => _httpContextAccessor?.HttpContext?.User;
}
You need to enable IHttpAccessor and register this class in DI:
services.AddHttpContextAccessor();
services.AddScoped<IPrincipalAccessor, HttpPrincipalAccessor>();
Now you can inject this interface in your repo and use the user claims. The repo isn't aware, and doesn't care where the user comes from, it just needs to know the current user.
class MyRepo
{
private IPrincipalAccessor _principalAccessor;
public MyRepo(IPrincipalAccessor principalAccessor)
{
_principalAccessor = principalAccessor;
}
Task Create(/* some parameters */)
{
var user = _principalAccessor.Principal;
if (user.HasClaim("eyes", "2"))
{
// user has two eyes
}
// ...
}
}
But the problem with your code is that you're not using dependency injection, so you need to inject your repo, instead of newing it up.
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!");
}
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 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();
}