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?
Related
I am using ASP.Net Core 2 WepAPI controller.
Current version of Chrome(currently 64).
Angular 5 SPA.
Need to work on localhost.
This is controller method:
public class TestController : ControllerBase
{
[HttpGet, Route("api/[controller]/test")]
public async Task<IActionResult> Get()
{
Response.Cookies.Append("testcookie", "testvalue", new CookieOptions
{
Path = "/",
Domain = "localhost",
Expires = DateTime.UtcNow.AddHours(6),
HttpOnly = false,
Secure = false
});
return Ok("Test Ok.");
}
}
And I don't think it matters, but this is my client code.
private async test2() {
const res = await this.http.get('http://localhost:59879/api/test/test').toPromise();
}
When I look in Chrome Console -> Network Tab -> the request line -> Cookies Tab; I see my response cookie(s) but no request cookie. I also do not see request cookie in HttpContext.Request.Cookies on subsequent requests.
How do I create any kind of cookie,
that is created server side, and returned from controller method,
and client/browser sends to server?
Assume server is on localhost:59879.
I have tried many iterations of setting the Domain to localhost, 127.0.0.1, false and excluding it. Tried not specifying CookieOptions at all, no Expires, and various combinations of HttpOnly and Secure settings to no avail.
These are some resources I have tried.
HTTP Cookies in ASP.NET Web API
Cookies on localhost with explicit domain
Chrome localhost cookie not being set
Googled the following: "net core 2 cookie -authentication"
Update - I see a recent SO that may have the same cause of what I am experiencing.
This code:
public class TestController : ControllerBase
{
[HttpGet, Route("api/[controller]/test")]
public async Task<IActionResult> Get()
{
Response.Cookies.Append("testcookie", "testvalue", new CookieOptions
{
Path = "/",
Domain = "localhost",
Expires = DateTime.UtcNow.AddHours(6),
HttpOnly = false,
Secure = false
});
return Ok("Test Ok.");
}
}
it will work if you accessing by browser. You trying access via ajax api, it won't work like that. If you analyze the code here:
Response.Cookies.Append("testcookie", "testvalue", new CookieOptions
{
Path = "/",
Domain = "localhost",
Expires = DateTime.UtcNow.AddHours(6),
HttpOnly = false,
Secure = false
});
this response is not access by browser, it access return ajax success.
See here can-an-ajax-response-set-a-cookie thread.
Or solution in mapping-header-cookie-string-to-cookiecollection-and-vice-versa.
That's not better approach access API and set-cookies via server, better it just do it on client side as discussion this thread.
I got this error when the Web portal contacts an self-hosted WebAPI service located in other server than the Web portal server:
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'someportaldomain' is therefore not
allowed access.
The self-hosted service always return "Access-Control-Allow-Origin" header in every response, but if the browser decides to do a pre-flight before calling the url, the user gets this error on the js console.
I post the answer here, since i didn't find any straight on and had to pull it out from several posts.
The Solution is to add Options method to the Self-hosted service, like this:
[ServiceContract]
public interface IACT_HttpService
{
//[FaultContract(typeof(ValidationFault))]
[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
void GetOptions();
//My other methods
...
}
public class ACT_HttpService : IACT_HttpService
{
//Adjust this method to restrict the origin as needed
public void GetOptions()
{
log.Debug("Get options fired");
//These headers are handling the "pre-flight" OPTIONS call sent by the browser
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE,OPTIONS");
//Add here any special header you have
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-A}llow-Origin", "*");
WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
}
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.
I am not able to set the keep-alive in the response header with the web Api. I am running in the IIS Express(Development server). I am using the message handler which sets the same
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (request.Headers.Authorization == null)
{
var NonAuthenicateresponse = new HttpResponseMessage(HttpStatusCode.Unauthorized);
NonAuthenicateresponse.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("NTLM"));
return NonAuthenicateresponse;
}
var response=await base.SendAsync(request,cancellationToken);
response.Headers.ConnectionClose = false;
return response;
}
Am i missing anything extra parameter for the same.
To add more details , I am trying to implement NTLM authentication for the application. Once the request is made from the browser we issue a 401 unauthorized and the negotiation happening but it is able to send the Authorization header back in the subsequent request.
We need to explicitly say to the browser about the Keep-alive the connection.
I have a Web API 2 application which uses Asp.Net Identity for Authentication and Authorization. I also have a custom Message Handler to do an additional custom check to finalize the authentication (as well as parse some API data that is necessary to connect to the right schema on a multi-tenancy data store).
Here is my working code for the message handler:
public class AuthenticationHeadersHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Headers.Contains("Authorization"))
{
// Authenticate
var auth = request.GetOwinContext().Authentication.AuthenticateAsync(OAuthDefaults.AuthenticationType);
// Get user ID from token
identityId = auth.Result.Identity.GetUserId();
// Please note, the oAuth token would have successfully authenticated by now
// ... Do some custom authentication and data gathering
if (failedCheck)
{
// If user fails checks, I would like to force Asp.Net Identity to
// return 401 not authorized here, or flag the request as not authorized
}
}
}
}
Considering the code above, how can I manually flag the request as unauthorized even if it passed the initial authentication?
I think this should work:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Headers.Contains("Authorization"))
{
// Authenticate
var auth = request.GetOwinContext().Authentication.AuthenticateAsync(OAuthDefaults.AuthenticationType);
// Get user ID from token
identityId = auth.Result.Identity.GetUserId();
// Please note, the oAuth token would have successfully authenticated by now
// ... Do some custom authentication and data gathering
if (failedCheck)
{
// Return 401 not authorized here.
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
// If we got here send an HTTP status of 200
return new HttpResponsMessage(HttpStatusCode.OK);
}