401 Unauthorized when sending ajax request to web api - asp.net

I've been scratching my head at this for 2 days now. I am using WebAPI version 2.2 and I am using CORS. This setup works on the server side, I am allowed to get authorized content from my web client server code but getting unauthorized in my ajax calls.
Here is my configuration:
Web API Config
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));
config.Filters.Add(new HostAuthenticationFilter(DefaultAuthenticationTypes.ApplicationCookie));
//enable cors
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Filters.Add(new ValidationActionFilter());
}
}
Startup.Auth.cs:
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(UserContext<ApplicationUser>.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieHttpOnly = true,
CookieName = "Outpour.Api.Auth"
}
);
//app.UseCors(CorsOptions.AllowAll);
//app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
(I've tried every combination of app.UseCors(CorsOptions.AllowAll) and config.EnableCors())
My Controller attribute:
[Authorize]
[EnableCors("http://localhost:8080", "*", "*", SupportsCredentials = true)]
[RoutePrefix("api/videos")]
public class VideosController : ApiController...
Web Client
Ajax Call:
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
options.crossDomain = {
crossDomain: true
};
options.xhrFields = {
withCredentials: true
};
});
function ajaxGetVideoResolutionList() {
var request = {
type: "GET",
dataType: "json",
timeout: Outpour.ajaxTimeOut,
url: Outpour.apiRoot + "/videos/resolutions"
};
$.ajax(request).done(onAjaxSuccess).fail(onAjaxError);
Cookie Creation:
var result = await WebApiService.Instance.AuthenticateAsync<SignInResult>(model.Email, model.Password);
FormsAuthentication.SetAuthCookie(result.AccessToken, model.RememberMe);
var claims = new[]
{
new Claim(ClaimTypes.Name, result.UserName), //Name is the default name claim type, and UserName is the one known also in Web API.
new Claim(ClaimTypes.NameIdentifier, result.UserName) //If you want to use User.Identity.GetUserId in Web API, you need a NameIdentifier claim.
};
var authTicket = new AuthenticationTicket(new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie), new AuthenticationProperties
{
ExpiresUtc = result.Expires,
IsPersistent = model.RememberMe,
IssuedUtc = result.Issued,
RedirectUri = redirectUrl
});
byte[] userData = DataSerializers.Ticket.Serialize(authTicket);
byte[] protectedData = MachineKey.Protect(userData, new[] { "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", DefaultAuthenticationTypes.ApplicationCookie, "v1" });
string protectedText = TextEncodings.Base64Url.Encode(protectedData);
Response.Cookies.Add(new HttpCookie("Outpour.Api.Auth")
{
HttpOnly = true,
Expires = result.Expires.UtcDateTime,
Value = protectedText
});
And last but not least, my headers.
Remote Address:127.0.0.1:8888
Request URL:http://127.0.0.1/api/videos/resolutions
Request Method:GET
Status Code:401 Unauthorized
**Request Headersview source**
Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:no-cache
Host:127.0.0.1
Origin:http://localhost:8080
Pragma:no-cache
Proxy-Connection:keep-alive
Referer:http://localhost:8080/video/upload
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36
**Response Headersview source**
Access-Control-Allow-Credentials:true
Access-Control-Allow-Origin:http://localhost:8080
Cache-Control:no-cache
Content-Length:61
Content-Type:application/json; charset=utf-8
Date:Wed, 08 Oct 2014 04:01:19 GMT
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/8.0
WWW-Authenticate:Bearer
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
Developer tools and fiddler claim there were no cookies sent with the request.

I believe you are mixing between cookies authentication and bearer tokens here, you are not sending an access token in the Authorization header with your request, that is why you keep getting 401.
As well you need only to allow CORS using application.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); and remove it else where from your controllers attribute even from configuration.
Check my Repo here where I've implemented CORS and the front end is AngularJS too. it is working correctly. Here is the live demo too for this repo, open developer tools and monitor the requests, you should see pre-flight request before you see your HTTP get request.
If you need just to protect your API using bearer tokens then I recommend you to read Token Based Authentication post

This could be because because your API is not on the came URL as your calling application. Your URL for the API is:
http://127.0.0.1/ (ignoring the folder path - which doesn't matter)
..but you're calling it from http://127.0.0.1:8888 which counts as a separate site as the port is different. Because the browser thinks the site is different, it will not send the cookie.
Have you tried testing it from a page hosted on the same URL as the API (with the same port)?
Most importantly: Check that you can see the Cookie being sent through in Fiddler.
You might also find more relevant information about getting this to work on this answer

