Get Session from SignalR.AuthorizeAttribute to SignalR.Hub - c#

I am working on a SignalR.Hub and I have custom Authorization in an SignalR.AuthorizeAttribute. I have been trying to pass the session I have to retrieve to confirm the User is Authenticated to use the Hub.
I've looked through all of the properties and it seems that they are mostly read-only. I can add something to SignalR.IRequest.Environment but it doesn't appear to be thread-safe and seems improper.
Could I extend the HubCallerContext + Everything that uses it in a way I can tack on my session?
The custom auth
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class HubAuthorize : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return VerifySession(request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
//Could I add something to the HubIncomingInvokerContext?
return VerifySession(hubIncomingInvokerContext.Hub.Context.Request);
}
public bool VerifySession(IRequest request)
{
bool success = false;
string token = "";
bool isApiToken = false;
// Check for token Header Auth
if (string.IsNullOrWhiteSpace(token))
{
token = request.QueryString["X-Custom-Token"];
if (string.IsNullOrWhiteSpace(token))
{
token = request.QueryString["X-Custom-Token"];
isApiToken = true;
}
}
SessionResponse session = null;
if (!string.IsNullOrWhiteSpace(token))
{
session = isApiToken ? ValidateApiToken(token) : ValidateToken(token);
}
if (session != null)
{
//Add Session to request! So I dont have to hit the db again..
//request.Add(new KeyValuePair<string, object>("session", session));
success = true;
}
return success;
}
//... other methods that aren't relevant
}
The Hub
[HubAuthorize]
public class NotificationHub : Hub
{
public void Send(string name, string message)
{
// Use the session here
Clients.All.broadcastMessage(name, message);
}
public override Task OnConnected()
{
Console.WriteLine(Context.ConnectionId);
return base.OnConnected();
}
}
Why have custom authorization if you cant use that to retrieve a session when you verify you are authenticated? Maybe I'm missing something but its pretty frustrating. /endrant

Seems you can't. I still verify the session in HubAuthorize.AuthorizeHubConnection, then in the NotificationHub.OnConnected I build a dictionary of Context.ConnectionId as the key and retrieve the session a secondary time for the value. Feels hackish but there doesn't seem to be a good way to prevent people from accessing the hub without using the .Net built in auth.

Related

Creating Custom AuthorizeAttribute in Web API (.Net Framework)

