adding cors to aspx web api 2 hybrid - asp.net

I've added Web API 2 to an existing vb aspx web forms project. and the routing went into the global asax application_start because I do not have an app_start folder with WebApiConfig as you do in a standard web api project. I downloaded CORS from the nugget package manager add attempted to enable CORS
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the application is started
RouteTable.Routes.MapHttpRoute(
name:="DefaultApi",
routeTemplate:="api/{controller}/{id}",
defaults:=New With {.id = RouteParameter.Optional}
)
Dim cors = New EnableCorsAttribute("*", "*", "*")
GlobalConfiguration.Configuration.EnableCors(cors)
End Sub
however whenever I attempt to run an html page that is making calls to my web api through jquery ajax I receive.
Cross-Origin Request Blocked: The Same Origin Policy
disallows reading the remote resource at https://xxxxx/specialdev/api/WSFobOrigin.
(Reason: CORS header 'Access-Control-Allow-Origin' missing)
So I'm not quite sure what I am missing I attempting adding it to each controller as well.
Public Class WSFobOriginController
Inherits ApiController
<EnableCors("*", "*", "*")>
<HttpGet>
<CustomAuthentication>
<Authorize(Roles:="WebService")>
Public Function logon() As IHttpActionResult
Return Ok("successfully loggon on")
End Function
Here is the ajax call (I tried it with and without the crossDomain: true)
this.logon = function () {
$('#signin').prop('disabled', true);
$.ajax({
url: "https://xxxxxxxx.dir.ad.dla.mil/specialdev/api/WSFobOrigin",
type: "GET",
datatype: "json",
crossDomain: true,
beforeSend: function (xhr) {
$('#logonSpinner').show();
xhr.setRequestHeader("Authorization", "Basic " + btoa(self.userName() + ":" + self.password()));
},
success: function (data) {
self.loggedon(true);
},
error: function (xhr, status, error) {
$('#signin').prop('disabled', false);
$('#logonSpinner').hide();
$('#logonError').show();
self.logOnErrorMessage("Status: " + xhr.status + " Message: " + xhr.statusText)
}
});
}
just noticed one more thing that is a bit odd to me. when I run the web api locally (through visual studio) and change my client jquery ajax call to the local url it works.
URL Protocol Method Result Type Received Taken Initiator Wait‎‎ Start‎‎ Request‎‎ Response‎‎ Cache read‎‎ Gap‎‎
http://localhost:52851/api/WSFobOrigin HTTP OPTIONS 200 420 B 31 ms CORS Preflight 0 16 0 15 0 203
and
URL Protocol Method Result Type Received Taken Initiator Wait‎‎ Start‎‎ Request‎‎ Response‎‎ Cache read‎‎ Gap‎‎
http://localhost:52851/api/WSFobOrigin HTTP GET 200 application/json 447 B 218 ms XMLHttpRequest 16 15 203 0 0 0
but when I change the client to point to the actual server the preflight aborts and the type no longer says OPTIONS it is null
URL Protocol Method Result Type Received Taken Initiator Wait‎‎ Start‎‎ Request‎‎ Response‎‎ Cache read‎‎ Gap‎‎
https://xxxxxxx.dir.ad.dla.mil/specialdev/api/WSFobOrigin HTTPS (Aborted) 0 B 47 ms CORS Preflight 0 47 0 0 0 796
some other posts had suggested adding a filter which I tried but that does not seem to work either
Imports System.Web.Http.Filters
Public Class AllowCors
Inherits ActionFilterAttribute
Public Overrides Sub OnActionExecuted(actionExecutedContext As HttpActionExecutedContext)
If actionExecutedContext Is Nothing Then
Throw New ArgumentNullException("actionExecutedContext")
Else
actionExecutedContext.Response.Headers.Remove("Access-Control-Allow-Origin")
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*")
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type")
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Methods", "GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS")
End If
MyBase.OnActionExecuted(actionExecutedContext)
End Sub
End Class
and decorating my controller with allowcors
<AllowCors>
<EnableCors("*", "*", "*")>
<HttpGet>
<CustomAuthentication>
<Authorize(Roles:="WebService")>
Public Function logon() As IHttpActionResult
Return Ok("successfully loggon on")
End Function
but still no luck
status: 404
Method: OPTIONS
Request Headers: Host: xxxxxx.dir.ad.dla.mil
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: null
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Connection: keep-alive
Response Headers: Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
X-Frame-Options: SAMEORIGIN
Date: Wed, 23 Mar 2016 16:53:06 GMT
Content-Length: 1245

