I want to create a Web API MVC.
This API will authorize TOKEN JWT, and I want to create my own Authorize attribute like CanRead, CanModify, CanWrite.
Three attributes just inherit Attribute class (no AuthorizeAttribute), is it ok ?
My application have complicates role and permission so I want to customize all about authorization and authentication.
I want to manage the permission dynamic
So how can I do it ?
Will I access database from attributes (CanRead or CanModify) to check permission
Create a custom AuthorizeAttribute instead. an example below.
public class KeyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
string key = httpContext.Request["X-Key"];
return ApiValidatorService.IsValid(key);
}
}
public static class ApiValidatorService
{
public static bool IsValid(string key)
{
int keyvalue;
if (int.TryParse(key, out keyvalue))
{
return keyvalue % 2137 == 7;
}
return false;
}
}
Taken from Jon Galloway's blog. I don't know specifically how you are authorizing, but if you create a class with:
public bool CanRead { get; set; }
public bool CanWrite { get; set; }
public bool CanModify { get; set; }
And then within the AuthorizeCore method, determine based on the setting if the user has the right permission.
Related
I am familiar with roles and authentication attributes in MVC, but as I am adding more and more information onto my database I think I ma going to run into a problem with primary keys being unencrypted or accessible.
I am using identity 2.1, so when a user is logged in I have access of their UserId and their CustomerID but my concern is that any user can go to /Customers/Delete/3 or any CustomerID and have access. Even if I created a GUID id or other encryption it could still be vulnerable to brute force attacks.
Is there a way in MVC to implement a check to only allow the current user to load pages that are related to them?
You can add extra field say "CreatedByUserId" to database table and when user access page check if CreatedByUserId matches with user id of logged in user or not.
You should be checking if the current logged in user has access to any of the information before you try and manipulate data. For example...
public async Task<HttpResponseMessage> DeleteCustomer(string customerId)
{
var appUser = await _authRepository.FindUser(User.Identity.GetUserName());
if(!_customerRepository.CanDeleteCustomer(appUser.Id, customerId){
return BadRequest();
}
// they have access so do what you need to do down here..
}
You can create a custom Authorize Attribute and a table in the database in which you store which user is allowed what Pages (Actions) or Controllers and then check that table while authorizing that whether the user is authorized for that Page/Controller. I have created an example for you in which I used Custom Authorize Attribute named MyAuthorizeAttribute and a database table named PageRoles.
Custom Authorize Attribute:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
readonly ApplicationDbContext _db = new ApplicationDbContext();
string _pageName;
public MyAuthorizeAttribute(string pageNameFromController)
{
_pageName = pageNameFromController;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var userId = httpContext.User.Identity.GetUserId();
var pageRoles = db.PageRoles.Where(m => m.UserId == userId);
foreach (var item in pageRoles)
{
if (item.PageName == _pageName && item.UserId == userId)
{
return base.AuthorizeCore(httpContext);
}
}
return false;
}
}
Model used:
public class PageRole
{
public int Id { get; set; }
public string UserId { get; set; }
public string PageName { get; set; }
public virtual ApplicationUser User { get; set; }
}
and then you will just have to use the attribute on your controllers just like you use Authorize attribute:
[MyAuthorize("Home")]
public class HomeController : Controller
{ }
I'm use to custom authorize attribute like
public class AdminAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return (base.AuthorizeCore(httpContext) && User.IsCurrentUserAdmin());
}
}
how to check is action call current user or not
as an example:
[OnlyYouself]
public ActionResult ViewUser(int userId)
{
...
}
means userId = current user
else redirect to previous view
You might want to build a custom attribute that looks like this:
public class AllowCurrentUserAttribute: AuthorizeAttribute
{
public string Field { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
return (base.AuthorizeCore(httpContext) && filterContext.HttpContext.User.Identity.Name == filterContext.HttpContext.Request.QueryString[Field];
}
And then use it like this:
[AllowCurrentUser(Field = "userId")]
public ActionResult ViewUser(int userId)
{
...
}
There are probably better ways to check the request properties, though. Also, you may want to have a default value of "id" for the field, assuming you use the default MVC routing. If your id field is called something else, you'll want to change it.
actionContext.RequestContext.Principal.Identity
I have a windows authentication MVC app that needs the username to do a lookup to determine if links are visible and set authorization. Note: I do visibility/Authorization with roles as well.
I need the username so I am currently doing it in OnAuthentification (not sure if this is the right place). I am splicing the username down to put it on the main page and say welcome, User. (presentation purposes)
[Authorize]
public abstract class ApplicationController : Controller
{
public static bool IsApprover;
protected override void OnAuthentication(AuthenticationContext filterContext)
{
base.OnAuthentication(filterContext);
if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated == true)
{
string userName = User.Identity.Name.Remove(0, 16).Replace('.', ' ').ToLower();
HttpContext.Application["UserName"] = TitleCase(userName, "Nothing");
//Initialize Values
HttpContext.Application["IsApprover"] = false; //used for link visibility
IsApprover = false; //used for Authorization
//do db lookup and set IsApprover values
}
}
}
So, I set the values above. I am not including the entity framework code just to be brief. The above works fine and every controller inherits from ApplicationController.
I also have
public class CustomAuthorizationValue : AuthorizeAttribute
{
private bool localIsAllowed;
public CustomAuthorizationValue(bool isAllowed)
{
localIsAllowed = isAllowed;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Request.IsLocal)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authorized => no need to go any further
return false;
}
return localIsAllowed;
}
return false;
}
}
For Authorization I use:
[CustomAuthorizationValue(IsApprover)]
public ActionResult Approve()
{
//code
}
For Visibility in Razor I use
#if((bool)#HttpContext.Current.Application["IsApprover"] == true)
{
<li>Approve (#HttpContext.Current.Application["ApproveCount"])</li>
}
This works fine but I have 2 different variables to use,
one for visibility (HttpContext.Current.Application["IsApprover"])
and
one for Authorization (IsApprover).
Is there a more elegant solution?
Is there another place to put the code rather than override void OnAuthentication?
Is there a way I can just set 1 variable for visibility and Authorization rather than having 2?
Is this the best practice or am I way off?
The above works fine and every controller inherits from
ApplicationController.
Hmmmm. You are storing user specific information information in the wrong scope:
HttpContext.Application["UserName"] = TitleCase(userName, "Nothing");
HttpContext.Application["IsApprover"] = false;
In ASP.NET, the Application scope is shared among ALL users of your website. So you have a concurrency issue here.
You should use the HTTP Context scope instead:
HttpContext.Items["UserName"] = TitleCase(userName, "Nothing");
HttpContext.Items["IsApprover"] = false;
Is there a more elegant solution?
You could use a view model:
public class MyViewModel
{
public string UserName { get; set; }
public bool IsApprover { get; set; }
}
and then have a couple of extension methods to work more easily:
public static class HttpContextExtensions
{
private const string MyViewModelKey = "__MyViewModel__";
public static MyViewModel GetMyViewModel(this HttpContextBase context)
{
return (MyViewModel)context.Items[MyViewModelKey];
}
public static void SetMyViewModel(this HttpContextBase context, MyViewModel model)
{
context.Items[MyViewModelKey] = model;
}
}
and then use those extension methods:
if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
{
string userName = User.Identity.Name.Remove(0, 16).Replace('.', ' ').ToLower();
bool isApprover = ... do db lookup and set IsApprover value
var model = new MyViewModel
{
UserName = TitleCase(userName, "Nothing"),
IsApprover = isApprover,
}
this.HttpContext.SetMyViewModel(model);
}
and in your view:
#if(HttpContext.GetMyViewModel().IsApprover)
{
<li>
<a href="#Url.Action("Approve", "Approve")">
Approve (#HttpContext.Current.Application["ApproveCount"])
</a>
</li>
}
NOTE: In this anchor text once again you seem to be using the Application scope to store user specific information such as ApproveCount which we discussed earlier.
Is this the best practice or am I way off?
Well, I would probably use claims based authentication and store this information (IsApprover, ...) as claims in the current user.
i have some controlers that provide access only to users that are in Admin role:
[Authorize(Roles = "Administrators")]
controler im talking about displays company details for customers and i want to provide access to that controler by some url for example:
www.mysite.com/Company/123?code=0932840329809u0932840
generating code will not be a problem, problem is what is the best solution to provide access to controler via that secret url AND access without secret url only for administrators?
thnx!
You could create a custom attribute filter by extending the AuthorizeAttribute.
Something like:
public class CustomAuthorizeAttribute : AuthorizeAttribute {
public string Code { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext) {
if (base.AuthorizeCore(httpContext)) {
return true;
}
string code = Code ?? GetCode() //parse you code as a parameter or get it from another method
if (httpContext.Request["code"] == code) {
return true;
}
return false;
}
}
//I wouldn't recommend parsing the code like this, I would get it in your action filter
[CustomAuthorizeAttribute(Code="0932840329809u0932840")]
public ActionResult Index() {
return View();
}
Have a look at http://schotime.net/blog/index.php/2009/02/17/custom-authorization-with-aspnet-mvc/
How can I include a user regardless of his role, dependent on a matching userID, and not always same user:
[Authorize(Roles="Group1") AND userID=uniqueID]
You won't be able to do this with the default AuthorizeAttribute. You will need to extend AuthorizeAttribute with a custom class that adds the user behavior. Typically it uses named users, but you could provide an alternative. Normally if you supply both Users and Roles, it will require that the user be in the list of users and have one of the indicated roles.
public class UserOrRoleAuthorizeAttribute : AuthorizeAttribute
{
public int? SuperUserID { get; set; }
protected override bool AuthorizeCore( HttpContextBase httpContext )
{
if (base.AuthorizeCore( httpContext ))
{
var userid == ...get id of current user from db...
return userid == this.SuperUserID;
}
return false;
}
}
Used as:
[UserOrRoleAuthorize(Roles="Admin",SuperUserID=15)]
public ActionResult SomeAction() ...
Note that you could also add in some way of specifing where to look for the id for this action, .i.e.,
Table="Admins",Column="AdminID",MatchProperty="Company",MatchParameter="company"
then put some code into the attribute to look up the value in the property table and column and comparing it to the specified RouteValue entry instead of hard-coding it.
You could write a custom Authorize filter (implement IAuthorizationFilter)
Your custom Authorize filter could take the userId as a parameter.
Something like
public class
YourAuthorizeFilterAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public string UserId { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
if(filterContext.HttpContext.User.Identity.Name != UserId &&
!filterContext.HttpContext.User.IsInRole(base.Roles))
{
filterContext.Result = new RedirectResult("/Account/LogOn");
}
}
}
Then use your own filter like so
[YourAuthorizeFilter(UserId = "theuser", Roles ="Group1")]
Kindness,
Dan