I'm using OAuth2.0 Owin (password grant) in my WebAPI.My initial token Response is like below
{
"access_token": "_ramSlQYasdsRTWEWew.....................",
"token_type": "bearer",
"expires_in": 17999,
"permissions": {
"user": [
"Add",
"Update",
"Delete"
],
"Product": [
"Read",
"Create"
]
}
}
I've customized the response by creating a new Key called permissions which hold the privileges for the corresponding user.
From here I need to validate each Request from my Resource server,by checking whether the user has enough permissions to call the API using Authorize Attribute.
I found a similar example from here where it deals with Dot net Core, which is not suitable for my case.
The difficult part is that the permission JSON Key is itself making a complex with ArrayList
[CustomAuthorize(PermissionItem.Product, PermissionAction.Read)]
public async Task<IActionResult> Index()
{
return View(Index);
}
public class CustomAuthorize : AuthorizeAttribute {
public AuthorizeAttribute (PermissionItem item, PermissionAction action) {
//Need to initalize the Permission Enums
}
public override void OnAuthorization (HttpActionContext actionContext) {
//Code to get the value from Permissions ArrayList and compare it with the Enum values
}
}
The above is the idea I'm having. But due to the complexity of the Permissions Key and Enum comparison I'm couldn't able to move forward.
Also, there is a question like If the permission for User is Add as well as Update means I need to create two Attribute conditions before my Controller.
Like
[CustomAuthorize(PermissionItem.User, PermissionAction.Add)]
[CustomAuthorize(PermissionItem.User, PermissionAction.Update)]
Which leads to adding more lines of Attributes. So Is there is any way to make it as in a single Conditions with | separated?
[CustomAuthorize(PermissionItem.User, PermissionAction.Update|PermissionAction.Add)]
Why don't you allow your CustomAuthorize constructor to have multiple Permission actions.
public class CustomAuthorize : AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
var currentIdentity = System.Threading.Thread.CurrentPrincipal.Identity;
if (!currentIdentity.IsAuthenticated) {
// redirect to access denied page
}
var userName = currentIdentity.Name;
// step 1 : retrieve user object
// step 2 : retrieve user permissions
// step 3 : match user permission(s) agains class/method's required premissions
// step 4 : continue/redirect to access denied page
}
}
And you'll annotate your class with:
[CustomAuthorize(PermissionItem.User, PermissionAction.Update, PermissionAction.Add)]
I'm not sure, what does OP want to achieve here. If you are relying on HTTP request to provide access rights then that is A BIG SECURITY HOLE. On each request you should retrieve user's access right information from the database and then match is against the required permission of the class/method.
As a rule of thumb you should not rely on request object to tell you what are the permissions that current user have. You should retrieve them from the datastore.
My Implementation of CustomAttribute
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
protected override Boolean IsAuthorized(HttpActionContext actionContext)
{
var currentIdentity = actionContext.RequestContext.Principal.Identity;
if (!currentIdentity.IsAuthenticated)
return false;
var userName = currentIdentity.Name;
using (var context = new DataContext())
{
var userStore = new UserStore<AppUser>(context);
var userManager = new UserManager<AppUser>(userStore);
var user = userManager.FindByName(userName);
if (user == null)
return false;
foreach (var role in permissionActions)
if (!userManager.IsInRole(user.Id, Convert.ToString(role)))
return false;
return true;
}
}
}
You could make us of flags and binary operations to allow you to | the different operations together.
The following code shows a small example of how it could be done
class Program {
static void Main(string[] args) {
Test test = new Test();
CustomAuthorizeAttribute customAuthorizeAttribute = (CustomAuthorizeAttribute)Attribute.GetCustomAttribute(typeof(Test), typeof(CustomAuthorizeAttribute));
customAuthorizeAttribute.Test();
Console.ReadKey();
}
}
[CustomAuthorize(PermissionActions = PermissionAction.Add | PermissionAction.Delete)]
public class Test {
}
public class CustomAuthorizeAttribute : Attribute {
public PermissionAction PermissionActions { get; set; }
public void Test() {
if ((PermissionActions & PermissionAction.Add) == PermissionAction.Add) Console.WriteLine("Add");
if ((PermissionActions & PermissionAction.Delete) == PermissionAction.Delete) Console.WriteLine("Delete");
if ((PermissionActions & PermissionAction.Update) == PermissionAction.Update) Console.WriteLine("Update");
}
}
public enum PermissionAction {
Add = 1,
Update = 2,
Delete = 4
}
Which yields the following output
We have created below API filter for authentication.
Here, "SecretToken", "MerchantKey" this two key passing in API request. we are validating this two from database using "IsValidMerchant" function.
IsValidMerchant this function direct connect with database table where respective value are store
public void OnAuthorization(AuthorizationFilterContext actionContext)
{
const string secretTokenName = "SecretToken";
const string merchentKeyName = "MerchantKey";
bool isValid = false;
if (!actionContext.Filters.Any(item => item is IAllowAnonymousFilter))
{
CPServiceResponse response = new CPServiceResponse();
var secretToken = actionContext.HttpContext.Request.Headers[secretTokenName].FirstOrDefault();
var merchentKey = actionContext.HttpContext.Request.Headers[merchentKeyName].FirstOrDefault();
isValid = this.IsValidMerchant(merchentKey, secretToken,_productCode);
if (isValid == false)
{
response.Status = (int)HttpStatusCode.Unauthorized;
response.Message = Hegic.Shared.Resource.Common.UnauthorizedRequestError;
actionContext.Result = new JsonResult("")
{
Value = new { Status = response }
};
}
}
}

ServiceStack ServerSentEvents restrict access to channel

In my ServiceStack app I would like to deny access to channels for unauthorized users - so even the join event would not fire for an unauthorized client. I am using custom auth provider that does not interact with the DB and is very minimalistic for now (mainly for testing purposes)
public class RoomsAuthProvider : CredentialsAuthProvider
{
private int userId = 0;
public RoomsAuthProvider(AppSettings appSettings) : base(appSettings)
{
}
public RoomsAuthProvider()
{
}
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
if (password == "ValidPassword")
{
return true;
}
else
{
return false;
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
//session.CreatedAt = DateTime.Now;
//session.DisplayName = "CustomDisplayName" + userId;
//session.IsAuthenticated = true;
//session.UserAuthName = session.UserName;
//session.UserAuthId = userId.ToString();
//Interlocked.Increment(ref userId);
//authService.SaveSession(session, SessionExpiry);
//return null;
}
}
Main service piece:
[Authenticate]
public class ServerEventsService : Service
{
...
}
sidenote - I have tried overriding the default DisplayUsername to not be username1...usernameN but no luck. My client code is
var client = new ServerEventsClient("http://localhost:1337/", "home")
{
OnConnect = OnConnect,
OnCommand = HandleIncomingCommand,
OnMessage = HandleIncomingMessage,
OnException = OnException,
OnHeartbeat = OnHeartbeat
}.Start();
client.Connect().Wait();
var authResponse = client.Authenticate(new Authenticate
{
provider = "credentials",
UserName = "test#gmail.com",
Password = "p#55w0rd",
RememberMe = true,
});
client.ServiceClient.Post(new PostChatToChannel
{
Channel = "home", // The channel we're listening on
From = client.SubscriptionId, // Populated after Connect()
Message = "Hello, World!",
});
Even if I skip the authenticate call the other clients will still get onJoin command about not authenticated client when it tries to do an unauthorized post (and get an error). Also when I intentionally do multiple unauthorized users counter grows - assigned username becomes username2, username3 and so on - how can I disable unauthorized users COMPLETELY? Marking my DTOs with Authenticate also didn't change anything. Any ideas are welcome as well as crytics as I'm new to ServiceStack and would like to implement the best practices.
There's already an option to limit access to authenticated users only with:
Plugins.Add(new ServerEventsFeature {
LimitToAuthenticatedUsers = true
});

