return status code Unauthorized for custom IActionFilter in WebAPI - asp.net-mvc-4

I am working with asp.net WebAPI and I need to create a custom ActionFilter that does a quick check to see if the user requesting the URI should actually be able to get data back.
They have already been authorized to use the web service via basic auth and their role has been validated via a custom role provider.
The last thing I need to do is to check that they have permission to view the data they are requesting with a parameter in their URI.
Here is my code:
public class AccessActionFilter : FilterAttribute, IActionFilter
{
public System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken, Func<System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>> continuation)
{
var result = //code to see if they have permission returns either 0 or 1
if (result==0) {
throw new ArgumentException("You do not have access to this resource");
}
return continuation();
}
}
Currently I just throw an error which is not what I want, I'd rather return System.Net.HttpStatusCode.Unauthorized but I am a little miffed by the method I am overriding and I do not really understand it completely.
How would I go about returning that value?

You are probably best sticking to an exception but using the HttpResponseException which will return an Http status code too.
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
Good question here about this.
p.s.
It may be simpler/cleaner to implement ActionFilterAttribute
public class AccessActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var result = //code to see if they have permission returns either 0 or 1
if (result==0)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
base.OnActionExecuting(actionContext);
}
}

Instead of throwing exception you can set status code
public class ExecutionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var result = 0;//code to see if they have permission returns either 0 or 1
if (result == 0)
{
actionContext.Response = new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("Unauthorized User")
};
}
base.OnActionExecuting(actionContext);
}
}

Related

Core 7 - Api error handling, model state validation + UseExceptionhandler

I am currently working on implementing some Apis using swagger/swashbuckle in net core 7 and implementing some error handling, I've gone down the route of using an exception handler. With separate endpoints from dev/prod.
E.g. Startup.cs
if (env.IsDevelopment())
{
...details ommited
app.UseExceptionHandler("/dev-error");
}
else
{
...details ommited
app.UseExceptionHandler("/error");
}
ErrorController.cs
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorController : Controller
{
private ILogger _logger;
public ErrorController(ILogger logger)
{
_logger = logger;
}
[Route("dev-error")]
public IAttempt DevError()
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var exception = context.Error;
return Attempt.Fail(exception);
}
[Route("error")]
public IAttempt Error()
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var exception = context.Error;
_logger.Log(LogLevel.Error, exception, exception.Message);
switch (exception)
{
case UnauthorizedAccessException:
Response.StatusCode = (int) HttpStatusCode.Unauthorized;
return Attempt.Fail("Unauthorised");
default:
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
return Attempt.Fail("Generic Error");
}
}
}
The idea is that all responses are of IAttempt, so that the FE user can check if its succeeded etc. and whether to handle the result or exception in a user friendly way.
This has been working great up until now when I've been implementing Api's that require the model to be validated. I wanted to amend the IAttempt class to provide modelstate feedback, however I have tried many approaches and cant seem to get modelstate validation flow through the exception handler.
I wanted to implement a custom ValidationException that contains the errors which is then handled in these controllers. But when an exception is thrown in either an IActionFilter or when overriding the InvalidModelStateResponseFactory the exception isn't caught by the exception handler.
Is there a work around? Am I missing something?
Alternatively I could define a InvalidModelStateResponseFactory that returns a similar model(IAttempt), but it would be nice for Failed requests to be handled in one place.
Cheers in advance
I think you can make the InvalidModelStateResponseFactory redirect to the ErrorController, sending the required data to create your response
According to your description, I suggest you could consider using the customer action filter to achieve your requirement.
Inside the custom action filter, we could get the model state's results, then you could throw the exception inside it.
More details, you could refer to below codes:
1.Create the custom action filter:
public class CustomValidationActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (!context.ModelState.IsValid)
{
var errorList = context.ModelState.Values
.SelectMany(m => m.Errors)
.Select(m => m.ErrorMessage)
.ToList();
throw new Exception();
}
}
public void OnActionExecuting(ActionExecutingContext context) { }
}
2.Inside the program.cs
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new CustomValidationActionFilter());
});
Then if it thrown the exception, it will go to the controller's error action method, since you set the global exception handler.
I was unnecessarily over complicating things so I have dropped what I attempted to do as in theory responses should be handled accordingly to their response status code rather then the object thats passed in.

