ASP.NET Web API OAuth2 customize 401 unauthorized response - asp.net

I am using Microsoft.Owin.Security.Jwt. My resource server is configured as follows:
// Resource server configuration
var audience = "hello";
var secret = TextEncodings.Base64Url.Decode("world);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
Currently, when a token is expired, the Reponse is as follows:
401 Unauthorized
**Headers:**
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
Www-Authenticate: Bearer
X-Sourcefiles: =?UTF-8?B?Yzpcc3JjXFVTQi5FbnRlcnByaXNlQXV0b21hdGlvbi5BdXRoQXBpXFVTQi5FbnRlcnByaXNlQXV0b21hdGlvbi5BdXRoQXBpXGFwaVx1c2VyXGxvb2t1cFxsaWtvc3Rv?=
X-Powered-By: ASP.NET
Date: Fri, 30 Dec 2016 13:54:26 GMT
Content-Length: 61
Body
{
"message": "Authorization has been denied for this request."
}
Is there a way to set a custom Www-Authenticate header, and/or add to the body if the token is expired?
I'd like to return something like:
WWW-Authenticate: Bearer realm="example",
error="invalid_token",
error_description="The access token expired"

One way to do this is to create a custom AuthorizeAttribute and then decorate the method or class in question. Make sure to override HandleUnauthorizedRequest and then call its base method to carry on as normal and return 401.
public class CustomAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
HttpContext.Current.Response.AppendHeader("WWW-Authenticate", #"Bearer realm=""example"" ... ");
base.HandleUnauthorizedRequest(actionContext);
}
}
Usage:
[CustomAuthorize]
public IHttpActionResult Get()
{
...
}
May need some further logic around headers but should be enough to get started with.

Related

Accept zip file using postman

I have created api project in .Net Core application. I have also created Method to accept HttpRequestMessage as parameter. Now I am trying to call my api method using Postman with including file as body parameter, but my api method is not calling.
Here is code
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpPost]
public HttpResponseMessage PostFiles()
{
HttpResponseMessage result = null;
var httpRequest = HttpContext.Request;
return result;
}}
Here is Postman request data
POST /api/values/PostFiles HTTP/1.1
Host: localhost:64226
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 6adcc652-a4ab-3714-cc5e-770bd214ac7a
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="data"; filename=""
Content-Type:
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Here is the screen shot of my api call using postman.
Request body with file as parameter
Is there anything that I am missing?
Can you please help me out to call my web api with accepting file as parameter using Postman?
I think there's a problem with your route. Try this :
public class ValuesController : Controller
{
[Route("api/values/postfiles")]
[HttpPost]
public HttpResponseMessage PostFiles()
{
HttpResponseMessage result = null;
var files = Request.Form.Files;
return result;
}
}
Then try accessing the API at http://localhost:yourport/api/values/postfiles .
replace "yourport" in your port no.
You'll get the file in "fileStream" if the file is available.
In an ASP.NET Core application use IHostingEnvironment. Then you can call ContentRootPath and WebRootPath

Conditionally disable Windows authentication

I have an ASP.NET WebApi SaaS application where users have the option of being able to do HTTP Basic authentication or Windows Authentication.
My controller knows based upon some query string value whether to perform Basic HTTP authentication or Windows Authentication.
public class MyController : ApiController
{
public IHttpActionResult Get(int id)
{
if (!User.Identity.IsAuthenticated)
{
if (id == 5) // do basic for this resource only
{
return new BasicAuthenticationChallenge();
}
return Unauthorized();
}
return Ok("hello world");
}
}
My current implementation for BasicAuthenticationChallenge is simply return a 401: Unauthorized along with a WWW-Authenticate: Basic realm="api" header.
However, since Windows authentication is enabled, IIS decides to append NTLM and Negotiate headers, which causes the client to not understand the response.
... (response headers)
WWW-Authenticate: Basic realm="api"
WWW-Authenticate: NTLM
WWW-Authenticate: Negotiate
... (response headers)
How do I get IIS to stop adding headers to my WebApi's 401 responses when I am trying Basic authentication?

Dreaded CORS issue with WebAPI and token

