We are using token authentication in our WebAPI application. Every call (other then method which obtains key) uses same pattern.
Authorization: our-token v01544b7dce-95c1-4406-ad4d-b29202d0776c
We implemented authentication using Attribute and IActionFilter
Controllers look like so:
[RoutePrefix("api/tms/auth")]
public class AuthController : BaseController
{
public ISecurityService SecurityService { get; set; }
[TokenAuth]
[Route("logout")]
[HttpPost]
public HttpResponseMessage Logout()
{
try
{
this.SecurityService.InvalidateAccessToken(this.StaticContextWrapperService.AccountId, token, HttpContext.Current.Request.UserHostAddress);
// Return OK status
return new HttpResponseMessage(HttpStatusCode.OK);
}
catch (LoginException le)
{
return this.LogoutFailureResponse(le.Message);
}
}
private HttpResponseMessage LogoutFailureResponse(string message)
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(message, Encoding.UTF8, "text/plain")
};
}
}
Swagger config has following:
c.ApiKey("our-token", "header", "Our Token Authentication");
Swagger UI showing "Authorize" button and I can paste token into field on popup.
However, no headers passed in any tests. And no methods have "lock" icon on them.
EDIT:
I also tried:
c.ApiKey("our-token", "header", "Our Token Authentication", typeof(TokenAuthAttribute));
Where attribute is just attribute:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TokenAuthAttribute : Attribute
{
}
Then we use IActionFilter to check if attribute applied to method and thats where we check for permission. This is done to use service via DI.
EDIT2:
I made change to how Attribute declared:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TokenAuthAttribute : AuthorizeAttribute
{
}
After than Swagger UI started to show all methods as secured, so it does analyze that it's in fact AuthorizeAttribute, not just Attribute
After that it started to put header like so:
our-token: ZGV2OnYwMTA2YjZmYjdhLWRlNTUtNDZlNC1hN2Q4LTYxMjgwNTg2M2FiZQ==
Where it should be:
Authorization: our-token GV2OnYwMTA2YjZmYjdhLWRlNTUtNDZlNC1hN2Q4LTYxMjgwNTg2M2FiZQ==
If I'm not mistaken you should have:
c.ApiKey("our-token", "header", "Our Token Authentication", typeof(TokenAuthAttribute));
With that in place, all the actions tagged with TokenAuth should show a lock icon
You can see it in action in one of mine:
https://turoapi.azurewebsites.net/swagger/ui/index
And the code behind that is here:
https://github.com/heldersepu/TuroApi/blob/master/TuroApi/App_Start/SwaggerConfig.cs#L67
Related
I am trying to retrieve ModelState validation errors from a micro service built with .NET Core 5 Web API from a ASP.NET Core MVC frontend.
Say I have a model that looks like this:
public class Comment
{
public string Title { get; set; }
[Required]
public string Remarks { get; set; }
}
When I call the rest endpoint in the micro service through Swagger to update the Comment model without a remark, I get a response body like this:
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Remarks": [
"The Remarks field is required."
]
}
}
Awesome! This is what I expect... However, when I call this endpoint through my MVC project, I can't seem to get the actual "errors".
This is how I am calling the rest endpoint:
var client = _httpClientFactory.CreateClient("test");
HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(comment), Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PutAsync($"api/comments", httpContent);
The response object just has a statusCode of BadRequest. I want to pull the info regarding the errors (the section that says "The Remarks field is required.") but I don't see it in the Content or Headers property or anything like that.
I am new to micro services and .NET Core - am I doing something wrong? Do I need to add something to the startup.cs? Seems weird that I can get the BadRequest status but no supporting problem details.
Thanks in advance!
Be sure your web api controller is not declared with [ApiController].
Web Api Project:
//[ApiController]
[Route("api/[controller]")]
public class CommentsController : ControllerBase
{
[HttpPut]
public IActionResult Put([FromBody] Comment model)
{
if(ModelState.IsValid)
{
//do your stuff...
return Ok();
}
return BadRequest(ModelState);
}
}
Mvc Project:
HttpResponseMessage response = await client.PutAsync($"api/comments", httpContent);
var result = response.Content.ReadAsStringAsync().Result;
I am creating asp.net webapi with two get methods. One returns all the records while the other should be filtering based on a string parameter called countrycode. I am not sure for what reason the get method with string parameter doesnt get invoked.
I tried the following uri's
http://localhost:64389/api/team/'GB'
http://localhost:64389/api/team/GB
Following is my code
Web API
public HttpResponseMessage Get()
{
var teams = _teamServices.GetTeam();
if (teams != null)
{
var teamEntities = teams as List<TeamDto> ?? teams.ToList();
if (teamEntities.Any())
return Request.CreateResponse(HttpStatusCode.OK, teamEntities);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Team not found");
}
public HttpResponseMessage Get(string countryCode)
{
if (countryCode != null)
{
var team = _teamServices.GetTeamById(countryCode);
if (team != null)
return Request.CreateResponse(HttpStatusCode.OK, team);
}
throw new Exception();
}
WebAPIConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("text/html"));
}
}
I think you are probably hitting the default 'Get()' method from your default API route.
I expect if you changed the parameter name to 'id' on your method like so, it would also work:
public HttpResponseMessage Get(string id)
This is because the optional parameter name in the default route is 'id'.
For attribute routing to work, you need to decorate your controller and methods with the values which were previously inferred by the route configuration.
So at the top of your controller, you would probably have:
[RoutePrefix("api/team")]
public class TeamController : ApiController
Then above your second get method:
[Route("{countryCode}")]
public HttpResponseMessage Get(string countryCode)
Since attribute routing, I haven't used the "old-style" routing.
Check out the ASP.NET page on attribute routing for more information.
Edit for comment:
If you have two routes which have the same parameters you need to differentiate them somehow in the route. So for your example of get by team name, I would probably do something like this:
[HttpGet()]
[Route("byTeamName/{teamName}")]
public HttpResponseMessage GetByTeamName(string teamName)
Your url would then be /api/team/byTeamName/...
Your other method name is "Get" and the default HTTP attribute routing looks for method names with the same as HTTP verbs. However you can name your methods anything you like and decorate them with the verbs instead.
I created custom authorize attribute to handle my custom permissions on WebAPI odata controller inherited from EntitySetController, here is the code for my attribute
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class RequirePermissionsAttribute : System.Web.Http.AuthorizeAttribute
{
public Permissions[] Permissions { get; set; }
public RequirePermissionsAttribute()
{ }
public RequirePermissionsAttribute(params Permissions[] permissions)
{
this.Permissions = permissions;
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// Custom authorization logic
}
Now I try to add this attribute on Get() method, it get invoked
public class ItemsController : EntitySetController<Item, Guid>
{
[EnableQuery(MaxExpansionDepth = 5)]
[RequirePermissionsAttribute(Permissions.ViewAll)]
public override IQueryable<Item> Get()
{
//Code go here
}
}
But when I add the same attribute on CreateEntity() it never get invoked
[RequirePermissionsAttribute(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
}
Any help appreciated
You should use your attribute as RequirePermissions without the tailed "Attribute" word, So change your code to be like this
[RequirePermissions(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
}
Islam
From the WebAPI source codes, the internal virtual function CreateEntity() is called in POST request. Here's the source codes in EntitySetController:
public virtual HttpResponseMessage Post([FromBody] TEntity entity)
{
TEntity createdEntity = CreateEntity(entity);
TKey entityKey = GetKey(entity);
return EntitySetControllerHelpers.PostResponse(this, createdEntity, entityKey);
}
I use your sample codes and send a POST request, the CreateEntity() can be invoked as:
POST ~/odata/Items
Content-type: application/json
{"Id":"9daf653f-212c-42e3-80a4-4778e445c092"}
However, if you want to get the correct response, you should override GetKey() because GetKey() is called after CreateEntity() in the Post() method. The same information is also mentioned in the remarks of CreateEntity() as below:
Sample Test
I create the following two functions in ItemsController:
protected override Guid GetKey(Item entity)
{
return entity.Id;
}
[RequirePermissionsAttribute(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
return item;
}
And send the same POST request mentioned above, I can get the following response:
HTTP/1.1 201 Created
Cache-Control: no-cache
.....
Content-Length: 124
{
"odata.metadata":"http://localhost:47794/odata/$metadata#Items/#Element","Id":"9daf653f-212c-42e3-80a4-4778e445c092"
}
Hope it can help you. Thanks.
I have the following action in my Web api controller:
// POST api/<controller>
[AllowAnonymous]
[HttpPost]
public bool Post(string user, string password)
{
return true;
}
I am getting the following error with a 404 status when hitting it with either fiddler or a test jQuery script:
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost/amsi-v8.0.0/api/account'.","MessageDetail":"No action was found on the controller 'Account' that matches the request."}
My http route is as follows:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Gets work fine. I found another question here which talks about removing WebDAV from IIS. I tried that, still same issue.
Why do I get a 404?
The default action selection behavior in ASP.NET Web API cares about your action method parameters as well. If they are simple type objects and they are not optional, you will need to supply them in order to invoke that particular action method. In your case, you should send a request against a URI as below:
/api/account?user=Foo&password=bar
If you wanna get these values inside the request body rather than the query string (which is a better idea), just create a User object and send the request accordingly:
public class User {
public string Name {get;set;}
public string Password {get;set;}
}
Request:
POST http://localhost:8181/api/account HTTP/1.1
Content-Type: application/json
Host: localhost:8181
Content-Length: 33
{"Name": "foo", "Password":"bar"}
And your action method should look like something below:
public HttpResponseMessage Post(User user) {
//do what u need to do here
//return back the proper response.
//e.g: If you have created something, return back 201
return new HttpResponseMessage(HttpStatusCode.Created);
}
When we are posting a json it expect a class so create class in model folder like this
public class Credential
{
public string username { get; set; }
public string password { get;set; }
}
and now change the parameter
[HttpPost]
public bool Post(Credential credential)
{
return true;
}
Try now everything will work smooth
I am setting up a simple RESTful controller for a Todo resource with an XML representation. It all works great - until I try to redirect. For example, when I POST a new Todo and attempt to redirect to its new URL (for example /todos/5, I get the following error:
Error 500 Unable to locate object to be marshalled in model: {}
I do know the POST worked because I can manually go to the new URL (/todos/5) and see the newly created resource. Its only when trying to redirect that I get the failure. I know in my example I could just return the newly created Todo object, but I have other cases where a redirect makes sense. The error looks like a marshaling problem, but like I said, it only rears itself when I add redirects to my RESTful methods, and does not occur if manually hitting the URL I am redirecting to.
A snippet of the code:
#Controller
#RequestMapping("/todos")
public class TodoController {
#RequestMapping(value="/{id}", method=GET)
public Todo getTodo(#PathVariable long id) {
return todoRepository.findById(id);
}
#RequestMapping(method=POST)
public String newTodo(#RequestBody Todo todo) {
todoRepository.save(todo); // generates and sets the ID on the todo object
return "redirect:/todos/" + todo.getId();
}
... more methods ...
public void setTodoRepository(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
private TodoRepository todoRepository;
}
Can you spot what I am missing? I am suspecting it may have something to do with returning a redirect string - perhaps instead of it triggering a redirect it is actually being passed to the XML marshaling view used by my view resolver (not shown - but typical of all the online examples), and JAXB (the configured OXM tool) doesn't know what to do with it. Just a guess...
Thanks in advance.
This happend because redirect: prefix is handled by InternalResourceViewResolver (actually, by UrlBasedViewResolver). So, if you don't have InternalResourceViewResolver or your request doesn't get into it during view resolution process, redirect is not handled.
To solve it, you can either return a RedirectView from your controller method, or add a custom view resolver for handling redirects:
public class RedirectViewResolver implements ViewResolver, Ordered {
private int order = Integer.MIN_VALUE;
public View resolveViewName(String viewName, Locale arg1) throws Exception {
if (viewName.startsWith(UrlBasedViewResolver.REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(UrlBasedViewResolver.REDIRECT_URL_PREFIX.length());
return new RedirectView(redirectUrl, true);
}
return null;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}