I've implemented my own Authorize attribute, and I notice that it queries to check permissions when I use [Authorize].
Is there any way I can get that permission and use it in the current controller that applies the Authorize attribute without having to rewrite and requery the code in the controller?
Yes, you can. If you implemented your Authorize attribute as an ActionFilterAttribute you can use the ViewData collection to store information like this :
public class RequireRegistrationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
HttpResponseBase response = filterContext.HttpContext.Response;
if (request != null &&
response != null)
{
bool isAuthenticated = request.IsAuthenticated;
filterContext.Controller.ViewData["IsAuthenticated"] = isAuthenticated;
if (!isAuthenticated)
{
string url = String.Format(
"/?ReturnUrl={0}",
HttpUtility.UrlEncode(request.Url.ToString()));
response.Redirect(url);
}
}
}
}
In the anoteated controller's acrion you can access the field with:
bool isAuthenticated = (bool)(ViewData["IsAuthenticated"] ?? false);
Related
I have the following .cs in order to create some basic authentication in my api. This works fine,but it appears only one time, when i run it for the first time.How do I make it appear again (in every run)?
namespace CMob
{
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var isValid = userName == "chrysa" && password == "1234";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
Thread.CurrentPrincipal = principal;
return;
}
}
HandleUnathorized(actionContext);
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
}
}ยจ
My controller
public class DController : ApiController
{
[BasicAuthentication]
[Route("api/D")]
public IEnumerable<D> Get()
{
using (CM_DataEntities entities = new CM_DataEntities())
{
return entities.Ds.ToList();
}
}
}
Thanks!
"To unauthenticated requests, the server should return a response whose header contains a HTTP 401 Unauthorized status[4] and a WWW-Authenticate field.[5]"
You should refer to https://en.wikipedia.org/wiki/Basic_access_authentication.
I am quite sure you can find the answer you're looking for over there.
Basically, The browser provides authentication, and you have absolutely no control over it.
You have to declare the attribute in WebApiConfig.cs :
config.Filters.Add(new BasicAuthenticationAttribute());
And you have to decorate your Controllers and or Actions :
public class MyController : ApiController
{
[BasicAuthentication]
public string Get()
{
return "Hello";
}
}
It actually depends on what behavior you want to define.
If you wish to use your authentication filter for your whole API, you can add it to the global filter list this way (in WebApiConfig.cs) :
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new BasicAuthenticationAttribute());
}
If you desire to restrict all methods of a controller, decorate it this way :
[BasicAuthentication]
public class RestrictedController : ApiController
{
//Your controller definition
}
Of course you can use it on a single method, this way :
[BasicAuthentication]
public JsonResult GetJsonDataAsAuthenticatedUser()
{
//your method definition
}
You can specify a method which require no authentication with AllowAnonymous decoration :
[BasicAuthentication]
public class RestrictedController : ApiController
{
[AllowAnonymous]
public IActionResult Authenticate()
{
//Your authentication entry point
}
}
You can refer to this link
I am validating a token stored on cookies, so I created a class
public class VIEWAuthorizeAttribute : AuthorizeAttribute
then I overrode the OnAuthorization class
public override void OnAuthorization(AuthorizationContext filterContext)
{
var cookie = filterContext.HttpContext.Request.Cookies.Get("Profile"); //This is working
if (cookie != null && IsValidToken(cookie["Token"]))
{
return;
}
HandleUnauthorizedRequest(filterContext);
}
That works fine for MVC Controllers, but when I try to do something similar for web api controller, I am not able to get the cookies from the request.
public override void OnAuthorization(HttpActionContext actionContext)
{
var foo = actionContext.Request.Headers.Cookies; //that is not working
if (Authorize(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
actionContext.Request.Headers does not have a method Cookies, I also tried with actionContext.Request.Headers.GetCookies("Bar") like this answer, but Header does not have that GetCookies method.
Any idea?
string Authentication = string.Empty;
if (actionContext.Request.Headers.Contains("Cookie_Phone"))
{
Authentication = actionContext.Request.Headers.GetValues("Cookie_Phone")?.FirstOrDefault();
}
To use this extension method the namespace System.Net.Http is needed.
Also, pay attention on the GetCookies(string name) is an extension method that returns any cookie header present in the request that contain a cookie state whose name that matches the specified value. It returns a CookieHeaderValue that contains a collection of CookieValueCollection instances. So you need extra enumeration to get a CookieState by name that contains a value.
Here is the valid code:
public async Task AuthenticateAsync(
HttpAuthenticationContext context,
CancellationToken cancellationToken)
{
var request = context.Request;
var token = request.Headers
.GetCookies("token").FirstOrDefault()
?.Cookies.FirstOrDefault(x=>x.Name == "token")
?.Value;
if (string.IsNullOrEmpty(token))
{
context.ErrorResult = new AuthenticationFailureResult(
"The token is missing.",
request);
return;
}
var principal = await SomeAuthentication(token);
if (principal == null)
{
context.ErrorResult = new AuthenticationFailureResult(
"The principal has not been resolved.",
request);
return;
}
context.Principal = principal;
}
I have created a custom Authorize attribute to authorize users trough a remote web API. After authorization I receive a object with token that is valid for some specific time and is used to access further information and I also get some basic user data like name, surname, role, etc ... which I store in Session.
Everything worked just fine but when I tried using Output Caching the Session I'm accessing in my Authorization Core method is null and application crashes there.
How to solve this problem or perhaps an alternative approach avoiding this as last resort?
Authorize attribute
public class AuthorizeUser : AuthorizeAttribute
{
private class Http401Result : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 401.
context.HttpContext.Response.StatusCode = 401;
context.HttpContext.Response.Write("Session expired, please log in again.");
context.HttpContext.Response.End();
}
}
private readonly string[] users;
public AuthorizeUser(params string[] usrs)
{
this.users= usrs;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool auth = false;
var loggedInUser= httpContext.Session["LoggedInUser"] as User;
if (loggedInUser != null)
auth = true;
return auth;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
filterContext.Result = new Http401Result();
else
filterContext.Result = new RedirectToRouteResult
(
new RouteValueDictionary
(
new
{
controller = "Account",
action = "Login",
}
)
);
}
}
Controller Setup
[AuthorizeUser]
public class SomeController : Controller
{
[HttpPost]
[OutputCache(VaryByParam ="Year", Duration = 3600)]
public async Task<JsonResult> SomeAction(int Year){ ... }
}
I have the follow Custom AuthorizeAttribute:
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Request is an Authenticated Method?
if (filterContext.HttpContext.Request.IsAuthenticated)
{
Debug.WriteLine("Test 1 " + PermissionForm);
if (!CurrentUserHasPermissionForm(PermissionForm))
{
//Deny access code
}
}
}
//...
}
After Login method it redirects to Index page from HomeController. The problem is when use SystemAuthorize Attribute in my HomeController the Form value always come as 0 when it should be 4 (Content).
HomeController method:
[SystemAuthorize(PermissionForm = Form.CONTENT, Permissions = PermissionValue.VIEW)]
public ActionResult Index()
{
return this.View();
}
Login method:
[AllowAnonymous]
[Route("Account/Login")]
public ActionResult Login(LoginViewModel model, string url = "")
{
var user= GetUserAccount(model);
if (user == null)
{
ModelState.AddModelError("", "User not found!");
return View(model);
}
else
{
FormsAuthentication.SetAuthCookie(user.Sign, false);
var authTicket = new FormsAuthenticationTicket(1, user.Sign, DateTime.Now, DateTime.Now.AddMinutes(20), false, JsonConvert.SerializeObject(user));
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
HttpContext.Response.Cookies.Add(authCookie);
return RedirectToAction("Index", "Home");
}
}
Form enum:
public enum Form : short
{
PATIENT = 1,
USERS = 2,
MEDICE = 3,
CONTENT = 4,
}
What I'm doing wrong or missing?
Unfortunately Microsoft made this a bit confusing by combining IAuthorizationFilter with Attribute in the same class. The fact of the matter is that attributes cannot do anything except store meta-data.
The part of MVC that reads the attribute is the IAuthorizationFilter which through some MVC magic is registered with MVC automatically when you place AuthorizeAttribute (or a subclass) on a controller or action.
But the only way to actually read the meta-data from the attribute is to use Reflection. The meta-data is in the same class, but not the same instance of the class. The meta-data is in the Attribute, but the code that executes when the filter runs is in the IAuthorizationFilter, which is a separate instance of the same class.
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
if (actionDescriptor != null)
{
var authorizeAttribute = this.GetSystemAuthorizeAttribute(actionDescriptor);
// If the authorization attribute exists
if (authorizeAttribute != null)
{
// Run the authorization based on the attribute
var form = authorizeAttribute.PermissionForm;
var permissions = authorizeAttribute.Permissions;
// Return true if access is allowed, false if not...
if (!CurrentUserHasPermissionForm(form))
{
//Deny access code
}
}
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Pass the current action descriptor to the AuthorizeCore
// method on the same thread by using HttpContext.Items
filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
base.OnAuthorization(filterContext);
}
private SystemAuthorizeAttribute GetSystemAuthorizeAttribute(ActionDescriptor actionDescriptor)
{
SystemAuthorizeAttribute result = null;
// Check if the attribute exists on the action method
result = (SystemAuthorizeAttribute)actionDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (SystemAuthorizeAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
return result;
}
}
Note that OnAuthorization has some logic in it that you will need to support output caching and the part of the code that checks for [AllowAnonymous], so you should not put your authorization check there, but in AuthorizeCore. But unfortunately, AuthorizeCore isn't passed the ActionDescriptor you need to check whether the attribute exists, so you need the above httpContext.Items hack to ensure it is passed into that method on the same thread.
The Reflection part becomes much more clear if you separate your Attribute into a different class from the IAuthorizationFilter, as in this example.
In our server, CORS is already enabled, so that scripts like ajax may communicate in our API. But this is only effective on API that has no SecureAttribute
This one is working properly:
[CorsPreflightEnabled]
public class DevicesController : ApiController
{
[CorsEnabled]
[HttpPost]
public bool Register(DTO::ClientInfo info)
While this one is always rejected:
[CorsPreflightEnabled]
[Http::Secure]
public class UserController : ApiController
{
[CorsEnabled]
[HttpPost]
public bool AddClaims(Domain::DTO.UserClaim claim)
This is the code for SecureAttribute:
public class SecureAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var calltoken = HttpContext.Current.Request.Headers["Device"] ?? "";
var token = JsonConvert.DeserializeObject<DeviceCallToken>(calltoken) ?? new DeviceCallToken();
var cachetoken = new ClientAuthentication().VerifyDevice(token);
if (cachetoken != null)
{
// if a cachetoken was successfully extracted from our records,
// then store the information into the principal for possible reuse
var principal = AppPrincipal.Current;
var identity = principal.Identity as AppIdentity;
identity.ServiceHeader.SessionId = token.SessionId;
identity.ServiceHeader.ClientKey = cachetoken.ClientKey;
identity.ServiceHeader.DeviceCode = cachetoken.DeviceCode;
identity.ServiceHeader.Merchant = cachetoken.Merchant;
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
return cachetoken != null && !string.IsNullOrWhiteSpace(cachetoken.Salt);
}
}
When I call the API using ajax the Method is set to OPTIONS and the value of calltoken is always null.
Now my question is, how can I bypass checking the security when the method is OPTIONS?
I found that, if I try to put values in calltoken via breakpoint, the IsAuthorized will be called again for the last time, and from there the Device header has now value.
I really hope that I explained myself well. If not, I may have to show some images.
EDIT: Working code
public class SecureAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var calltoken = HttpContext.Current.Request.Headers["Device"] ?? "";
var token = JsonConvert.DeserializeObject<DeviceCallToken>(calltoken) ?? new DeviceCallToken();
var cachetoken = new ClientAuthentication().VerifyDevice(token);
if (cachetoken != null)
{
// if a cachetoken was successfully extracted from our records,
// then store the information into the principal for possible reuse
var principal = AppPrincipal.Current;
var identity = principal.Identity as AppIdentity;
identity.ServiceHeader.SessionId = token.SessionId;
identity.ServiceHeader.ClientKey = cachetoken.ClientKey;
identity.ServiceHeader.DeviceCode = cachetoken.DeviceCode;
identity.ServiceHeader.Merchant = cachetoken.Merchant;
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
else
{
var originalRequest = actionContext.Request;
var isCorsRequest = originalRequest.Headers.Contains("Origin");
if (originalRequest.Method == HttpMethod.Options && isCorsRequest)
{
// Allow to penetrate
return true;
}
}
return cachetoken != null && !string.IsNullOrWhiteSpace(cachetoken.Salt);
}
}
There is no way to bypass the authorisation attribute, that would be pretty insecure if it were possible. Your options are:
Remove the attribute from the methods you need to call.
Pass the correct security headers in the ajax call.
Modify the SecureAttribute class to allow OPTIONS method (but that sounds pretty insecure too)
Create new methods for ajax that don't have the attribute attached.
I'd suggest option 2.