.Net Web Api - Override AuthorizationFilter

Hello I have a web api controller inside a mvc web site.
I'm trying to allow access to the controller using 2 rules:
User is admin or the request came from local computer;
I'm new to AuthorizationFilterAttribute but I tried to write one that limit access
to local request only:
public class WebApiLocalRequestAuthorizationFilter : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (actionContext.Request.IsLocal())
{
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Content = new StringContent("Username and password are missings or invalid");
}
}
Then I decorated my controller with 2 attributes as
[Authorize(Roles = "Admin")]
[WebApiLocalRequestAuthorizationFilter]
public class ContactController : ApiController
{
public ContactModel Get(int id)
{
ContactsService contactsService = new ContactsService();
return contactsService.GetContactById(id).Map<ContactModel>();
}
}
But as I suspected , now, in order to access the controller I need to be admin and the request should be made from localhost. How can I do it?
Kind regards,
Tal Humy
One solution is to create a class that inherits from AuthorizeAttribute
e.g. something like this
public class MyAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool accessAllowed = false;
bool isInGroup = false;
List<string> roleValues = Roles.Split(',').Select(rValue => rValue.Trim().ToUpper()).ToList();
foreach (string role in roleValues)
{
isInGroup = IdentityExtensions.UserHasRole(httpContext.User.Identity, role);
if (isInGroup)
{
accessAllowed = true;
break;
}
}
//add any other validation here
//if (actionContext.Request.IsLocal()) accessAllowed = true;
if (!accessAllowed)
{
//do some logging
}
return accessAllowed;
}
...
}
Then you can use it like so:
[MyAuthorizeAttribute(Roles = "Support,Admin")]
In the above code, IdentityExtensions checks for, and caches, ActiveDirectory roles which also allows us to fake the current user having roles by changing the cache.

Managing SignalR connections for Anonymous user