You can configure CORS support for the Web API at three levels:
At the Global level
At the Controller level
At the Action level
To configure CORS support at the global level,
first install the CORS package (Which you already did)
and then open WebApiConfig.cs file from App_Start folder.(here you said you dont have that folder)
Dim cors = New EnableCorsAttribute("http://localhost:5901", "*", "*")
config.EnableCors(cors)
(As you are not using that method, then we will go to next Level)
Action Level
<EnableCors(origins := "*", headers := "*", methods := "*")>
<HttpGet>
<CustomAuthentication>
<Authorize(Roles:="WebService")>
Public Function logon() As IHttpActionResult
Return Ok("successfully loggon on")
End Function
In the above method you need to set parameters to allow all the headers and support all the HTTP methods by setting value to star.
Controller Level
<EnableCors(origins := "*", headers := "*", methods := "*")> _
Public Class ClassesController
Inherits ApiController
End Class
In this you need to set parameters to allow all the headers and support all the HTTP methods by setting value to star. you can exclude one of the actions from CORS support using the [DisableCors] attribute.
So finally Here are the Attributes of EnableCors
There are three attributes pass to EnableCors:
Origins: You can set more than one origins value separated by commas. If you want any origin to make AJAX request to the API then set origin value to wild card value star.
Request Headers: The Request header parameter specifies which Request headers are allowed. To allow any header set value to *
HTTP Methods: The methods parameter specifies which HTTP methods are allowed to access the resource. To allow all methods, use the wildcard value '*'. Otherwise set comma separated method name to allow set of methods to access the resources.
So combining above points in VB you need to Declare as below
<EnableCors(origins := "http://localhost:XXX,http://localhost:YYYY", headers := "*", methods := "POST,GET")> _
Public Class ClassesController
Inherits ApiController
End Class
Update
Try to Add this Config to your web-config
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, PUT, POST, DELETE, HEAD, OPTIONS" />
<add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
<add name="Access-Control-Allow-Headers" value="Content-Type, Accept, Authorization" />
</customHeaders>

I think you forgot adding Microsoft.AspNet.Cors. If you use Visual Studio, you can add use this way:
Tools-> Nuget Package Manager ->Manage Nuget Packages for Solutions
You should find Microsoft.AspNet.Cors and install to api project

Related

Identity Server OAuth Resource Owner Password Grant always returns invalid_client