Related

HttpClient with Windows Authentication returns 401 Unauthorized with Xamarin.Forms

I'm trying to make a Login for my Xamarin.Forms App and the Windows Authentication does not work for me, but just from my Xamarin-Code. When I try to browse the Webservice through Postman or just a regular Browser for example I return my result. Before Windows Authentication I used Basic Authentication what worked well and uncomplicated for me.
I have a IIS Server 8.5 where my Webservice runs from.
public async Task<bool> GetLoginAccess(UserCredential userCredentials)
{
HttpClientHandler authHandler = new HttpClientHandler()
{
PreAuthenticate = true,
AllowAutoRedirect = true,
UseDefaultCredentials = true
};
using (HttpClient client = new HttpClient(authHandler))
{
_client.BaseAddress = new Uri(Properties.Resources.URL_Webservice_Login);
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var response = await _client.GetAsync("api/LoginChecker?application=***&user=***").ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var access = JsonConvert.DeserializeObject<bool>(json);
return access;
}
else
{
return false;
}
}
}
Error:
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Date: Tue, 14 Jun 2022 08:21:30 GMT
Server: Microsoft-IIS/8.5
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Android-Received-Millis: 1655194890132
X-Android-Response-Source: NETWORK 401
X-Android-Selected-Protocol: http/1.1
X-Android-Sent-Millis: 1655194890044
X-Powered-By: ASP.NET
Content-Length: 1344
Content-Type: text/html
}}
I don't know what I'm making wrong...
EDIT
So now my Code looks like this:
var credentialsCache = new NetworkCredential(userCredentials.User, userCredentials.Password, "DOM");
HttpClientHandler authHandler = new HttpClientHandler()
{
PreAuthenticate = true,
Credentials = credentialsCache,
AllowAutoRedirect = true,
UseDefaultCredentials = false
};
using (HttpClient client = new HttpClient(authHandler, true))
{
client.BaseAddress = new Uri(Properties.Resources.URL_Webservice_Login);
var response = await _client.GetAsync("/api/LoginChecker?application=***&user=***");
if (response.IsSuccessStatusCode)...
And still not working. If im routing to this Page with the Simulator or another physical android device, i get to enter my Credentials, which works fine there, so something in my Code is wrong... :/
EDIT #2
After hours and hours of failures finding my mistake I decided to try WebClient instead of HttpClient. And guess what: IT WORKS GREAT!
From the IIS documentation:
Windows authentication (formerly named NTLM, and also referred to as Windows NT Challenge/Response authentication) is a secure form of authentication because the user name and password are hashed before being sent across the network. (see here)
I'm not an expert in IIS-topics, neither in NTLM, but I guess in order to do so, you'd have to be logged in as a domain user on the respective device. If you are, the device sends the hashed user credentials to IIS, which in turn authenticates you. Since you are logged in on your machine, your browser and postman are able to send this hashed credentials, get authenticated and are allowed to access whatever you have to access. You're not logged in with your user credentials on the mobile device, hence it is not able to send the credentials and therefor you're neither authenticated, nor authorized, which results in the 401 Unauthorized. Even if the app runs in the simulator on your local machine, it does not know about the credentials, same outcome.
This answer suggests that you'd have to set the HttpClientHandler.Credentials to a CredentialsCache instance (code snipped shamelessly copied) in order to make the authentication/authorization work
var credentialsCache = new CredentialCache
{
{
httpRequestMessage.RequestUri,
"NTLM",
new NetworkCredential("user", "password", "domain")
}
};
var handler = new HttpClientHandler { Credentials = credentialsCache };
var client = new HttpClient(handler);
await client.PostAsync(url, data).ConfigureAwait(false);
It seems that the problem is at the client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));.
All of the solutions I found on the SO use the the code client.DefaultRequestHeaders.Add("Key Name", "Key Value");
You can check the following cases:
how to solve Http 401 error,but working in postman,not working in xamarin forms
I am getting StatusCode: 401 "Unauthorized" in a HttpClient.PostAsync (C#)
I'm getting error 401 (unauthorized) while trying to make a GET request from my API that has a JWT token

Cookie created in WebAPI response is never sent in subsequent client requests. Seeking example with round trip Server->Client->Server

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.

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.

WebAPI & SignalR web application - Authenticating & Authorizing

I have a WebAPI solution and I use token authentication, so the flow is the following:
user tries to login using the username and password
if the credentials are correct, he is given a token in order to use in the following requests (to be placed in the header of the AJAX requests).
Now SignalR comes into play. Since it uses WebSockets, you are not able to pass a header.
Searching for a solution, I've come across the following:
Actually pass a header to SignalR - not an option since it forces SignalR to use longPolling.
$.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };
Pass the same token used for authenticating WebAPI calls as a query string/or store it in a cookie for SignalR, then create a provider that somehow unwraps and expands the token into identity. I followed this blog post but I seem to be missing something.
Again pass the token as a query string/or as a cookie, but this time create a custom Authorize Attribute to Authorize SignalR hubs or methods.
Again a blog post about this. The problem with this solution was in Unprotect method on the token.
The final and the easiest solution is to also enable cookie authentication, keep using bearer token authentication for WebAPI calls and let the OWIN Middleware authorize calls on the hubs. (this solution actually works).
Now, the issue is that using the default template for a WebAPI application with Individual User Accounts (so with token authentication), whenever I send an AJAX request to the API it also sends the cookie.
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
app.UseOAuthBearerTokens(OAuthOptions);
}
}
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 }
);
}
}
Even if I did this:
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Authorization: Bearer 2bTw5d8Vf4sKR9MNMqZsxIOPHp5qtXRTny5YEC_y7yWyrDLU0__q8U8Sbo7N7XBjPmxZXP18GRXjDVb3yQ9vpQnWXppRhVA8KDeGg2G5kITMxiOKvGMaKwyUGpORIeZ0UHyP9jA2fX9zPwzsCqHmq-LoGKls0MQNFjXgRGCCCvro5WPMAJcLs0kUoD_2W_TOTy9_T-koobw-DOivnazPo2Z-6kfXaIUuZ1YKdAbcSJKzpyPR_XrCt4Ma2fCf-LcpMPGo4gDFKfxWdId0XtfS9S-5cXmmOmGM4Y6MkAUK8O9sZlVrpmpvV0hjXF2QwfLtQViPyEctbTr1vPBNn014n60APwGSGnbUJBWMvJhqcjI5pWoubCmk7OHJrn052U_F3bDOi2ha1mVjvhVY1XMAuv2c3Pbyng2ZT_VuIQI7HjP4SLzV6JjRctfIPLEh67-DFp585sJkqgfSyM6h_vR2gPA5hDocaFs73Qa22QMaLRrHThU0HM8L3O8HgFl5oJtD
Referer: http://localhost:15379/index.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,ro;q=0.6
Cookie: .AspNet.Cookies=E71BnnTMv8JJ4hS9K46Y2yIbGMQCTS4MVBWBXezUYCSGXPbUPNZh98Q0IElQ0zqGyhB7OpYfdh10Kcy2i5GrWGSiALPPtOZUmszfAYrLZwG2JYiU5MSW80OGZVMY3uG2U1aqvvKJpv7eJwJSOoS4meD_3Qy8SwRzTg8feZArAE-REEXSsbPfq4jQBUUbxfDAyuPVRsLNfkn4oIAwZTs85IulRZI5mLnLqOS7VLejMGIWhkuyOWvvISu1pjsP5FMDXNwDkjv2XCaOpRzZYUxBQJzkcdpDjwW_VO2l7HA263NaG_IBqYpLqG57Fi-Lpp1t5Deh2IRB0VuTqAgrkwxifoBDCCWuY9gNz-vNjsCk4kZc8QKxf7el1gu9l38Ouw6K1EZ9y2j6CGWmW1q-DobaK9JXOQEPm_LGyaGPM5to2vchTyjuieZvLBAjxhLKnXdy34Z7MZXLVIwmpSmyPvmbIuH9QzOvTWD-I1AQFJyCDw8
Do you see an easier way of authenticating SignalR with token authentication? Is this final approach (if I manage to suppress the sending of the cookie with requests) viable in production?
When working on that particular project, I ended up simply using cookie authentication and CSRF, but I was still interested in seeing how to authenticate WebApi and SignalR using bearer token authentication.
For a working sample, please see this GitHub repository.
I ended up creating an OAuthBearerTokenAuthenticationProvider class that tries to retrieve the token from the cookie.
public class OAuthBearerTokenAuthenticationProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];
if (!String.IsNullOrEmpty(tokenCookie))
context.Token = tokenCookie;
return Task.FromResult<object>(null);
}
}
And here is the method in the Startup class that deals with authentication:
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerTokenAuthenticationProvider()
});
}
For a working sample, please see this GitHub repository.
Hope this helps someone at some point.

