Web API 2 Basic Authentication and allow actions not marked [Authorize] - security

I have been looking at Basic Authentication in Web Api2 and don’t seem to find an explanation for something I am confused about.
I created a web api application project with individual authentication in Visual studio 2017.
I have the default code
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
}
I call these actions via postman, browser etc all good.
If I add the [Authorize] attribute to one of the methods I get 401 unauthorized response as expected.
So far so good.
I then add basic authentication by creating a class derived from AuthorizationFilterAttribute
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 == "ade" && password == "password";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
HttpContext.Current.User = 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:");
}
I register the filter in WebApiConfig.cs
config.Filters.Add(new BasicAuthenticationAttribute());
I use postman to call the action marked with [Authorize] and send with header Authorization: Basic YWRlOnBhc3N3b3Jk
The request is authorized and I get my action response. All good.
Now I call the action that is not marked with [Authorize] without a Authorization header from postman expecting to get a response but the OnAuthorization is called and obviously returns HandleUnathorized(actionContext); I only expected the OnAuthorization method to be called where an action is marked with [Authorize]
So now I am thinking what is the point of the [Authorize] attribute because OnAuthorization is called regardless so what is the point of marking actions [Authorize] attribute?
Secondly, I added the following method to my class
private static bool SkipAuthorization(HttpActionContext actionContext)
{
Contract.Assert(actionContext != null);
return actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()
|| actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
}
I call this method at the beginning of OnAuthorization
if (SkipAuthorization(actionContext)) return;
If I mark my actions with [AllowAnonymous] it works.
If there is no [Authorize] attribute on the controller or specific actions then surely the OnAuthorization should also be skipped?
I just don't see the point of using [Authorize], I am clearly missing something here, am I doing something wrong or do I need to mark the actions with [AllowAnonymous] to exclude them.

If you are using [Authorize] attribute and windows authentication, then authorization will done automatically, you don't need to do any special configuration, but any special case if you need to override [Authorize] class then your class is like below,
Instead of inheriting AuthorizationFilterAttribute, you can
inherit AuthorizeAttribute
public class BasicAuthenticationAttribute : AuthorizeAttribute
{
//your override methods
}
Instead of using [Authorize] attribute, use your derived class name. In your case use [BasicAuthenticationAttribute], not [Authorize]

Thanks Fran you set me off on the right path.
I commented out the following line
config.Filters.Add(new BasicAuthenticationAttribute());
I used the following attributes in controller
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Authorize]
[BasicAuthentication]
public string Get(int id)
{
return "value";
}
}
If I called the action get() I got a response, OnAuthorisation was not called.
If I call get(int id) I get 401 Unauthorised and OnAuthorisation is not called.
I removed the [Authorize] from get(int id) action
[BasicAuthentication]
public string Get(int id)
{
return "value";
}
and it all worked, OnAuthorisation was called as expected.

Related

Azure Mobile Apps authorize filter not honored

I'm using azure mobile app and i notice that each attribute i put in a controller is not getting honored. only global filters works.
for example
[MobileAppController]
[ZboxAuthorize]
public class BoxesController : ApiController
{
[ZboxAuthorize]
[Route("api/boxes", Order = 3)]
public async Task<HttpResponseMessage> GetBoxesAsync()
{
var userid = User.GetCloudentsUserId();
return Request.CreateResponse("ok");
}
now zboxauthorize attribute is not working. the ctor get called but nothing else
the atuthorize attribute looks like this
public class ZboxAuthorizeAttribute : AuthorizeAttribute, IOverrideFilter
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
return false;
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
HttpContext.Current.Response.AddHeader("AuthenticationStatus", "NotAuthorized");
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
}
if i put it in my global filter its works perfectly.
what am i missing? thanks.

web api 2 - Passing data from action filter to action as an argument