Why do I need to set a DefaultForbidScheme

In a WebAPI .net core project I have created a Middleware class that validates an api key. From validating it, it retrieves the permissions that the key has (user or admin) within the invoke method.
I pass it through a switch to set the principle like so
GenericIdentity identity = new GenericIdentity("API");
GenericPrincipal principle = null;
//we have a valid api key, so set the role permissions of the key
switch (keyValidatorRes.Role)
{
case Roles.User:
principle = new GenericPrincipal(identity, new[] { "User" });
context.User = principle;
break;
case Roles.Admin:
principle = new GenericPrincipal(identity, new[] { "Admin" });
context.User = principle;
break;
default:
principle = new GenericPrincipal(identity, new[] { "Other" });
context.User = principle;
break;
}
On controllers methods I have
[Authorize(Roles = "Admin")]
to validate the roles of an authenticated api key
If the user has the admin principle it goes through as expected. However, if it has a user or other principle then I get an error about
not having a DefaultForbidScheme
I googled around and added Authentication to my startup.cs with a customer scheme
services.AddAuthentication(options=> {
options.DefaultForbidScheme = "forbidScheme";
options.AddScheme<AuthSchemeHandle>("forbidScheme", "Handle Forbidden");
});
and created the AuthSchemeHandle
public class AuthSchemeHandle : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.NoResult());
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
throw new NotImplementedException();
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.FromResult(AuthenticateResult.Fail("Failed Auth"));
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
Now if the principle does not have Admin it fails without the error but the response that is returned on the API is 200 with no content. I was expecting a 4xx response with the message "Failed Auth"
I am just trying to work out why it is not as expected as although it seems "fixed" I do not understand how it has fixed it.
Is there a better way that I should be doing this?
why it is not as expected as although it seems "fixed" I do not understand how it has fixed it.
There's no dark magic when the authentication handler calls IAuthenticationHandler.ForbidAsync() method. We have to do relevant things ourself. In short, setting the StatusCode=403 as your need.
public async Task ForbidAsync(AuthenticationProperties properties)
{
properties = properties ?? new AuthenticationProperties();
_context.Response.StatusCode = 403;
// ...
return Task.CompletedTask;
}
As a side note, you don't need return a Task.FromResult() as it doesn't care about the result.
Is there a better way that I should be doing this?
The ASP.NET Core Team provides us an abstract class AuthenticationHandler to handle authentication. This abstract class has a built-in implementation for ForbidAsync(AuthenticationProperties properties) (and also for other public methods). So it's much easy to extends this abstract class as below:
public class MyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public MyAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
return AuthenticateResult.NoResult();
}
}
Finally, add a configuration for authentication service:
services
.AddAuthentication(options=>{
options.DefaultAuthenticateScheme = "forbidScheme";
options.DefaultForbidScheme = "forbidScheme";
options.AddScheme<MyAuthenticationHandler>("forbidScheme", "Handle Forbidden");
});
It should work as expected.

Add values to Web API model using ActionFilter or Custom Model Binder?

I'm using WebAPI 2 and I'm finding that most of my API POST endpoints are requiring the same two values be added to the model: UserId from HttpContext.Current.User.Identity.GetUserId() and SignalR_ConnectionId from a custom request header. I'd like to add these two values to the model without repeating the same code within each request handler.
I like the idea of using an ActionFilter because then I can easily decorate the endpoints that need this functionality with the annotation. So far my ActionFilter looks like the following:
public class AddUserIdActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var headerValues = actionContext.Request.Headers.GetValues("SignalRId");
if (HttpContext.Current.User.Identity != null)
{
Guid userId = Guid.Parse(HttpContext.Current.User.Identity.GetUserId());
}
else
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
//Modify the model??
}
}
How do I modify the model within the ActionFilter? (I assume that each model that is decorated with the attribute would descend from a base class with a property for userId and SignalRId).
If I can't add the values to the model, I'll have to consider a Custom Model Binder. I don't want to change the default model binding, just add my two properties to what's already happening. How do I approach this in Web API 2?
The following worked fine.
public class AddUserIdActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
IEnumerable<string> headers;
string connectionId;
Guid userId;
if(actionContext.Request.Headers.TryGetValues("SignalR_Id", out headers))
{
connectionId = headers.First();
}
else
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest));
}
if (HttpContext.Current.User.Identity != null)
{
userId = Guid.Parse(HttpContext.Current.User.Identity.GetUserId());
}
else
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
try
{
var model = (BaseCommand)actionContext.ActionArguments.Values.First();
model.UserId = userId;
model.ConnectionId = connectionId;
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError));
}
}
}

