Check authorize in SignalR attribute - c#

i have some services on ServiceStack and use SignalR in this project.
And now, i would like to secure hub connection (access only for authenticated users), but i use ServiceStack framework authentication.. (not asp.net authentication) and ServiceStack's sessions (write AuthUserId ih this session and authentication flag).
So, when user trying connect to the hub -- hub must to check authentication...
(yes, i can request Cookies from Hub (method OnConnected, for example), but SignalR check authentication in Authorize Attribute - and i must do it in this class (not in hub)
(http://www.asp.net/signalr/overview/signalr-20/security/hub-authorization)
So, i create class
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class AuthorizeMyAttribute : AuthorizeAttribute
{
protected override bool UserAuthorized(System.Security.Principal.IPrincipal user)
{
//... how can i request Cookies? / or may be can access for ServiceStack session...
// and return true or false
}
}
What can i do for it?
Thanks!

AuthorizeAttribute has two more virtual methods:
AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
http://msdn.microsoft.com/en-us/library/microsoft.aspnet.signalr.authorizeattribute(v=vs.118).aspx
The default implementations of both methods call UserAuthorized with the request's IPrincipal.
AuthorizeHubConnection is passed an IRequest directly.
In AuthorizeHubMethodInvocation, you can access the IRequest object from the IHubIncomingInvokerContext like so: hubIncomingInvokerContext.Hub.Context.Request.

I still struggled with this for some time trying to get the ServiceStack.Web.IRequest from the SignalR.IRequest so I could use ServiceStack's functions to request the session to see if the user had been auth'd. In the end I gave up and got the cookies from SignalR. I hope the following code snippet helps someone else nagivate this.
public class AuthorizeAttributeEx : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return IsUserAuthorized(request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
return IsUserAuthorized(hubIncomingInvokerContext.Hub.Context.Request);
}
protected bool IsUserAuthorized(IRequest thisRequest)
{
try
{
// Within the hub itself we can get the request directly from the context.
//Microsoft.AspNet.SignalR.IRequest myRequest = this.Context.Request; // Unfortunately this is a signalR IRequest, not a ServiceStack IRequest, but we can still use it to get the cookies.
bool perm = thisRequest.Cookies["ss-opt"].Value == "perm";
string sessionID = perm ? thisRequest.Cookies["ss-pid"].Value : thisRequest.Cookies["ss-id"].Value;
var sessionKey = SessionFeature.GetSessionKey(sessionID);
CustomUserSession session = HostContext.Cache.Get<CustomUserSession>(sessionKey);
return session.IsAuthenticated;
}
catch (Exception ex)
{
// probably not auth'd so no cookies, session etc.
}
return false;
}
}

Related

Conditionally Ignore Authorize .NET Core 3.1

I am migrating a web API from .net Framework to .net Core. The old version was able to ignore the Authorize Attribute on the controller if the app was running on a private server. Here is the code for that. I know .net core 3.1 does not offer Custom AuthorizeAttributes. That is not my question.
// A custom AuthroizeAttribute
public class ConditionalAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext httpContext)
{
if (environment_private())
return true;
else
return base.IsAuthorized(httpContext);
}
private bool environment_private()
{
// code that will tell you if you are in your dev environment or not
return Properties.Settings.Default.PrivateServer;
}
}
// How it was called from the controller
[ConditionalAuthorize(Roles = "MyRole")]
[Route(...)]
// More code for controller
I just need a simple way to authorized all requests when running the project on our private server (which is determined by a variable in appSettings.json). I have tried policies, but I face the following difficulties:
1) I couldn't pass the variable in the configuration from the controller to a parameterized authorize attribute.
2) I couldn't inject the configuration into a parameterized authorize attribute.
This effectively eliminated my ability to follow this guide in any way: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.2
This leads to my question: How can I use a value from appSettings.json to override whether the request checks a role or not?
After a lot of research, I found a way to do it using TypeFilterAttribute. Essentially, it's the same way of doing it (using a custom attribute to filter all requests and check the condition within the custom attribute) except I used .net Core-supported methods.
In case you are trying to solve this same issue, here are the exact steps to my solution.
Add two files, "YourAttributeNameAttribute.cs" and "YourFilterNameFilter.cs".
In the "YourAttributeNameAttribute.cs" file, the code is as follows:
public class YourAttributeNameAttribute : TypeFilterAttribute
{
public YourAttributeNameAttribute(string role) : base(typeof(YourFilterNameFilter))
{
Arguments = new object[] { role };
}
}
The code in "YourFilterNameFilter.cs":
public class YourFilterNameFilter : IAuthorizationFilter
{
private readonly string Role;
public YourFilterNameFilter(string role)
{
Role = role;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
// If private server, ignore roles
if (private_server_logic_here)
return;
var user = context.HttpContext.User;
// Check role if on public server
if (!user.IsInRole(Role))
{
context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Unauthorized);
return;
}
}
}
The code for the controller:
[YourAttributeName("role_name")]
[Route("api/my_route")]
[HttpGet]

