.Net Web Api - Override AuthorizationFilter - c#

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.

Related

C# .NET 6.0 How to redirect an unauthorized user to an unauthorizedpage

I would like to redirect the unauthorized user to a unauthorizedpage. Authorization is Roles based. For example [Authorize(Roles = "Admin")] I'm looking for a general solution. I don't want to write a redirect in every controllor/endpoint. I'm using Windows Auth with the Negotiate protocol.
I'm using the AuthorizeAttribute. When the user is unauthorized for a view then the application shows a blank HTML page to the user.
I tried multiple CustomAttributes, Configure AccesDeniedPaths, HandleUnauthorizedRequest. But every way ended in a blank HTML page.
I hope someone has an solution.
CustomAuthorizeAttribute
public class CustomAuthorizeAttribute : ActionFilterAttribute
{
public string Roles { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//if user isn't logged in.
if (filterContext.HttpContext.User.Identity == null || !filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("/Unauthorized/");
}
var user = filterContext.HttpContext.User;
//Check user rights here
if (!user.IsInRole(Roles))
{
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.Result = new RedirectResult("/Unauthorized/");
}
}
}
Controller
[CustomAuthorize(Roles = "Admin")]
[HttpGet]
public IActionResult Index()
{
return View();
}
ASP.NET has a defualt [Authorize] attribute which you can add to an arbitrary action.
Eg:
[HttpGet]
[Authorize]
public async Task<ActionResult<IEnumerable<Classes>>> GetClasses()
{
return await _context.Classes.ToListAsync();
}
This will by default redirect the user to the login page if an unauthorized user tries to access a forbidden page.
However, if you want to redirect to a custom page. You could try writing a custom filter attribute like this.
public class CustomAuthorizeAttribute : 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));
}
//Check user rights here
if (userNotRight)
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
and use it in a controller like this,
[HttpGet]
[CustomAuthorize]
public async Task<ActionResult<IEnumerable<Classes>>> GetClasses()
{
return await _context.Classes.ToListAsync();
}
If you would like to authorize based on roles for the custom filter attribute. You could either
1. write a custom authorize attribute and add it along with the filter attribute.
public class AuthorizeAdmin : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authenticated
return false;
}
var user = httpContext.User;
if (user.IsInRole("Admin")) // Your desired role
{
return true;
}
}
}
controller:
[HttpGet]
[CustomAttribute]
[AuthorizeAdmin]
public async Task<ActionResult<IEnumerable<Classes>>> GetClasses()
{
return await _context.Classes.ToListAsync();
}
2. you could directly check it along with the custom filter attribute
public class CustomAuthorizeAttribute : ActionFilterAttribute
{
// Check if is in "Admin" role
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authenticated
return false;
}
var user = httpContext.User;
if (user.IsInRole("Admin")) // Your desired role
{
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));
}
//Check user rights here
if (userNotRight)
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
}
controller:
[HttpGet]
[CustomAttribute]
public async Task<ActionResult<IEnumerable<Classes>>> GetClasses()
{
return await _context.Classes.ToListAsync();
}

CustomAuth attribute as simply as possible

I wanted to test very simple auth layer
public class CustomAuth : AuthorizeAttribute, IAuthorizationFilter
{
public CustomAuth()
{
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var cookies = context.HttpContext.Request.Cookies;
var ok = cookies["Auth0"] == "asdf";
if (!ok)
{
context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
return;
}
}
}
[CustomAuth]
public IActionResult Index()
{
return View();
}
And when there's no cookie named Auth0 with value asdf then everything work's fine, but when I add it then No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
I tried setting context.Result = ...; to e.g new OkResult() or RedirectToActionResult and it worked, but I just want to let him go straight to that Index instead of moving everything from that action to that OnAuthorization method
how can I achieve that?
For CustomAuth, it inherited from AuthorizeAttribute. The Authentication middleware will check the identity by default authentication scheme.
If you prefer go to Index without configuring any authentication, you could try change AuthorizeAttribute to Attribute like
public class CustomAuth : Attribute, IAuthorizationFilter
{
public CustomAuth()
{
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var cookies = context.HttpContext.Request.Cookies;
var ok = cookies["Auth0"] == "asdf";
if (!ok)
{
context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
return;
}
}
}

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 }
};
}
}
}

Allow user to visit [Authorize] pages - MVC

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
{
}

CustomAuthorize Attribute Not Updating After Adding/Changing User

I'm having an issue with an internal MVC site. I may have titled this wrong as I don't know exactly where the issue lies. I have the following custom authorize attribute:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public CustomAuthorizeAttribute(params string[] roles)
{
using (var dataLayer = new CarrierBoundEntities())
{
string userNames = string.Empty;
foreach (var user in dataLayer.tbl_PremiumWriteOffs_Users)
{
if (roles.Contains(user.Role))
{
userNames += user.Username + ",";
}
}
if (userNames.Length > 0)
{
// Remove last comma
userNames.Remove(userNames.Length - 1);
}
Users = userNames;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
The datalayer is the default DbContext created by the Entity Framework. There is a view that allows users with an admin role to add/edit users. Here is the controller that handles a post to edit a user:
[HttpPost]
[ValidateAntiForgeryToken]
[CustomAuthorize(new string[] { "Admin" })]
public ActionResult UsersEditView(UsersVM viewModel)
{
using (var dataLayer = new CarrierBoundEntities())
{
var userToEdit = dataLayer.tbl_PremiumWriteOffs_Users.SingleOrDefault(x => x.ID == viewModel.UserSubmit.ID);
if (userToEdit == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
// If the username was changed then check and display error if it's the same as another entry
if (userToEdit.Username.Equals(viewModel.UserSubmit.Username, StringComparison.OrdinalIgnoreCase) == false)
{
foreach (var user in dataLayer.tbl_PremiumWriteOffs_Users)
{
if (user.Username.Equals(viewModel.UserSubmit.Username, StringComparison.OrdinalIgnoreCase))
{
ModelState.AddModelError("UserSubmit.Username", "A user with this username already exists.");
break;
}
}
}
if (ModelState.IsValid)
{
userToEdit.Username = viewModel.UserSubmit.Username;
userToEdit.Role = viewModel.UserSubmit.Role;
userToEdit.UserLastModified = this.User.Identity.Name;
userToEdit.DateLastModified = DateTime.Now;
dataLayer.SaveChanges();
return RedirectToAction("UsersView");
}
viewModel.RoleSelect = GetRoleSelectList();
return View(viewModel);
}
}
Now when I run it locally on my machine things work fine, but when deployed on a server something doesn't get updated when a new user is added or the role of an existing user is changed. New users still don't get access to any part of the site, and a user that is changed from admin to user will still have access to the admin areas. It stays this way until the app is restarted on the server.
The odd thing is that after making a change, the change is visible on both the front end and back end, so it seems that both the database and entity context are being updated fine. So I'm thinking it might be the custom authorize attributes that aren't updating with the new list of usernames, but I really have no idea and am having trouble debugging since it works as it should locally.
Any help would be greatly appreciated.
The way you are trying to extend the AuthorizeAttribue is wrong. You need to do something like this:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private string[] _roles;
public CustomAuthorizeAttribute(params string[] roles)
{
_roles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.User;
var isAuthorized = false;
// check in the database to see if the user
// is in one of the Roles in _roles and therefore authorized...
if(/* some code to check if the user is authorized */)
{
isAuthorized = true;
}
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}

Categories