ServiceStack, Authenticate attribute

I am trying to write my own authentication, so I inherited CredentialsAuthProvider and have overridden the Authenticate method. Auth is working fine, also when i call another service i can see all data that i saved in the session.
The Problem is: When i try add the Authenticate attribute and call it from a client, it goes and throws an Unauthorized exception, even if i want to use Requered Role.
Auth service is:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return true;
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
session.FirstName = "Name";
//...
session.Authenticate = true;
session.UserName = request.UserName;
session.Roles = new List<string>;
session.Roles.Add("admin")
//....
authService.SaveSession(session, SessionExpiry);
// Return custom object
return new UserAuthResponse { SessionId = session.Id ......};
}
AppHost is:
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
new CustomCredentialsAuthProvider()
}));
Plugins.Add(new RegistrationFeature());
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
}
and test service:
[Authenticate]
public class TestService : Service {
public object Any(UserRequest request) {
return new UserResponse{Name = request.Name};
}
}
It is not real code, so sorry for syntax mistake!))))
But the idea is the same! Help me please what is wrong, why I got Unauthorized exception when i call Test service??????????
When I had this issue, I had to create a custom authenticate attribute [CustomAuthenticate] with guidance from this gist -> https://gist.github.com/joeriks/4518393
In the AuthenticateIfBasicAuth method, I set provider to use MyAuthProvider.Name instead of BasicAuthProvider.Name
Then,
[CustomAuthenticate]
public class TestService : Service {
public object Any(UserRequest request) {
return new UserResponse{Name = request.Name};
}
}
Also see: http://joeriks.com/2013/01/12/cors-basicauth-on-servicestack-with-custom-authentication/

ASP.NET Web API and Status Code For Null Response

If a requested resource is not found by the Service Layer returning null to the Web API controller; what is the best way to throw a HttpStatusCode.NotFound response back to the client without hard coding it in the controller, and by checking if it's null?
Personally I would just do the checks in the controllers as per Oppositional's comment but what you are asking for is perfectly reasonable. Again using action filters either attached per controller (or registered globally) you could do something along these lines:
Example Model:
public class Foo
{
public string Bar { get; set; }
}
Example Controller:
public class FoosController : ApiController
{
[NullObjectActionFilter]
public Foo Get(string id)
{
// - Returns model and 200
//return new Foo() { Bar = "TEST" };
// - Returns 404
//return null;
}
}
The Filter:
public class NullObjectActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
object outValue = null;
actionExecutedContext.Response.TryGetContentValue<object>(out outValue);
if (outValue == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
base.OnActionExecuted(actionExecutedContext);
}
}
I agree with Mark that the ActionFilter is the way to go - small action methods are a good smell.
However, HttpActionExecutedContext.Response can be null when an exception occurs; and the NullObjectActionFilter class shown above can obscure error HTTP status codes. You're better off checking for successful exit and a successful HTTP code.
Here's an action filter I use:
/// <summary>
/// Converts <c>null</c> return values into an HTTP 404 return code.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class NullResponseIs404Attribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if ((actionExecutedContext.Response != null) && actionExecutedContext.Response.IsSuccessStatusCode)
{
object contentValue = null;
actionExecutedContext.Response.TryGetContentValue<object>(out contentValue);
if (contentValue == null)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "Object not found");
}
}
}
}