SignalR: Receive Custom Id on Every Call Including OnConnected

I am using SignalR to relay messages from a WebAPI server back-end to a JavaScript web page. These messages are only relayed to certain users so I need to map the SignalR ConnectionId with the custom id of the user of the webpage.
Currently the WebAPI uses FormsAuthentication and the custom id I need is in the cookie.
Initially I inherited the IUserIdProvider to pull the value off of the cookie:
public class CustomIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
Cookie formsAuthCookie = request.Cookies[FormsAuthentication.FormsCookieName];
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(formsAuthCookie.Value);
var obj = JsonConvert.DeserializeObject(ticket.Name) as JObject;
return (string)obj.GetValue("UserId");
}
catch (Exception)
{
return null;
}
}
}
which worked as far as getting the custom id correctly. But that value was never set on the identity as far as I could tell. I also was unable to edit any of the Identity values due to Context.User.Identity.Name all being readonly.
Edit: Trying the CustomIdProvider again, I correctly get the value out of the cookie but on returning from the GetUserId method, OnConnected is never called.
My next approach was based off of Shaun Xu's blogpost
Set Context User Principal For Customized Authentication In SignalR.
Here is my implementation:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private const string CUSTOM_IDENTITY_KEY = "server.User";
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
string customId;
// This gets the custom id from the cookie.
TryGetCustomId(request, out customId);
// The CustomIdentity class just sets the customId to the name.
request.Environment.Add(CUSTOM_IDENTITY_KEY, new ClaimsPrincipal(new CustomIdentity(customId, true)));
return true;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
string connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
IDictionary<string, object> environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
object obj;
environment.TryGetValue(CUSTOM_IDENTITY_KEY, out obj);
var principal = obj as ClaimsPrincipal;
if (principal?.Identity == null || !principal.Identity.IsAuthenticated)
{
return false;
}
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);
return true;
}
and this actually works to set the Context.User.Identity correctly when both methods are invoked.
However, my problem is that when the user first connects and OnConnected is called, it does not call the AuthorizeHubMethodInvocation and therefore the Context.User.Identity is not available in OnConnected.
I want to be able to access the correct Identity containing my custom id at all stages of the SignalR hub: OnConnected, OnDisconnected, and invoked methods.
Does anyone have a good solution for this?
I have some comments:
1) You should not try to establish your custom identity in authorization stage. It should be the concern of the authentication stage.
2) Implementing custom IUserIdProvider and establishing custom identity id of your Context.User.Identity are separate concerns. The custom IUserIdProvider is just to map any of the properties of your Context.User.Identity as the identifier of user in signalR.
So, to fix your problem. Try establishing your custom identity id at authentication stage. There are many ways to do it depending on how you setup your application. For example:
1) Establishing your custom identity id in Application_PostAuthenticateRequest:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
//Provide custom identity id to your HttpContext.Current.User
//In your case, you may extract that information from your authentication ticket.
}
You could look at this post if you need detailed information: ASP.NET MVC - Set custom IIdentity or IPrincipal
2) Using claims identity, you can return the custom id as a claim, everytime when the browser sends a request to your server, your claims identity is re-established. In the example below, I use owin cookie authentication.
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, user.Id.ToString()));
var id = new ClaimsIdentity(claims, "Cookies");
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
Then in your IUserIdProvider implementation, you can extract the corresponding information in your identity (Name property, claim,...) to use as user identifier in your signalR application.

Possible to bypass servicestack's authentication mechanisms for non standard authentication

