I am interested how to do a MVC WEB API autorization. I have checked basic authentication , but I have a different scenario. In my case login params are expected as an action parameter and not inside header.
namespace Test.Controllers
{
public class TestController : Controller
{
[RequireHttps]
[Authorize]
public void TestRequest(int actionParam, string username, string token, int appID)
{
something.......
}
}
}
I have also found this explanation http://www.codeproject.com/Tips/867071/WebAPI-Security-Custom-Authorization-Filters but would like to know is it possible to access action parameters instead of header value from Authorize?
Simply get the query string parameters in your OnAuthorization override either from the HttpActionContext or from HttpContext.Current.Request:
see: How to get Request Querystring values?
public override void OnAuthorization(HttpActionContext actionContext)
{
var queryString = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query.Substring( 1 ));
var username = queryString["username"];
}
or see: Accessing QueryString in a custom AuthorizeAttribute
Add using System.Web; then:
public override void OnAuthorization(HttpActionContext actionContext)
{
var username = HttpContext.Current.Request.QueryString["username"];
}
Related
I am currently working on a POST controller. In the past I've handled logic regarding authentication in the controller itself like this:
[HttpPost]
public HttpResponseMessage Post([FromBody] Foo foo)
{
if (foo.bar !== user.bar){
return;
}
I am not the best c# programmer, so have no clue how this should be handled. While researching I stumbled upon Policies. So I already use a [Authenticated] tag above the controller, but based on if the foo.bar in this example is the same as me.bar I am not allowed to make this post. (So the authenticated tag is for authentication but I want to change the Authorization)
Is it possible that I can make a [Policy=("fooPoster")] and can use the body of the post in there to determine whether I am authorized or not to access the post, or can I only access the global state to determine it?
You can try custom authorization. Refer to the code below.
[HttpPost]
[CustomAuthorization(Foo.bar)]
public HttpResponseMessage Post([FromBody] Foo foo)
{
if (foo.bar !== user.bar)
{
return;
}
}
public class CustomAuthorizationAttribute : AuthorizeAttribute
{
private readonly string allowedroles;
public CustomAuthorizationAttribute(string roles)
{
this.allowedroles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorize = false;
if (Me.bar != allowedroles)
{
authorize = true;
}
return authorize;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
For more details you can go through here :
I have the custom AuthorizeAttribute where I need to use one of the business layer services to validate some data in the database before giving user a permission to view the resource. In order to be able to allocate this service within the my AuthorizeAttribute I decided to use service location "anti-pattern", this is the code:
internal class AuthorizeGetGroupByIdAttribute : AuthorizeAttribute
{
private readonly IUserGroupService _userGroupService;
public AuthorizeGetGroupByIdAttribute()
{
_userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
}
//In this method I'm validating whether the user is a member of a group.
//If they are not they won't get a permission to view the resource, which is decorated with this attribute.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
return _userGroupService.IsUserInGroup(currentUserId, groupId);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContex)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(actionContex);
}
else
{
actionContex.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
I have couple of other attributes like this in my application. Using service locator is probably not a good approach. After searching the web a little bit I found some people suggesting to use IAuthorizationFilter with dependency injection instead. But I don't know how to write this kind of IAuthorizationFilter. Can you help me writing IAuthorizationFilter that will do the same thing that the AuthorizeAttribute above?
So after struggling for a while I think I managed to resolve this issue. Here are the steps you have to do in order to that:
1) First you have to make GetGroupByIdAttribute passive, and by passive I mean an empty attribute without any logic within it (it will be used strictly for decoration purposes)
public class GetGroupByIdAttribute : Attribute
{
}
2) Then you have to mark a controller method, for which you want to add authorization, with this attribute.
[HttpPost]
[GetGroupById]
public IHttpActionResult GetGroupById(int groupId)
{
//Some code
}
3) In order to write your own IAuthorizationFilter you have to implement its method ExecuteAuthorizationFilterAsync. Here is the full class (I included comments to guide you through the code):
public class GetGroupByIdAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get; set; }
private readonly IUserGroupService _userGroupService;
//As you can see I'm using a constructor injection here
public GetGroupByIdAuthorizationFilter(IUserGroupService userGroupService)
{
_userGroupService = userGroupService;
}
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
//First I check whether the method is marked with the attribute, if it is then check whether the current user has a permission to use this method
if (actionContext.ActionDescriptor.GetCustomAttributes<GetGroupByIdAttribute>().SingleOrDefault() != null)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
//If the user is not allowed to view view the resource, then return 403 status code forbidden
if (!_userGroupService.IsUserInGroup(currentUserId, groupId))
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
}
}
//If this line was reached it means the user is allowed to use this method, so just return continuation() which basically means continue processing
return continuation();
}
}
4) The last step is to register your filter in the WebApiConfig.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Here I am registering Dependency Resolver
config.DependencyResolver = ServiceLocator.Instance.DependencyResolver;
//Then I resolve the service I want to use (which should be fine because this is basically the start of the application)
var userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
//And finally I'm registering the IAuthorizationFilter I created
config.Filters.Add(new GetGroupByIdAuthorizationFilter(userGroupService));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Now, if needed, I can create additional IActionFilters that use IUserGroupService and then inject this service at the start of the application, from WebApiConfig class, into all filters.
Perhaps try it like shown here:
Add the following public method to your class.
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// gets the dependecies from the serviceProvider
// and creates an instance of the filter
return new GetGroupByIdAuthorizationFilter(
(IUserGroupService )serviceProvider.GetService(typeof(IUserGroupService )));
}
Also Add interface IFilterMetadata to your class.
Now when your class is to be created the DI notices that there is a CreateInstance method and will use that rather then the constructor.
Alternatively you can get the interface directly from the DI in your method by calling
context.HttpContext.Features.Get<IUserGroupService>()
I have created an ASP.Net MVC project with WebApi option. Then modified the values controller with the code below:
public class ValuesController : ApiController
{
static List<string> data = initList();
private static List<string> initList()
{
var ret = new List<string>();
ret.Add("value1");
ret.Add( "value2" );
return ret;
}
// GET api/values
public IEnumerable<string> Get()
{
return data ;
}
// GET api/values/5
public string Get(int id)
{
return data[id];
}
// POST api/values
public void Post([FromBody]string value)
{
data.Add(value);
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
data[id] = value;
}
// DELETE api/values/5
public void Delete(int id)
{
data.RemoveAt(id);
}
}
When I am running the project and navigating to API/values URL, the following image is showing error.
.
The error description in text is:
<Error>
Authorization has been denied for this request.
</Error>
Have a look at the following article about
Authentication and Authorization in ASP.NET Web API
It will explain the different ways of how to use the [Authorize] and [AllowAnonymous] attribute on your controller/actions and any configurations you would need to do.
The following was taken from the linked article above:
Using the [Authorize] Attribute
Web API provides a built-in authorization filter,
AuthorizeAttribute. This filter checks whether the user is
authenticated. If not, it returns HTTP status code 401 (Unauthorized),
without invoking the action.
You can apply the filter globally, at the controller level, or at the
level of inidivual actions.
Globally: To restrict access for every Web API controller, add the
AuthorizeAttribute filter to the global filter list:
public static void Register(HttpConfiguration config){
config.Filters.Add(new AuthorizeAttribute());
}
Controller: To restrict access for a specific controller, add the
filter as an attribute to the controller:
// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
public HttpResponseMessage Get(int id) { ... }
public HttpResponseMessage Post() { ... }
}
Action: To restrict access for specific actions, add the attribute to
the action method:
public class ValuesController : ApiController
{
public HttpResponseMessage Get() { ... }
// Require authorization for a specific action.
[Authorize]
public HttpResponseMessage Post() { ... }
}
Alternatively, you can restrict the controller and then allow
anonymous access to specific actions, by using the [AllowAnonymous]
attribute. In the following example, the Post method is restricted,
but the Get method allows anonymous access.
[Authorize]
public class ValuesController : ApiController {
[AllowAnonymous]
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
}
In the previous examples, the filter allows any authenticated user to
access the restricted methods; only anonymous users are kept out. You
can also limit access to specific users or to users in specific roles:
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}
So, I've been dealing with this error for awhile.
I didn't understand it at first, so I just removed and lived with it.
I finally got sick of it, because it's rather stupid. Microsoft wants a user to be authorized before they have signed in.
My error was looking for GET method which asks for HomeTown. In my case, I had changed it to CityCode.
Since the user is not logged in, there is no CityCode to GET. So, you get either a 402 or a 500 Resource Not Found.
I still don't understand it so, I gave CityCode some default data. So, from MeController I put the following code:
Public Function [Get]() As GetViewModel
Dim userInfo As ApplicationUser = UserManager.FindById(User.Identity.GetUserId())
Return New GetViewModel() With {.CityCode = "94110"}
End Function
App loads completely error free now.
This is a quick fix, not a certified solution.
What steps must be done to implement basic authentication in ASP.NET MVC 5?
I have read that OWIN does not support cookieless authentication, so is basic authentication generally possible?
Do I need a custom attribute here? I am not sure about how these attributes work.
You can use this simple yet effective mechanism using a custom ActionFilter attribute:
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
this.Username = username;
this.Password = password;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
/// thanks to eismanpat for this line: http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/#comment-2507605761
filterContext.Result = new HttpUnauthorizedResult();
}
}
It can be used to put under Basic Authentication a whole controller:
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public class HomeController : BaseController
{
...
}
or a specific ActionResult:
public class HomeController : BaseController
{
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public ActionResult Index()
{
...
}
}
In case you need additional info check out this blog post that I wrote on the topic.
You can do this with a custom attribute. There is an implementation of a custom attribute that supports base authentication in the open source project SimpleSecurity, which you can download here. There is a reference application to demonstrate how it is used. It was originally developed to work with SimpleMembership in MVC 4 and has been recently ported to use ASP.NET Identity in MVC 5.
I wanted to amend the answer shared by Darkseal, because that code has a major security flaw. As written, that action filter does not actually terminate the request when res.End() is called. The user is prompted for credentials and a 401 response is returned if the credentials don't match, but the controller action is still executed on the server side. You need to set the filterContext.Result property to something in order for the request to terminate properly and not continue to the action method.
This was particularly bad for my situation, as I was trying to protect a web service endpoint that receives a data feed from a third party. As written, this action filter didn't protect anything because the data was still being pushed through my action method.
My "quick fix" is below:
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
this.Username = username;
this.Password = password;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
var res = filterContext.HttpContext.Response;
res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
filterContext.Result = new HttpUnauthorizedResult();
}
}
Great answer from #Darkseal. Here's the same code repurposed for use with ASP.NET Web API (close cousin to MVC). Same idea, slightly different namespaces and context classes. Add it to your classes and methods in exactly the same way.
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
Username = username;
Password = password;
}
public override void OnActionExecuting(HttpActionContext filterContext)
{
var req = filterContext.Request;
var auth = req.Headers.Authorization;
if (auth?.Scheme == "Basic")
{
var cred = Encoding.ASCII.GetString(Convert.FromBase64String(auth.Parameter)).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
filterContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", BasicRealm ?? "YourRealmName"));
}
}
HTTP basic authentication doesn't require a cookie. It's based on a HEADER in the HTTP request. The header is named Authorization and its value should be username and password combined into a string, "username:password" (all base64 encoded).
Sincerely I never used basic authentication with ASP.NET MVC, but I used Web API to create a custom attribute (you can start from here for WebAPI or here for MVC).
you can try this package on Nuget (AuthPackage)
its enables you to add authentication to your asp.net mvc easily.
install package using Package Manager Console:
Install-Package AuthPackage
add Connection String to your Web.config in (appSettings):
<add key="connectionString" value="connectionStringHere" />
you're ready to register users, login, logout
example:
public async Task<ActionResult> SignIn()
{
var context = System.Web.HttpContext.Current;
AuthUser authUser = new AuthUser(context);
await authUser.SignIn("waleedchayeb2#gmail.com", "123456");
return RedirectToAction("Index", "Home");
}
You can read the Documentation here
The Darkseal’s answer
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
has 2 disadvantages :
name and password are hardcoded and they support only single user.
More flexible solution should support multiple username/password pairs stored in configuration.
Microsoft describes a sample https://gm/aspnet/samples/tree/main/samples/aspnet/WebApi/BasicAuthentication.
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
In overload of
abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,
CancellationToken cancellationToken);
you can implement check to find if username/password from the header exist in configuration/secret list of username/password pairs
It’s also possible to create HTTP module that performs Basic Authentication. You can easily plug in an ASP.NET membership provider by replacing the CheckPassword method.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/basic-authentication#basic-authentication-with-custom-membership
Example of OWIN implementation https://github.com/scottbrady91/Blog-Example-Classes/tree/master/OwinBasicAuthentication/WebApi
Possible implementation in .Net core is described in
https://github.com/mihirdilip/aspnetcore-authentication-basic
An application of ours "accidentally" used basic authentication because of the following code in Web.config:
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
</modules>
... other stuff
</system.webServer>
The application is otherwise configured to use forms authentication.
The browser authentication window popped up whenever normal forms authentication would otherwise have been used.
I have an MVC app where controller A calls an internal HTTPGET method (handled by controller B). A has a view and B doesn't.
The HTTPGET in the controller B looks like this :
[HttpGet]
public String GetToken(string accessToken, string UID) {
....
// Log errors and other metrics
return someToken;
}
I want to use an action filter with my B controller which does the error logging for me. I do need the parameters passed with HTTP GET while logging. How can I pass accessToken and UID to the action filter such that I can log it.
What I'm looking for is something like this :
The controller should be something like
[MyActionFilter]
[HttpGet]
public String GetToken(string accessToken, string UID) {
....
return someToken;
}
while the action filter should do the logging
public class MyActionFilterAttribute : ActionFilterAttribute {
public override void onActionExecuted(HttpActionExecutedContext actionExecutedContext) {
// READ THE HTTP GET PARAMETERS AND DO THE LOGGING
}
}
You can use this:
public class MyActionFilterAttribute : ActionFilterAttribute {
public override void onActionExecuted(
ActionExecutedContext actionExecutedContext) {
// READ THE HTTP GET PARAMETERS AND DO THE LOGGING
actionExecutedContext.HttpContext.Request.QueryString; // HTTP GET
actionExecutedContext.HttpContext.Request.Params;
// HTTP GET / POST / COOKIE PARAMETERS As Key Value List
}
}
Best way would be to Log QueryString and other Items as suggested by other answers,
however if you want to access only the Method Parameters then you can do it
as shown below, ActionParameters Dictionary will give you all method parameters.
public class MyActionFilterAttribute : ActionFilterAttribute {
public override void OnActionExecuted
(HttpActionExecutedContext filterContext) {
foreach (KeyValuePair<string,object> item
in filterContext.ActionParameters)
{
//print item.Key
//print item.Value
}
}
}
I solved this by making the required parameters public in the controller, and reading the params directly as filterContext.Controller.publicParam.
The ActionFilter now looks like this -
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var thisController = ((myController)filterContext.Controller);
// public function in the controller that returns the required object
RequiredParameters requiredParams =
thisController.getParametersForLogging( );
// read the variables from requiredParams object and do the logging
}