I am calling a Web API method like this:
$.post("/api/attendances", { gigId: button.attr("data-gig-id") })
.done(function() {
button.removeAttr("btn-default")
.addClass("btn-primary")
.text("going");
})
.fail(function() {
alert("something went wrong");
});
And the Web API class looks like this:
[Authorize]
public class AttendancesController : ApiController
{
private ApplicationDbContext _context;
public AttendancesController()
{
_context = new ApplicationDbContext();
}
[HttpPost]
public IHttpActionResult SaveAttenance(AttendanceDto dto)
{
string userId = User.Identity.GetUserId();
if (_context.Attendances.Any(a => a.GigId == dto.GigId && a.AttendeeId == userId))
{
return BadRequest();
}
_context.Attendances.Add(new Attendance()
{
GigId = dto.GigId,
AttendeeId = userId
});
_context.SaveChanges();
return Ok();
}
}
I am testing the call with anonymous user,when calling the method, I get status code 200 back which is not what I am expecting. I am also receiving this:
responseText :"{"Message":"Authorization has been denied for this
request."}"
status:200
statusText : "OK"
Why isn't the Authorize attribute returning a status code that matches the responseText? In my case, the JavaScript code inside the .done function will execute regardless if the user is authorized or not. Any guidance is appreciated.
Update: Here's a link to my web.config if it helps: https://pastebin.com/B26QGjv8
That's because Although you are using the [Authorize] attribute, You are not doing anything with the result.
The method works as expected, you are issuing a request, you are not authorized but you continue on with your work in the controller.
Handle your exception in the controller:
Override the On Exception method and create an exception attribute:
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
And in your controller call it like this:
public class ProductsController : ApiController
{
[NotImplExceptionFilter]
public Contact GetContact(int id)
{
throw new NotImplementedException("This method is not implemented");
}
}
In your WebApiConfig.cs add:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ProductStore.NotImplExceptionFilterAttribute());
// Other configuration code...
}
}
Use This as reference, All snippets are taken from here:
Handling exceptions in Web Api.
Related
I've been trying to use Namespace routing to build some APIs dynamically without the need to worry about hardcoding the routes. However, I did find an example from MSDN to use namespaces and folder structure as your API structure. Here's the sample that I have to use Namespace routing:
public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
private readonly string _baseNamespace;
public NamespaceRoutingConvention(string baseNamespace)
{
_baseNamespace = baseNamespace;
}
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector => selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
var namespc = controller.ControllerType.Namespace;
if (namespc == null) return;
var templateParts = new StringBuilder();
templateParts.Append(namespc, _baseNamespace.Length + 1, namespc.Length - _baseNamespace.Length - 1);
templateParts.Replace('.', '/');
templateParts.Append("/[controller]/[action]/{environment}/{version}");
var template = templateParts.ToString();
foreach (var selector in controller.Selectors)
{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template
};
}
}
}
And here's the controller:
namespace Backend.Controllers.Api.Project.Core
{
public class UserController : ApiBaseController
{
public UserController()
{
}
[HttpPost]
public IActionResult Login(LoginInput loginInput) // <-- loginInput properties return null
{
if (!ModelState.IsValid) return BadRequest();
return Ok(user);
}
}
}
in Startup.cs
namespace Backend
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Let's use namespaces as the routing default way for our APIs
services.AddControllers(options =>
{
options.Conventions.Add(new NamespaceRoutingConvention(typeof(Startup).Namespace + ".Controllers"));
});
}
}
}
Everything works ok except that when I trigger a POST api call to Login action the LoginInput doesn't get populated the values I'm sending through Postman i.e. {"username": "value", "password": "sample"} and it always returns null value. I'm not sure what am I doing wrong with the NamespaceRoutingConvention. Bear in mind if I remove it and hard-code the route in the controller like:
[ApiController]
[Route("api/project/core/[controller]/[action]/proda/v1")]
It works as expected. Any ideas?
Try to use this instead:
[HttpPost]
public IActionResult Login([FromBody]LoginInput loginInput)
{
if (!ModelState.IsValid) return BadRequest();
return Ok(user);
}
I think that by setting AttributeRouteModel, you're preventing the middleware invoked by having ApiControllerAttribute in the Controller to do its job, and so the defaults of treating object parameters as body is not applied.
This is a guess though, I haven't been able to find the corresponding code in the source code.
I have the following base controller class:
public class ApiControllerBase : ControllerBase
{
[ApiExplorerSettings(IgnoreApi = true)]
protected void IsAccess(int carrierId)
{
if (condition)
{
ControllerContext.HttpContext.Response.StatusCode = 401;
}
}
and then I call it from my controller, inherited from ApiControllerBase:
public IActionResult GetTabletListByGroup(...)
{
IsAccess(55555);
I want to return Forbid() if condition is true. But set StatusCode is not enough for it. How to do it correctly?
First, the status code indicated by Forbid() is 403 instead of 401.
Secondly, the Forbid() method needs to rely on the authentication stack to respond. If you don't have any authentication handlers in your pipeline, you can't use Forbid(). Instead, you should use return StatusCode(403).
You can refer to this.
I have made a simple demo, you can refer to it:
Update
ApiController:
[Route("api/[controller]")]
[ApiController]
public class ApiControllerBase : ControllerBase
{
[ApiExplorerSettings(IgnoreApi = true)]
protected IActionResult IsAccess(int carrierId)
{
if (carrierId >= 1)
{
ControllerContext.HttpContext.Response.StatusCode = 403;
return StatusCode(403);
}
else
{
return Ok();
}
}
}
TestBaseController :
public class TestBaseController : ApiControllerBase
{
public IActionResult GetTabletListByGroup()
{
return IsAccess(55555);
}
}
Here is the test result:
I have a project using .NET core version 3.1 and I'm using token for logging in. Everything works perfectly when testing with Postman, it created token and I can use it to access the Home page.
The problem is, when I started testing on client side, it doesn't work. I debugged and saw after logging in, the token is generated but I can't access the HomeController because of [Authorize] attribute.
This is my code to generate token:
public async Task<HttpResponse<LoginResult>> GetTokenAsync(LoginRequest loginInfo)
{
var audience = await _audiences.FindAsync(a => a.Id == loginInfo.ClientId);
string message = string.Empty;
if (audience != null)
{
bool audienceIsValid = _jwtProvider.ValidateAudience(audience.Issuer
, audience.SecretKey
, ref message);
if (audienceIsValid)
return await GenerateToken(loginInfo);
else
message = ErrorMessages.Login_AudienceInvalid;
}
else
message = string.Format(ErrorMessages.Login_Not_Permitted, "Your client Id");
return HttpResponse<LoginResult>.Error(message, HttpStatusCode.BadRequest);
}
I guess that token couldn't be stored correctly.
What am I missing?
UPDATE
This is my code in login
[HttpPost]
[Route("login")]
[AllowAnonymous]
public async Task<ActionResult> Login([FromForm]LoginRequest model)
{
model.ClientId = 1;
var response = await _services.GetTokenAsync(model);
if (response.StatusCode == 200)
{
return RedirectToAction("Index", "Home");
}
return RedirectToAction("Login");
}
And this is what I'm trying to access
[HttpGet]
[Route("index")]
[Authorize]
public IActionResult Index()
{
return View();
}
You need to create a custom policy to specify in the Authorize attribute that is configured to use a custom requirement handler
First you lay out the requirement of the custom policy via a class that inherits IAuthorizationRequirement
public class TokenRequirement : IAuthorizationRequirement
{
}
This is where you would optionally accept parameters if you need them. But normally you pass a token in the header of a request which your custom policy's requirement handler would have access to without the need for explicit parameters.
Your requirement's requirement handler to be used by your custom policy would look something like this
public class TokenHandler : AuthorizationHandler<TokenRequirement>
{
//Some kind of token validator logic injected into your handler via DI
private readonly TokenValidator _tokenValidator;
//The http context of this request session also injected via DI
private readonly IHttpContextAccessor _httpCtx;
//The name of the header your token can be found under on a Http Request
private const string tokenHeaderKey = "authToken";
//Constructor using DI to get a instance of a TokenValidator class you would
//have written yourself, and the httpContext
public TokenHandler(TokenValidator tokenValidator, IHttpContextAccessor httpCtx)
{
_tokenValidator = tokenValidator;
_httpCtx = httpCtx;
}
//Overriden implementation of base class AuthorizationHandler's HandleRequirementAsync method
//This is where you check your token.
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context
,TokenRequirement requirement)
{
if (context.Resource is Endpoint endpoint)
{
HttpRequest httpReqCtx = _httpCtx.HttpContext.Request;
string token =
httpReqCtx.Headers.TryGetValue(tokenHeaderKey, out StringValues tokenVal)
? tokenVal.FirstOrDefault()
: null;
if (string.IsNullOrWhitespace(token))
{
context.Fail();
}
else
{
bool tokenIsValid = await _tokenValidator.ValidToken(token);
if(tokenIsValid)
context.Succeed(requirement);
else
context.Fail();
}
}
return Task.CompletedTask;
}
}
You'd register your custom requirement handler on a custom policy name in Startup.cs like so
//This is a framework extension method under Microsoft.Extensions.DependencyInjection
services.AddHttpContextAccessor();
//Your custom handler
services.AddSingleton<IAuthorizationHandler, TokenHandler>();
//Your custom policy
services.AddAuthorization(options =>
{
options.AddPolicy(
//Your custom policy's name, can be whatever you want
"myCustomTokenCheckerPolicy",
//The requirement your policy is going to check
//Which will be handled by the req handler added above
policy => policy.Requirements.Add(new TokenRequirement())
);
});
The impl on the attribute would look like this
[HttpGet]
[Route("index")]
[Authorize(Policy = "myCustomTokenCheckerPolicy")]
public IActionResult Index()
{
return View();
}
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'm encountering an issue with CORS while using IAsyncResourceFilter implementation.
I want to be able to call my actions from other domains as well...
I've defined the CORS policy under my Startup file as the following:
services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder =>
{
builder.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
});
});
And under the Configure method:
app.UseCors("AllowAllOrigins");
It works fine without using a TypeFilterAttribute which use IAsyncResourceFilter.
For example calling my API action without any TypeFilterAttribute attribute works:
public bool Get()
{
return true;
}
But when adding my TypeFilterAttribute as follows it doesn't work and returns the error about the CORS:
[MyTypeFilterAttribute("test")]
public bool Get()
{
return true;
}
Anything I'm missing? What should I add when using IAsyncResourceFilter?
The following is the MyTypeFilterAttribute code: (With no real logic...)
public class MyTypeFilterAttribute : TypeFilterAttribute
{
public MyTypeFilterAttribute(params string[] name) : base(typeof(MyTypeFilterAttributeImpl))
{
Arguments = new[] { new MyTypeRequirement(name) };
}
private class MyTypeFilterAttributeImpl: Attribute, IAsyncResourceFilter
{
private readonly MyTypeRequirement_myTypeRequirement;
public MyTypeFilterAttributeImpl(MyTypeRequirement myTypeRequirement)
{
_myTypeRequirement= myTypeRequirement;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
context.Result = new OkResult();
await next();
}
}
}
public class MyTypeRequirement : IAuthorizationRequirement
{
public string Name { get; }
public MyTypeRequirement(string name)
{
Name = name;
}
}
Cors middleware sets headers on the response result object.
I believe you are resetting these with context.Result = new OkResult();
See poke's reply below. If you set any result in an action filter, this result gets sent back immediately, thus overwriting any other one!