I swear this has happened so many times to me that I actually hate CORS.
I have just split my application in two so that one handles just the API side of things and the other handles the client side stuff.
I have done this before, so I knew that I needed to make sure CORS was enabled and allowed all, so I set this up in WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Enable CORS
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
// Web API configuration and services
var formatters = config.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var serializerSettings = jsonFormatter.SerializerSettings;
// Remove XML formatting
formatters.Remove(config.Formatters.XmlFormatter);
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
// Configure our JSON output
serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
serializerSettings.Formatting = Formatting.Indented;
serializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
serializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
// Configure the API route
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
As you can see, my first line Enables the CORS, so it should work.
If I open my client application and query the API, it does indeed work (without the EnableCors I get the expected CORS error.
The problem is my /token is still getting a CORS error. Now I am aware that /token endpoint is not part of the WebAPI, so I created my own OAuthProvider (which I must point out is being used in other places just fine) and that looks like this:
public class OAuthProvider<TUser> : OAuthAuthorizationServerProvider
where TUser : class, IUser
{
private readonly string publicClientId;
private readonly UserService<TUser> userService;
public OAuthProvider(string publicClientId, UserService<TUser> userService)
{
if (publicClientId == null)
throw new ArgumentNullException("publicClientId");
if (userService == null)
throw new ArgumentNullException("userService");
this.publicClientId = publicClientId;
this.userService = userService;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var user = await this.userService.FindByUserNameAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var oAuthIdentity = this.userService.CreateIdentity(user, context.Options.AuthenticationType);
var cookiesIdentity = this.userService.CreateIdentity(user, CookieAuthenticationDefaults.AuthenticationType);
var properties = CreateProperties(user.UserName);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
context.AdditionalResponseParameters.Add(property.Key, property.Value);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == this.publicClientId)
{
var redirectUri = new Uri(context.RedirectUri);
var expectedRootUri = new Uri(context.Request.Uri, redirectUri.PathAndQuery);
if (expectedRootUri.AbsoluteUri == redirectUri.AbsoluteUri)
context.Validated();
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
As you can see, In the GrantResourceOwnerCredentials method I enable CORS access to everything again. This should work for all requests to /token but it doesn't.
When I try to login from my client application I get a CORS error.
Chrome shows this:
XMLHttpRequest cannot load http://localhost:62605/token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:50098' is therefore not allowed access. The response had HTTP status code 400.
and Firefox shows this:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS request failed).
For testing purposes, I decided to use fiddler to see if I could see anything else that might give me a clue as to what is happening. When I try to login, FIddler shows a response code as 400 and if I look at the raw response I can see the error:
{"error":"unsupported_grant_type"}
which is strange, because the data I am sending has not changed and was working fine before the split.
I decided to use the Composer on fiddler and replicated what I expect the POST request to look like.
When I Execute it, it works fine and I get a response code of 200.
Does anyone have any idea why this might be happening?
Update 1
Just for reference, the request from my client app looks like this:
OPTIONS http://localhost:62605/token HTTP/1.1
Host: localhost:62605
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:50098
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36
Access-Control-Request-Headers: accept, authorization, content-type
Accept: */*
Referer: http://localhost:50098/account/signin
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
from the composer, it looks like this:
POST http://localhost:62605/token HTTP/1.1
User-Agent: Fiddler
Content-Type: 'application/x-www-form-urlencoded'
Host: localhost:62605
Content-Length: 67
grant_type=password&userName=foo&password=bar
Inside of
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
Get rid of this:
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
Currently you are doing the CORS thing twice. Once with .EnableCors and also again by writing the header in your token endpoint.
For what it's worth, in my OWIN startup class I have this at the very top:
app.UseCors(CorsOptions.AllowAll);
I also do NOT have it in my WebAPI register method, as I'm letting the OWIN startup handle it.
Since OAuthAuthorizationServer runs as an Owin middleware you must use the appropriate package Microsoft.Owin.Cors to enable CORS that works with any middleware in the pipeline. Keep in mind that WebApi & Mvc are just middleware themselves in regards to the owin pipeline.
So remove config.EnableCors(new EnableCorsAttribute("*", "*", "*")); from your WebApiConfig and add the following to your startup class.
Note app.UseCors it must precede the app.UseOAuthAuthorizationServer
app.UseCors(CorsOptions.AllowAll)
#r3plica
I had this problem, and it is like Bill said.
Put the line "app.UseCors" at the very top in Configuration method()
(before ConfigureOAuth(app) is enough)
Example:
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
HttpConfiguration config = new HttpConfiguration();
ConfigureWebApi(config);
ConfigureOAuth(app);
app.UseWebApi(config);
}
We ran into a similar situation and ended up specifying some CORS data in the system.webServer node of the web.config in order to pass the preflight check. Your situation is slightly different than ours but maybe that would help you as well.
Here's what we added:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS" />
</customHeaders>
</httpProtocol>
It turns out that there was no issue with CORS at all. I had an interceptor class that was modifying the headers incorrectly. I suggest for future reference, anyone else having these issues, if you have your CORS set up either in WebConfig.cs or your Startup class or even the web.config then you need to check that nothing is modifying your headers. If it is, disable it and test again.

Using POST for .NET ApiController action with name starting with "Delete"

I'd like to have an ApiController with a controller action that accepts POST requests and has a name that starts with Delete. I'm using the following jQuery:
function DeleteMyDomainObjectButton_OnClick() {
var $div = $(this).closest("div.domain-object");
var url = baseUrl + "Api/MyBusiness/DeleteMyDomainObject";
var dto = {
"MyDomainObjectId": $div.find("input[name=MyDomainObjectId]").val()
};
$.ajax({
complete: DeleteMyDomainObjectAjax_OnComplete
, dataType: "json"
, contentType: "application/json"
, accept: "applcation/json"
, data: JSON.stringify(dto)
, error: DeleteMyDomainObjectAjax_OnError
, method: "POST"
, success: DeleteMyDomainObjectAjax_OnSuccess
, url: url
});
}
I keep getting a 405: {"Message":"The requested resource does not support http method 'POST'."}. The response headers are:
HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Allow: DELETE
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Tue, 07 Jul 2015 20:45:35 GMT
Content-Length: 73
I did check the request headers, and it actually is sending a POST. Here's my controller:
public class BaseApiController : ApiController
{
/* Properties for All API Controllers set by DI */
}
public class MyBusinessController : BaseApiController
{
public virtual MyDomainObject FooBar(DeleteMyDomainObjectDto dto)
{
return this.DeleteMyDomainObject(p_input);
}
[HttpPost] // Doesn't work.
// [AcceptVerbs(HttpVerbs.Post)] // Also doesn't work.
public virtual MyDomainObject DeleteMyDomainObject(DeleteMyDomainObjectDto dto)
{
return BusinessLogic.DeleteMyDomainObject(dto)
}
}
Now, if I change the controller action that I reference from jQuery to FooBar, it works.
Question: How can I turn off the magic sauce that makes the ApiController only allow DELETE requests for controller actions with names starting with the string Delete? Or am I doing something else wrong?

Cannot post object from .net Client web-api web service

I have one .net client which tries to make http request to web api service
here is my Request:
public List<Category> GetCategories()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:54558/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Task<string> response = client.GetStringAsync("api/CategoryApi/");
List<Category> lstCategory = JsonConvert.DeserializeObjectAsync<List<Category>>(response.Result).Result;
return lstCategory;
}
public void Create(Category category)
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var stringContent = new StringContent(category.ToString());
HttpResponseMessage responseMessage = client.PostAsync("api/CategoryApi/", stringContent).Result;
}
and in my webapi
public IEnumerable<Category> GetCategories()
{
return categoryRepository.data;
}
public string PostCategory(Category category)
{
categoryRepository.add(category);
return "MessageOk";
}
SO when I make request to my GetCategories action of the web-api everything is OK.
and no matter what I do it seems that .net application cannot find the Post action of the web-api and I never actually see entering in Postcategory method
as I have also put breakpoints here.
I only get the error
atusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Pragma: no-cache
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNccG9zdGdyZXNcRGVza3RvcFxXZWJBcGlTZXJ2aWNlXFdlYkFwaVNlcnZpY2VcYXBpXENhdGVnb3J5QXBpXA==?=
Cache-Control: no-cache
Date: Sat, 13 Jun 2015 17:55:16 GMT
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 1022
Content-Type: application/json; charset=utf-8
Expires: -1
}}
What may be the issue. Thak you in advance
You are posting just a Category.ToString which if you didn't override ToString to be a Json or XML string, it will fail on the server side because there is no way to deserialize the content into a Category object. You should serialize the Category on the client before posting it. Also make sure your request headers include the proper Content-Type of application/json. By posting StringContent, the Content-Type won't be application/json. You are setting the Accept header, but that only describes the data coming back to your client, not the data you are posting. One last thing, I would not use the same HttpClient for both the get and the post request. Each method should use it's own HttpClient so you don't have any extra headers depending on the call.

Resources