I am using SignalR version 2.1.2 with ASP.Net MVC 5 & NServiceBus and have following requirement
There is a signup page (anonymous authentication) in which SignalR is used to send notifications. Every form submit will generate a new connection id which needs to be kept in a collection so that I can send response to the client. Context.User.Identity.Name is empty hence _connections.Add(name, Context.ConnectionId); cannot be used in OnConnected() hub event as given in this post
Similar problem exists in Login page.
If there is a possibility to control the ConnectionId then I could overcome this situation but it looks like new version of SignalR has got rid of connection factory.
I am using Redis cache so one option is to write my own connection management code to keep these connection ids in it.
Second option is to use Forms Authentication in such a way that a 'Anonymous Role' is assigned to these users which restricts the usage to anonymous views/controllers but gives a 'Name' to the user so that Context.User.Identity.Name is not empty. With this I can use built in SignalR mechanism to manage connection ids for me.
This is what we did in BaseAnonymousController
public class BaseAnonymousController : Controller
{
protected override void OnAuthentication(System.Web.Mvc.Filters.AuthenticationContext filterContext)
{
if (filterContext.Controller.GetType().Name == "AccountController" && filterContext.ActionDescriptor.ActionName == "login")
{
Guid result;
if (!string.IsNullOrEmpty(SessionVariables.UserId) && Guid.TryParse(SessionVariables.UserId, out result))
{
//Already a anonymous user, so good to go.
}
else
{
//Seems to be a logged in a user. So, clear the session
Session.Clear();
}
}
//Perform a false authentication for anonymous users (signup, login, activation etc. views/actions) so that SignalR will have a user name to manage its connections
if (!string.IsNullOrEmpty(SessionVariables.UserId))
{
filterContext.HttpContext.User = new CustomPrincipal(new CustomIdentity(SessionVariables.UserId, "Anonymous"));
}
else
{
string userName = Guid.NewGuid().ToString();
filterContext.HttpContext.User = new CustomPrincipal(new CustomIdentity(userName, "Anonymous"));
FormsAuthentication.SetAuthCookie(userName, false);
SessionVariables.UserId = userName;
}
base.OnAuthentication(filterContext);
}
}
and used this class as base class for all of anonymous controllers.
public class AccountController : BaseAnonymousController
{
[AllowAnonymous]
public ActionResult Signup()
{
//Your code
}
[AllowAnonymous]
public ActionResult Login()
{
//Your code
}
[AllowAnonymous]
public ActionResult ForgotPassword()
{
//Your code
}
[AllowAnonymous]
public ActionResult ForgotUsername()
{
//Your code
}
}
In the SignalR hub (nothing extraordinary than what is in SignalR documentation)
public override Task OnConnected()
{
SignalRConnectionStore.Add(Context.User.Identity.Name, Context.ConnectionId);
return base.OnConnected();
}
public override Task OnReconnected()
{
string name = Context.User.Identity.Name;
//Add the connection id if it is not in it
if (!SignalRConnectionStore.GetConnections(name).Contains(Context.ConnectionId))
{
SignalRConnectionStore.Add(name, Context.ConnectionId);
}
return base.OnReconnected();
}
public override Task OnDisconnected(bool stopCalled)
{
SignalRConnectionStore.Remove(Context.User.Identity.Name, Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
This works for both anonymous and authenticated users.
SignalRConnectionStore class and Interface
public interface ISignalRConnectionStore
{
int Count { get; }
void Add(string userName, string connectionId);
IEnumerable<string> GetConnections(string userName);
void Remove(string userName, string connectionId);
}
internal class SignalRConnectionStore : ISignalRConnectionStore
{
private readonly Dictionary<string, HashSet<string>> _connections = new Dictionary<string, HashSet<string>>();
public int Count
{
get
{
return _connections.Count;
}
}
public void Add(string userName, string connectionId)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(connectionId))
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(userName, out connections))
{
connections = new HashSet<string>();
_connections.Add(userName, connections);
}
lock (connections)
{
connections.Add(connectionId);
}
}
}
}
public IEnumerable<string> GetConnections(string userName)
{
if (!string.IsNullOrEmpty(userName))
{
HashSet<string> connections;
if (_connections.TryGetValue(userName, out connections))
{
return connections;
}
}
return Enumerable.Empty<string>();
}
public void Remove(string userName, string connectionId)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(connectionId))
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(userName, out connections))
{
return;
}
lock (connections)
{
connections.Remove(connectionId);
if (connections.Count == 0)
{
_connections.Remove(userName);
}
}
}
}
}
}
Declare a static variable of SignalRConnectionStore in Hub class as below.
public class ProvisioningHub : Hub
{
private static ISignalRConnectionStore SignalRConnectionStore;
public ProvisioningHub(ISignalRConnectionStore signalRConnectionStore)
: base()
{
SignalRConnectionStore = signalRConnectionStore; //Injected using Windsor Castle
}
}
Use Forms Authentication, store a Federated Cookie and store the hub region in the cookie as well..
In SignalR jQuery code, use a jQuery plugin to read HTTP cookie and get the region name and subscribe to notifications.
Alternatively, in your .cshtml, render jQuery with region populated from your View Model.
Note: Use FormsAuthentication.SetAuthCookie as this will create HTTP Only cookie and will be sent in Ajax and non-Ajax calls.

AuthorizeAttribute and parameters

I have a custom AuthorizeAttribute in my application which takes an input parameter bool UserIsOnline. This parameter is used to increase a table field that holds information about the time of the last user interaction, i.e. for ajax requests that are executed behind the scenes I supply a false and for regular request, or user initiated ajax requests, a true value.
This works most of the time but not always. I've read that AuthorizeAttribute is not thread safe which makes me wonder whether this UserIsOnline parameter is wrong because it gets modified by another process before being handled. How would I go about to solve this problem? Should I not use AuthorizeAttribute for this action?
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private MyMembershipProvider _provider = new MyMembershipProvider(); // this class is thread-safe
private bool _userIsOnline = true;
public bool UserIsOnline { get { return _userIsOnline; } set { _userIsOnline = value; } }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
// Check if user is authenticated
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
// Check that the user still exists in database
MyMembershipUser myUser = (MyMembershipUser)_provider.GetUser(user.Identity.Name, _userIsOnline);
if (myUser == null)
{
// User does not exist anymore, remove browser cookie
System.Web.Security.FormsAuthentication.SignOut();
return false;
}
return true;
}
}
You can skip the parameter altogether and use httpContext.Request.IsAjaxRequest
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
// Check if user is authenticated
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
if (!httpContext.Request.IsAjaxRequest())
{
// do your thing in the DB
}

Categories