HttpClient does not send cookies from CookieContainer

I'm developing a ASP WebAPI (ASP MVC 4) application with a WPF (.NET 4.0) client, using Visual Studio 2012. The client needs to login to the server. I use FormsAuthentication with an authentication cookie to login. The login already works fine in ASP MVC.
The problem is that, although the login is sucessfully executed on the server and the cookie is sent back to the client, the cookie is not sent in subsequent calls to the server, even though the CookieContainer is reused with the auth cookie set.
Here is a simplified version of the code:
CLIENT
public async Task<UserProfile> Login(string userName, string password, bool rememberMe)
{
using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
using (var httpClient = new HttpClient(handler))
{
httpClient.BaseAddress = new Uri("http://localhost:50000/");
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var result = await httpClient.PostAsJsonAsync("api/auth/login", new
{
username = userName,
password = password,
rememberMe = rememberMe
});
result.EnsureSuccessStatusCode();
var userProfile = await result.Content.ReadAsAsync<UserProfile>();
if (userProfile == null)
throw new UnauthorizedAccessException();
return userProfile;
}
}
public async Task<ExamSubmissionResponse> PostItem(Item item)
{
using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
using (var httpClient = new HttpClient(handler))
{
httpClient.BaseAddress = new Uri("http://localhost:50000/");
var result = await httpClient.PostAsJsonAsync("api/Items/", item);
}
}
SERVER
[HttpPost]
public HttpResponseMessage Login(LoginModel model)
{
if (this.ValidateUser(model.UserName, model.Password))
{
// Get user data from database
string userData = JsonConvert.SerializeObject(userModel);
var authTicket = new FormsAuthenticationTicket(
1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(10 * 15),
model.RememberMe,
userData
);
string ticket = FormsAuthentication.Encrypt(authTicket);
var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return response;
}
return null;
}
First I debugged the problem using Fiddler2 (I used the base address as "http://localhost.fiddler:50000/" to view local traffic). Then I suspected that fiddler might be interfering, so I just debugged with Visual Studio 2012.
What I have tried and verified:
The server is reached by the Login method
The user is sucessfully authenticated with the data sent from the client
The cookie is set on the server
The cookie is in the response (verified with fiddler)
The cookie is in the CookieContainer after the operation. There is a strange thing here: the domain of the cookie in the container is set as "localhost" (verified with VS2012 debugger). Shouldn't it be "http://localhost:50000" ? When I try to get the cookies of the container using cookieContainer.GetCookies(new Uri("http://localhost:50000")) it returns nothing. When I try it using cookieContainer.GetCookies(new Uri("localhost")) it gives me an invalid Uri error. Not sure what's going on here.
The cookie is in the container just before the PostItem request is made. The container is correctly set in the HttpClient when the statement httpClient.PostAsJsonAsync is reached.
The cookie is not sent to the server (I checked it with fiddler and in the Application_PostAuthenticateRequest method in the Global.asax.cs, verifying this.Request.Cookies)
I suspect the cookie is not being sent due to a domain mismatch in the CookieContainer, but why the domain is not set as it should in the CookieContainer in the first place?
Your problem is that you are not setting any path on the cookie that you send back from your Web Api controller.
There are two things that control where cookies are sent:
The domain of the cookie
The path of the cookie
Regarding the domain, the consensus seems to be that the port number should no longer (but still might) be a factor in evaluating the cookie domain. See this question for more info about how port number affects the domain.
About the path: Cookies are associated with a specific path in their domain. In your case, the Web Api is sending a cookie without specifying it's path. By default the cookie will then be associated with the path of the request/response where the cookie was created.
In your case the cookie will have the path api/auth/login. This means the the cookie will be sent to child paths (for lack of a better term) of this path but not to parent or sibling paths.
To test this, try:
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login")
This should give you the cookie. So should this:
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login/foo/bar")
These on the other hand will not find the cookie:
cookieContainer.GetCookies(new Uri("http://localhost/")
cookieContainer.GetCookies(new Uri("http://localhost/api/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/foo")
cookieContainer.GetCookies(new Uri("http://localhost/api/Items/")
To fix the issue, simply add the path "/" (or perhaps "/api") to the cookie before sending the resonse:
...
string ticket = FormsAuthentication.Encrypt(authTicket);
var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
cookie.Path = "/";
var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
...

Resources