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
}
Related
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.
I have an action that is being authorized as following:
[Authorize(Roles = "Admin, Agency, Subscribed, Normal")]
public ActionResult LocationProfile()
{}
what I need is to add another filter that gets executed before the authorization filter and if the result is true is doesn't execute the authorize attribute and proceeds to execute my action directly (LocationProfile())
is there any way to accomplish this task
You will have to roll your own version of the Authorize attribute that has that functionality built in. Included from the C# corner post linked above:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
Entities context = new Entities(); // my entity
private readonly string[] allowedroles;
public CustomAuthorizeAttribute(params string[] roles)
{
this.allowedroles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorize = false;
foreach (var role in allowedroles)
{
var user = context.AppUser.Where(m => m.UserID == GetUser.CurrentUser/* getting user form current context */ && m.Role == role &&
m.IsActive == true); // checking active users with allowed roles.
if (user.Count() > 0)
{
authorize = true; /* return true if Entity has current user(active) with specific role */
}
}
return authorize;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
In the AuthorizeCore method, you need to add your check to account for the situation describe.
My project got pages with [Authorize] where user have to log in to visit those pages.
Upon successful login with same userid and password as in database, the current users id get stored in session. But how do I do I authenticate/allow user to visit pages with [Authorize]?
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(User u)
{
if (ModelState.IsValid) //this is check validity
{
using (UserEntities db = new UserEntities())
{
var v = db.Users.Where(a=>a.UserName.Equals(u.UserName) && a.Password.Equals(u.Password)).FirstOrDefault();
if (v != null)
{
Session["LoggedUserID"] = u.Id.ToString();
Session["UserFullname"] = u.Name.ToString();
return RedirectToAction("AfterLogin");
}
}
}
return View(u);
}
Any help is much appreciate. Thanks.
If you absolutely want to manage login and security yourself using Session, You can create your own action filter which checks whether session has a user id set to it.
Something like this
public class AuthorizeWithSession : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Session == null ||
context.HttpContext.Session["LoggedUserID"]==null)
{
context.Result =
new RedirectToRouteResult(new RouteValueDictionary(
new {controller = "Account", action = "Login"}));
}
base.OnActionExecuting(context);
}
}
Now decorate this action filter on your secure actions/controllers
[AuthorizeWithSession]
public class TeamController : Controller
{
}
You should have your own role management if you want to control what the users can do.
Each user should have one or more roles, each role can have a set of permissions and you can create an action filter that inherits from AuthorizeAttribute to make sure it is executed as early as possible.
Inside the AuthorizeCore method of the AuthorizeAttribute , you will see if the user is authenticated or not, and if he is authenticated then you can read his identity, read his roles and permissions from the database and compare it to a value passed to the role.
ex:
public class RequireRoleAttribute : AuthorizeAttribute
{
public RoleEnum[] RequiredRoles { get; set; }
public RequireRoleAttribute()
{
}
public RequireRoleAttribute(params RoleEnum[] roles)
: this()
{
RequiredRoles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var principle = httpContext.User;
if (principle == null || principle.Identity == null || !principle.Identity.IsAuthenticated)
{
return false;
}
if (RequiredRoles != null)
{
if (!HasRole(RequiredRoles))
{
httpContext.Response.Redirect("/AccessDenied");
}
}
return base.AuthorizeCore(httpContext);
}
public bool HasRole(RoleEnum[] roles)
{
foreach (var role in roles)
{
if (HasRole(role))
return true;
}
return false;
}
public bool HasRole(RoleEnum role)
{
return true if the user role has the role specified (read it from database for example)
}
}
Then in your controller, just annotate the controller or action with the attribute
[RequireRole(RoleEnum.Administator)]
public class MySecureController : Controller
{
}
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.
I was using a custom authorize attribute, to restrict users without subscription from accessing some actions
public class IsSubscriptionActive : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//check if user logged in , if not return false
//get user object from request
if(UserObject.IsSubscriptionActive)
return true;
else
return false;
}
}
The problem here is that doing this redirects users to login page regardless of whether the user is logged in or not.
So, I want to use the default authorize attribute as it is, but have another attribute which will check for subscription status and redirect.
How can I do this?
Not tested but try it out:
public class IsSubscribedAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity == null ||
!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result =
new RedirectResult(System.Web.Security.FormsAuthentication.LoginUrl
+ "?returnUrl=" +
filterContext.HttpContext.Server.UrlEncode
(filterContext.HttpContext.Request.RawUrl));
}
if (isSubscribed)//check subscription here.
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new HttpUnauthorizedResult();
}//you can set the statuscode/result as you like?
}
}