In order to avoid getting the user data on every action I've create an custom action filter that gets the user by its ID and then passes to the action.
public class UserDataAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
...
// getting the user and storing it in the request properties
object user = userBLL.GetUserById(userId);
actionContext.Request.Properties.Add("User", user);
}
}
And the I can get the user object in the action method like this:
[Authorize]
[UserData]
[HttpGet]
[Route("dosomething")]
public IHttpActionResult DoSomething()
{
// retrieve the user
object user;
Request.Properties.TryGetValue("User", out user);
User u = (User)user;
return Ok();
}
However, in MVC it's possible to use ActionParameters in the filter to store something that will be used by the action method, like so:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
// Create object parameter.
filterContext.ActionParameters["User"] = userBLL.GetUserById(userId);
}
And then use the User object as if it were part of the original request:
[AddActionParameter]
public ActionResult Index(User user)
{
// Here I can access the user setted on the filter
...
return View();
}
So, my question is: There is a way in Web API 2 to pass the User object from the action filter to the action as an argument, just like in MVC?
With ASP.NET Web API, you can create a parameter binding to receive an object, User in your case. You don't have to create a filter for this. So, you will create a binding like this.
public class UserParameterBinding : HttpParameterBinding
{
public UserParameterBinding(HttpParameterDescriptor descriptor) :
base(descriptor) { }
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
HttpActionContext context,
CancellationToken cancellationToken)
{
SetValue(context, new User() { // set properties here });
return Task.FromResult<object>(null);
}
}
Then, to use the binding, you will configure it, like this.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// snip
config.ParameterBindingRules.Insert(0, d =>
d.ParameterType == typeof(User) ? new UserParameterBinding(d) : null);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
With that, wherever you have User as action method parameter, it will automatically bind the instance you are creating inside UserParameterBinding to that parameter.

Specific TableController name not working

I have an extremely odd error and wondered if anyone knew the reason for this.
When I create a new DataObject and TableController called Content and ContentController respectively, it doesn't register the tablecontroller and the help documentation it automatically generates has lost its styling.
I can't connect to the controller at all but all other controllers work as expected.
If I just rename it to DataController and that's just the name of the controller, not the dataobject everything works perfectly.
Is ContentController a reserved word of some kind or is this just specifically happening on my machine?
public class DataController : TableController<Content>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileContext context = new MobileContext();
DomainManager = new EntityDomainManager<Content>(context, Request, Services);
}
// GET tables/Content
public IQueryable<Content> GetAllContent()
{
return Query();
}
// GET tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Content> GetContent(string id)
{
return Lookup(id);
}
// PATCH tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Content> PatchContent(string id, Delta<Content> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<IHttpActionResult> PostContent(Content item)
{
Content current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteContent(string id)
{
return DeleteAsync(id);
}
}
An MVC project will create an application directory called Content. This will override your route mapping to the ContentController.
You can get around this if desired through changing RouteMaps and other trickery although probably the simpliest answer is to change the name of the controller...

Authenticate Attribute for MVC: ExecuteServiceStackFiltersAttribute: SessionFeature not present in time to set AuthSession?