My authentication mechanism is in a way different it cannot fit into 1 of ServiceStack's current authentication methods (even overriding method 'TryAuthenticate' does not provide a solution). So would it be possible to authenticate from some arbitrary ServiceStack service?
To give an example:
I open a plain old HTML login page (I am using Angular for the record).
I login in and call my custom ServiceStack service in order to send the non-standard credentials to the server (of course using Angular's http directive).
I validate the credentials myself. If correct I like to hook up into servicestack authentication mechanism and probably have to send back a ServiceStack authentication cookie to the browser. Am I correct?
If someone can make 3 work I can call ServiceStack services which have the authenticate attribute
To be allowed through the [Authenticate] attribute, it needs any one of the registered AuthProviders IsAuthorized() to return true, i.e:
public class CustomAuthProvider : AuthProvider
{
public CustomAuthProvider()
{
this.Provider = "custom";
}
public override bool IsAuthorized(
IAuthSession session, IAuthTokens tokens, Authenticate request=null)
{
return true; //custom logic to verify if this session is authenticated
}
public override object Authenticate(
IServiceBase authService, IAuthSession session, Authenticate request)
{
throw new NotImplementedException();
}
}
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomAuthProvider()
}));
In your Custom Authentication Service you should also save the Users Session with IsAuthenticated=true, e.g:
public object Any(CustomAuth request)
{
//Authenticate User
var session = base.SessionAs<CustomUserSession>();
session.IsAuthenticated = true;
this.SaveSession(session);
}

Web Api 2 Stateless with Users

I believe I understand the basics of sessionless/stateless REST but I am having problems with implementation in Asp.Net Web Api 2 because I haven't used it before. I have set up ApiControllers that use a custom System.Web.Http.AuthorizeAttribute like this.
public class ApiAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization != null)
{
//Set identity??
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
I have a database that contains users and I need to use them for getting privileges to get/post/put/delete things but dont want to use a session. I have never worked with the asp.net Identity so I am not familiar with its features and capabilities.
My idea for implementation is to use user credentials or api secret signing to authenticate and get privileges for a user for every request. The question is, by using a AuthorizeAttribute or something similar, how do i give the controller(s) during that one request the user information if their credentials were correct?
UPDATE:
Is using this.User (ApiController.User) session based or can it be used for that single request. If so, how does one set it
You can use HttpContext.Items to hold user data for current requests.
After setting identity based on Auth header , you can have
System.Web.HttpContext.Current.Items["userdata"]=userDataObject,
Another approach would be write your own action filter for authentication (but take utmost care)and pass data to controller.
public class MyAuthAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//do authorization here
base.OnActionExecuting(filterContext);
// Create object parameter.
filterContext.ActionParameters["userdata"] = new User("John", "Smith");
}
}
then in controller
[MyAuthAttribute]
ActionResult SomeAction(User userdata) //this will have user data
{
}
It looks like that using IPrincipal and setting HttpContext.Current.User will allow the ApiControllers to access that user through using
this.User
with web api not having access to the session
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization != null)
{
//Check user credentials/token here
//Get internal user
IPrincipal principal = new MyOwnCustomPrincipal(internalUser);
HttpContext.Current.User = principal;
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}

Custom authorizations in Web.API

My understanding of ASP.NET MVC is that for authorizations I should use something like -
public class IPAuthorize : AuthorizeAttribute {
protected override bool AuthorizeCore(HttpContextBase httpContext) {
//figure out if the ip is authorized
//and return true or false
}
But in Web API, there is no AuthorizeCore(..).
There is OnAuthorization(..) and the general advice for MVC is not to use OnAuthorization(..).
What should I use for custom authorizations in Web API?
Authorization is done in an authorization filter - that mean you derive from System.Web.Http.AuthorizeAttribute and implement the IsAuthorized method.
You don't implement authorization in a normal action filter because they run later in the pipeline than authorization filters.
You also don't implement authentication in a filter (like parsing a JWT) - this is done even earlier in an extensibility point called MessageHandler.
The method we use for is an custom ApiAuthorize attribute that inherits from System.Web.Http.AuthorizeAttribute. for example:
public class ApiAuthorizeAttribute : AuthorizeAttribute
{
readonly CreditPointModelContext _ctx = new CreditPointModelContext();
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if(Authorize(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext)
{
try
{
//boolean logic to determine if you are authorized.
//We check for a valid token in the request header or cookie.
}
catch (Exception)
{
return false;
}
}
}

Categories

Resources