new Client
{
ClientId = "esmifavorito",
ClientName = "esmifavorito-client",
Enabled = true,
ClientSecrets = new List<ClientSecret>
{
new ClientSecret("esmifavorito".Sha256()) //PQ/pIgjXnBfK67kOxGxz9Eykft6CKPkPewR3jUNEkZo=
},
Flow = Flows.ResourceOwner,
//RequireConsent = false,
//AllowRememberConsent = false,
//ClientUri = "http",
RedirectUris = new List<string>
{
"https://localhost:44304",
},
ScopeRestrictions = new List<string>
{
},
AllowedCorsOrigins = new List<string>
{
"https://localhost:44304",
"http://localhost:50655",
"chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm",
"*",
},
PostLogoutRedirectUris = new List<string>
{
"https://localhost:44304",
},
AccessTokenType = AccessTokenType.Jwt,
IdentityTokenLifetime = 3000,
AccessTokenLifetime = 3600,
AuthorizationCodeLifetime = 300
}
I have registered my client, with implicit flow it works but I need to implement a login form so I'm trying Resource owner password credentials grant.
I'm doing requests to the endpoint with Postman in Chrome (that's why I added the chrome-extension to CORS, just to see if that was the error...)
I've tried a lot of requests (using https)
POST /connect/token HTTP/1.1
Host: localhost:44302
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=test&password=testuser&client_id=esmifavorito
-
POST /connect/token HTTP/1.1
Host: localhost:44302
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=test&password=testuser&client_id=esmifavorito&client_secret=PQ%2FpIgjXnBfK67kOxGxz9Eykft6CKPkPewR3jUNEkZo%3D
-
POST /connect/token HTTP/1.1
Host: localhost:44302
Authorization: Basic ZXNtaWZhdm9yaXRvOlBRL3BJZ2pYbkJmSzY3a094R3h6OUV5a2Z0NkNLUGtQZXdSM2pVTkVrWm89
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=test&password=testuser
Those should have worked but I'm always getting invalid_client
The error log is empty, I don't know if I have done the tracer registration right
LogProvider.SetCurrentLogProvider(new DiagnosticsTraceLogProvider());
app.UseIdentityServer(new IdentityServerOptions
{
LoggingOptions = new LoggingOptions {
IncludeSensitiveDataInLogs = true,
WebApiDiagnosticsIsVerbose = true,
EnableWebApiDiagnostics = true,
//EnableHttpLogging = true
},
SiteName = "Thinktecture IdentityServer3 - UserService-AspNetIdentity",
SigningCertificate = Certificate.Get(string.Format(#"{0}\bin\IdentityServer\IdentityServerEMFDev.pfx", AppDomain.CurrentDomain.BaseDirectory), "KG0yM0At"),
Factory = idSvrFactory,
CorsPolicy = CorsPolicy.AllowAll,
AuthenticationOptions = new AuthenticationOptions
{
IdentityProviders = ConfigureAdditionalIdentityProviders,
},
}
);
With this in web.config
<trace autoflush="true"
indentsize="4">
<listeners>
<add name="myListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="Trace.log" />
<remove name="Default" />
</listeners>
</trace>
The client data is correct since I have succesfuly logged in with implicit flow.
What am I missing? This is getting on my nerves, I'm reading the OAuth RFC and I don't see why this shouldn't work.
I tried the new version of Postman (I don't know its number, but now it runs on the desktop as a chrome app), I copied the values from the old Postman version and now everything works.
POST /connect/token HTTP/1.1
Host: localhost:44302
Authorization: Basic ZXNtaWZhdm9yaXRvOmVzbWlmYXZvcml0bw==
Cache-Control: no-cache
Postman-Token: fc4acc63-29f2-6a37-b92c-b62034b13c29
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=test&password=testuser&scope=write
This is the resulting request.
In Postman 1 I have the same thing (excluding the Postman-Token) and it gives me invalid_client. I even used a similar Firefox tool with the same results.
I don't know how is this possible.
Could it be something with the chrome-extension://?
I'm gonna answer myself but if someone knows what is happening here I'll be eternally grateful.
It seems per this article that Authorization must be sent in the header
https://github.com/IdentityServer/IdentityServer3/issues/1520
In my case I had the same problem and I noticed it was due to my HttpClient setting a "custom" authorization header.
If using IdentityModel to request the resource owner password token, notice the Authorization header must contain client_id:client_secret in base 64.
In my case I was setting a different authorization header and, although the body values were correct, the IResourceOwnerPasswordValidator was not even attempting to validate the request.

Caching of Web Service not working when request has query string

I'm trying to implement the client-side caching of web service calls, and based on information from the web, I was able to do it according to the SetCachingPolicy() function as shown in code 1 below.
I was able to successfully get it working with a web method, RetrieveX, but not with method RetrieveY. I noticed that RetrieveX has no parameters and RetrieveY has one string parameter, and on inspection under Fiddler, the difference seems to be that the HTTP GET request of the web service call from RetrieveY has a query string for the parameter.
All HTTP GET web service calls so far without a query string is doing the caching properly, but not this call that has a query string in it.
Examination under Fiddler indicates that RetrieveX has the following caching information in output 1, and RetrieveY has the information in output 2.
Is this a limitation of this caching method or can I do something to get the client side caching of RetrieveY working?
Code 1: SetCachingPolicy
private void SetCachingPolicy()
{
HttpCachePolicy cache = HttpContext.Current.Response.Cache;
cache.SetCacheability(HttpCacheability.Private);
cache.SetExpires(DateTime.Now.AddSeconds((double)30));
FieldInfo maxAgeField = cache.GetType().GetField(
"_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
maxAgeField.SetValue(cache, new TimeSpan(0, 0, 30));
}
Code 2: RetrieveX
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
public string[] RetrieveX()
{
SetCachingPolicy();
// Implementation details here.
return array;
}
Code 3: RetrieveY
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
public string[] RetrieveY(string arg1)
{
SetCachingPolicy();
// Implementation details here.
return array;
}
Output 1: RetrieveX caching info
HTTP/200 responses are cacheable by default, unless Expires, Pragma, or Cache-Control headers are present and forbid caching.
HTTP/1.0 Expires Header is present: Wed, 12 Sep 2012 03:16:50 GMT
HTTP/1.1 Cache-Control Header is present: private, max-age=30
private: This response MUST NOT be cached by a shared cache.
max-age: This resource will expire in .5 minutes. [30 sec]
Output 2: RetrieveY caching info
HTTP/200 responses are cacheable by default, unless Expires, Pragma, or Cache-Control headers are present and forbid caching.
HTTP/1.0 Expires Header is present: -1
Legacy Pragma Header is present: no-cache
HTTP/1.1 Cache-Control Header is present: no-cache
no-cache: This response MUST NOT be reused without successful revalidation with the origin server.
I ran into this issue as well, I thought I'd share what worked for me. The underlying issue is that VaryByParams is not being set on the response. If you add this to your SetCachingPolicy() method RetrieveY should begin working as expected:
cache.VaryByParams["*"] = true

ASP.NET MVC ignoring Content-Length?

I've been having some problems with missing post data in ASP.NET MVC which has lead me to investigate how ASP.NET MVC deals with invalid content lengths. I had presumed that a post with a invalid content length should be ignored by MVC.NET but this doesn't seem to be the case.
As an example, try creating a new ASP.NET MVC 2 web application and add this action to the HomeController:
public ActionResult Test(int userID, string text)
{
return Content("UserID = " + userID + " Text = " + text);
}
Try creating a simple form that posts to the above action, run fiddler and (using "Request Builder") modify the raw data so that some of the form data is missing (e.g. remove the text parameter). Before executing the request, remember to un-tick the "Fix Content-Length header" checkbox under the Request Builder options then set a break point on the code above and execute the custom http request.
I find that the request takes a lot longer than normal (30 seconds or so) but to my amazement is still processed by the controllers action. Does anyone know if this is expected behavior and, if so, what would you recommend to safeguard against invalid content-lengths?
ASP.NET does not ignore the Content-Length request header. Consider the following controller action as an example which simply echoes back the foo parameter:
[HttpPost]
public ActionResult Index(string foo)
{
return Content(foo, "text/plain");
}
Now let's make a valid POST request to it:
using (var client = new TcpClient("127.0.0.1", 2555))
using (var stream = client.GetStream())
using (var writer = new StreamWriter(stream))
using (var reader = new StreamReader(stream))
{
writer.Write(
#"POST /home/index HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:2555
Content-Length: 10
Connection: close
foo=foobar");
writer.Flush();
Console.WriteLine(reader.ReadToEnd());
}
As expected this prints the response HTTP headers (which are not important) and in the body we have foobar. Now try reducing the Content-Length header of the request:
POST /home/index HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:2555
Content-Length: 5
Connection: close
foo=foobar
Which returns a single f in the response body. So as you can see an invalid HTTP request could lead to incorrect parsing of the parameters.

JQuery consuming ASP.Net Web Service webserver - Request format is unrecognized for URL unexpectedly ending in

Done a lot of Googling on this but cant seem to find an answer.
When I call my web service from Jquery I am receiving the error
Request format is unrecognized for URL unexpectedly ending in '/AirportSearchGeneric'.
Factors
I am currently calling a webservice that is on the same machine but on a different webserver (calling app is port 64004 and receiving app is 1400) - possible cross "domain" issue? Both are local host.
Both are using the test web server that is part of visual studio.
I have tried adding the 2 protocols to the web.config (add name="HttpGet" add name="HttpPost")
The error occures in the Event Viewer on the server.
I get the following in Firebug...
OPTIONS AirportSearchGeneric
http://localhost:1400/services/airportservice.asmx/AirportSearchGeneric
500 Internal Server Error
localhost:1400
... not seen OPTIONS before but the request is being accessed with a POST request.
JQuery code...
$.ajax({
type: "POST",
url: "http://localhost:1400/services/airportservice.asmx/AirportSearchGeneric",
data: "{'criteria':'EGBB', 'maxResults':'10'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
alert(msg.d);
}
});
Web service code...
[WebService(Namespace = "http://localhost/WebServices")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class AirportService : WebService
{
[WebMethod]
[ScriptMethod(ResponseFormat=ResponseFormat.Json)]
public string AirportSearchGeneric(string criteria, int maxResults)
{
IAirportService svc = new Airports.AirportService.AirportService();
List<AirportSearchResult> res = svc.AirportSearchGeneric(criteria, maxResults);
DataContractJsonSerializer serializer = new DataContractJsonSerializer(res.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, res);
string jsonString = Encoding.Default.GetString(ms.ToArray());
ms.Close();
return jsonString;
}
}
... dont think its a problem in here as when debugging, no code in here gets executed.
Pretty sure I have covered off all of the reasons I have read as to why this occurs so would be greatful for any advice on how I can get this working.
Cheers.
For reference the firebug headers are as follows:
Host localhost:1400
User-Agent Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 ( .NET CLR 3.5.30729; .NET4.0E)
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language en-gb,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 115
Connection keep-alive
Origin http://localhost:64004
Access-Control-Request-Me... POST
(No response is received in firebug apart from the 500 error, there is no html response at all).
Using different ports on the same machine is considered cross domain and not allowed by the browser as you were suspecting.
You either call the other port by tricking the browser using JSONP (if can limit yourself to only using GET requests) or change one of the ports to be the same as the other.
Ajax Cross Domain Calls

Determine if a HTTP request is a soap request on HttpApplication.AuthenticateRequest

I there a way to know if a request is a soap request on AuthenticateRequest event for HttpApplication? Checking ServerVariables["HTTP_SOAPACTION"] seems to not be working all the time.
public void Init(HttpApplication context) {
context.AuthenticateRequest += new EventHandler(AuthenticateRequest);
}
protected void AuthenticateRequest(object sender, EventArgs e) {
app = sender as HttpApplication;
if (app.Request.ServerVariables["HTTP_SOAPACTION"] != null) {
// a few requests do not enter here, but my webservice class still executing
// ...
}
}
I have disabled HTTP POST and HTTP GET for webservices in my web.config file.
<webServices>
<protocols>
<remove name="HttpGet" />
<remove name="HttpPost" />
<add name="AnyHttpSoap" />
</protocols>
</webServices>
Looking at ContentType for soap+xml only partially solves my problem. For example,
Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 1131
Content-Type: text/xml
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ro
Host: localhost
mymethod: urn:http://www.wsnamespace.com/myservice
Some clients instead of having the standard header SOAPAction: "http://www.wsnamespace.com/myservice/mymethod", have someting like in example above. "mymethod" represents the method in my web service class with [WebMethod] attribute on it and "http://www.wsnamespace.com/myservice" is the namespace of the webservice. Still the service works perfectly normal.
The consumers use different frameworks (NuSOAP from PHP, .NET, Java, etc).
You could look at Request.ContentType property, which if properly set by the client should be
application/soap+xml; charset=utf-8
The utf-8 part may not be present.
Aside from that, surely you can just check the URL, and if it's a webservice one then that tells you what it is.
I always give web services their own port. That way I don't have to filter every HTTP request that comes across port 80. Or rather, I can filter port 80 for browser-oriented issues, and SOAP/SOA ports for other types of attacks.
IMAO, mixing (potentially) sensitive business data with public data just so you don't have to open another hole in the firewall is thumbing your nose at the very reason you have a firewall in the first place.
You could also go down the harder route and figure things out based on everything else that's below HTTP headers. What I mean by that is, to analyze things like below, which is the SOAP request body - part of the request...
<soap:Envelope xmlns:soap="..." soap:encodingStyle="...">
IBM
Have you tested the System.Web.HttpContext.Current.Request.CurrentExecutionFilePathExtension ??
Normally this would be .asmx for webservices (json and xml), as long as you handle the service of course.
I am using following code to identify the request type. Try this if it match your requirment. Mark as answer if it help you.
if (request.Headers["SOAPAction"] != null || request.ContentType.StartsWith("application/soap+xml"))
return ServiceRequestTypes.SoapRequest;
else if ("POST".Equals(request.RequestType, StringComparison.InvariantCultureIgnoreCase) && request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.InvariantCultureIgnoreCase))
return ServiceRequestTypes.HttpPostRequest;
else if ("POST".Equals(request.RequestType, StringComparison.InvariantCultureIgnoreCase) && request.ContentType.StartsWith("application/json", StringComparison.InvariantCultureIgnoreCase))
return ServiceRequestTypes.AjaxScriptServiceRequest;
return ServiceRequestTypes.Unknown;

Resources