I'm trying to create a simple Credentials Auth using OrmLiteAuthRepository(Postgres) and Memcached as caching layer on Mono 3.2.x / Ubuntu 12.04 in an MVC Application - I am using ServiceStack libraries version 4.0x
I am using a custom session object, adapted from ServiceStack's SocialBootstrap example
What works perfectly:
Getting the session inside a controller action, such as:
var currentSession = base.SessionAs<MyCustomUserSession>();
However, I don't want to check / validate the session and what may or may not be inside it in the action code, I would like to use an attribute, and this leads me to:
What does not work: Using the Authenticate attribute above the action name:
My problem (null AuthSession) shows up when trying to utilize the [Authenticate] attribute on an MVC action.
[Authenticate]
public ActionResult Index()
{
return View();
}
I have managed to narrow it down to the fact that ExecuteServiceStackFiltersAttribute executes this code, but it appears the AuthSession has not yet been made available by the SessionFeature - so the AuthSession will always be null at this point:
var authAttrs = GetActionAndControllerAttributes<AuthenticateAttribute>(filterContext);
if (authAttrs.Count > 0 && ( ssController.AuthSession==null || !ssController.AuthSession.IsAuthenticated))
{
filterContext.Result = ssController.AuthenticationErrorResult;
return;
}
If, for example I override the AuthenticationErrorResult and try to throw an exception if I manually initialize the session from the SessionFeature, it will throw the "there is life in the session" exception (of course, when I logged in with a valid user):
public override ActionResult AuthenticationErrorResult
{
get
{
if (AuthSession == null)
{
// the Authenticate filter is triggered by ExecuteServiceStackFilters attribute
// which seems to always have AuthSession null
var session = SessionFeature.GetOrCreateSession<MyCustomUserSession>(AuthService.Cache);
if (session == null || (session != null && session.IsAuthenticated == false))
{
throw new Exception("Hmmm...dead as a dodo");
}
else
{
throw new Exception("there is life in the session:" + session.UserName);
}
}
var returnUrl = HttpContext.Request.Url.PathAndQuery;
return new RedirectResult(LoginRedirectUrl.Fmt(HttpUtility.UrlEncode(returnUrl)));
}
}
Aside from creating my custom attributes / filters, is there a solution I should try (properties to set) with the incumbent ServiceStack codebase? If I'm missing something, please let me know.
My regards for a great project in any case.
My problem (null AuthSession) shows up when trying to utilize the [Authenticate] attribute on an MVC action.
Are you getting an Exception or are you just getting redirected to the 'Login' page? If you are not getting an Exception and just be redirected because you're not authenticated, the below may work. Also, are you implementing your own Custom Authentication Provider? If so, could you post a sample of it?
I don't think you have it in your code samples but I think your MVC Controller code is probably something like...
public class SomeController : ServiceStackController
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}
Can you try adding your custom MyCustomUserSession to the Type of the ServiceStackController making it...
public class SomeController : ServiceStackController<MyCustomUserSession>
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}

How would I change a ServiceStack response DTO

I'm working on an API where I'd like to be able to customize the response structure based on a parameter from the client. Response filters seem like a good place to do this in order to avoid doing so in each service or action. The problem is that while I have access to the response DTO returned by the action, and could change its properties, I can't find how or where to replace the object entirely.
Naively replacing the object in the response filter did not work, but this help illustrate what I'm trying to do:
public class ChangeResponseAttribute : ResponseFilterAttribute
{
public override void Execute(IHttpRequest req, IHttpResponse res, object responseDto)
{
var overrideText = req.QueryString["override"];
if (!String.IsNullOrEmpty(overrideText))
responseDto = new { Message = overrideText };
}
}
[ChangeResponse]
public class TodosService : Service
{
public object Get(Todos request)
{
return new object[0];
}
}
It looks like another option would be to write the custom response directly & end the request, but that would bypass any other processing left to do by ServiceStack.
Is there a better place to do this than a response filter? Or do I need to bite the bullet and return the optimal DTO in each action?
You can't change the Response DTO in a filter, but yes one option is to write the response in the filter itself (see this answer for an example of how to do this).
The other option is to use a ServiceRunner and override the OnAfterExecute() custom hook which does let you modify the response returned, e.g:
public class MyServiceRunner<T> : ServiceRunner<T>
{
public override object OnAfterExecute(
IRequestContext requestContext, object response)
{
// Called just after any Action is executed
var overrideText = req.Get<IHttpRequest>().QueryString["override"];
return !string.IsNullOrEmpty(overrideText)
? new { Message = overrideText } : null;
}
}
To get ServiceStack to use it you need to override the CreateServiceRunner method in your AppHost, e.g:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(
ActionContext actionContext)
{
return new MyServiceRunner<TRequest>(this, actionContext);
}

Resources