Jersey client doesn't redirect properly on 301 response (after PATCH) - http

I use a Jersey client to make a request to an API to change the ID of a resource, using the PATCH method:
Client client = ClientBuilder.newClient()
.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // To allow PATCH
...
// Setting uri, path, accessToken and entity used below
...
WebTarget target = client.target(uri).path(path);
Response response = target.request(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, BEARER + accessToken)
.method("PATCH", entity);
I see in Fiddler that the ID is changed successfully: a "301 Moved Permanently" response with a Location header is returned, the Location header contains the URL with the new ID.
Then again in Fiddler I see that the Jersey client calls the URL from the Location header, right after the first request, but using the PATCH method again...
Shouldn't it make the request to the URL in the Location header using GET since the response code was 301?
Thanks.

Related

ASP.NET 4.5 Rest API's work in Unity Android/iOS build but fails with "Unknown error" in Unity WebGL build

I have scoured every possible forum for this and somehow have not gotten my WebGL to consume my ASP.NET 4.5 REST API's.
From what I can tell it is possibly related to WebGL requiring CORS, but even enabling this I cannot get the game to communicate with my API's
So either there's something wrong with the way I have implemented global CORS settings in ASP.NET or something else is breaking.
To be clear these API's are running perfectly well on Android/iOS/Windows builds and even in the editor.
What I have done so far:
Installed the Microsoft CORS build as recommended by Microsoft's documentation relating to it, then added the following code to the WebAPIConfig class in Visual Studio:
public static void Register(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
////new code
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
This is also in my web.config:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
<add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
</customHeaders>
</httpProtocol>
I need these settings global so I used the "*" as indicated by the documentation to include all domains, method types, and headers because I use ASP.NET token authentication for my API.
Here is a code snippet that gets the token in the Unity project (just to be clear, this works on other platforms, only throws an error in a WebGL build)
public IEnumerator login()
{
string url = API.ROUTEPATH + API.TOKEN;
WWWForm form = new WWWForm();
form.AddField("grant_type", "password");
form.AddField("username", API.APIUSERNAME);
form.AddField("password", API.APIPASSWORD);
UnityWebRequest uwr = UnityWebRequest.Post(url, form);
uwr.SetRequestHeader("Content-Type", "application/json");
yield return uwr.SendWebRequest();
try
{
if (uwr.isNetworkError)
{
Debug.Log(uwr.error);
}
else
{
APIAuthToken returnauth = JsonUtility.FromJson<APIAuthToken>(uwr.downloadHandler.text);
if (!string.IsNullOrEmpty(returnauth.access_token))
{
API.hasAuth = true;
API.token = returnauth.access_token;
Debug.Log(returnauth.access_token);
}
}
}
catch
{
}
}
uwr.error produces the following, very helpful error: Unknown Error So I'm not even sure if it is CORS related, it's just my best guess based on the research I have done, but even with multiple different implementations of it I still sit with the same error. So if it's not a problem with the API's and with my Unity code please just ignore the ASP.NET code snippet.
cURL - A simple curl -I <endpoint> or curl -X OPTIONS -v <endpoint> can reveal a ton of information about what is happening related to CORS. It can allow you to set different origins, check preflight responses, and more.
"Let's say you have a backend API that uses cookies for session management. Your game works great when testing on your own domain, but breaks horribly once you host the files on Kongregate due to the fact that your API requests are now cross-domain and subject to strict CORS rules."
Is this your problem?
Problably on both sides if things are not set up properly will refuse to send cookies, but its good, its mean you have the control to allow what domains your sessions cookies will be sent to.
So probably you need first to configure the server to allow multiplies origins but make sure to validate the value against a whitelist so that you aren't just enabling your session cookies to be sent to any origin domain.
Example on a Node Express with CORS middleware(game ID 12345) and an origin whitelist below:
express = require('express')
var cors = require('cors')
var app = express()
var whitelist = ['https://game12345.konggames.com'];
var corsOptions = {
credentials: true,
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
};
app.use(cors(corsOptions));
app.options('*', cors(corsOptions)); // Enable options for preflight
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(8080, () => console.log(`Example app listening on port 8080!`))
cURL command to check the headers for an OPTIONS preflight request from an origin in the whitelist array:
curl -X OPTIONS -H"Origin: https://game12345.konggames.com" -v http://localhost:8080/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> OPTIONS / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Origin: https://game12345.konggames.com
>
< HTTP/1.1 204 No Content
< X-Powered-By: Express
< Access-Control-Allow-Origin: https://game12345.konggames.com
< Vary: Origin, Access-Control-Request-Headers
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
< Content-Length: 0
< Date: Tue, 24 Sep 2019 22:04:08 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
instruct the client to include cookies when it makes a cross-domain request,If the preflight response did not include Access-Control-Allow-Credentials: true, or if your Access-Control-Allow-Access is set to a wildcard (*) then the cookies will not be sent and you are likely to see errors in your browser's Javascript console:
Access to XMLHttpRequest at 'https://api.mygamebackend.com' from origin 'https://game54321.konggames.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Unity's UnityWebRequest and the older WWW classes use XMLHttpRequest under the hood to fetch data from remote servers. Since there is no option to set the withCredentials flag to true, we have to perform a pretty dirty hack when initializing our application in order to turn that on for the appropriate requests.
In your WebGL template or generated index.html:
<script>
XMLHttpRequest.prototype.originalOpen = XMLHttpRequest.prototype.open;
var newOpen = function(_, url) {
var original = this.originalOpen.apply(this, arguments);
if (url.indexOf('https://api.mygamebackend.com') === 0) {
this.withCredentials = true;
}
return original;
}
XMLHttpRequest.prototype.open = newOpen;
</script>
This snippet of code overrides the open method of XMLHttpRequest so that we can conditionally set withCredentials equal to true when desired. Once this is in place, cross-origin cookies should begin working between the Kongregate-hosted iframe domain and the game's backend servers!
info taken from here
also looks nice for this

Apache2.49 cookies not working via my ProxyPass VirtualHost

In the apache virtualHost i have these commands:
ProxyPass "/s" "http://127.0.0.1:3001"
ProxyPassReverse "/s" "http://127.0.0.1:3001"
RewriteRule ^/s/(.*) http://127.0.0.1:3001/$1 [P,L]
ProxyPassReverseCookiePath "/" "/"
The backend server is NodeJS. The proxy itself works fine. The problem is that the Node is sending a set-cookie in the HTTP header (session ID) but the browser seems to ignore it. I tested with Chromium and Firefox but none creates the cookie. I tried to change the virtualhost configuration but nothing appears to solve the problem The set-cookie command is:
set-cookie: sid=s%3AhgHWDO3D...BBUZbbOA; Path=/; HttpOnly; Secure;HttpOnly;Secure
I need your help to solve this problem. Thank you.
UPDATE
If the url is containing a direct request for the Node:
https://example.com/s/backend
it works. It creates the session is cookie. But if this URL is called from a AJAX request in the JS, it does not create the cookie.
The https://example.com load a HTML with a script load of a JS file. That JS file makes the AJAX call to the backend using the path https://example.com/s/something and in this case the cookie is never created.
Any suggestions?
UPDATE
I discovered that the problem is when i use the Fetch API to retrieve a JSON file. This code running does not create the session ID cookie:
fetch("https://localbestbatteriesonline.com/s/p.json?0103")
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
But if i have this code, it creates the cookie:
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
}
};
xhttp.open("GET", "https://localbestbatteriesonline.com/s/p.json?0103", true);
xhttp.send();
Analysing the requests, both are exactly the same. Both receive the cookie to create.
Any ideas why with the fetch does not work?
Problem solved. Using the Fetch API does not include the cookies exchange like it does in the XMLHttpRequest. Therefor, it does not create the session id cookie. To enable this, the Fetch call must have the option:
credentials:"same-origin".
fetch("https://localbestbatteriesonline.com/s/p.json?0103",{credentials:"same-origin"})
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
Now it works.

Move query parameter value to header before auth request in NGINX?

I have a location that uses ngx_http_auth_request_module. The authorization sub-request looks for a token in an Authorization header on the original request. For this particular location, the token can only be passed as a query parameter on the request (e.g. /sites/<site-id>/charts/<chart-name>?token=<token>).
Is it possible to set an Authorization header to Bearer: <token> (<token> being the value passed to the request as the token query parameter) before the auth sub-request is fired?
The location:
location ~* "^\/sites\/([0-9A-Fa-f]{8}-?([0-9A-Fa-f]{4}-?){3}[0-9A-Fa-f]{12})\/charts\/([^\/]*\/?)$" {
resolver 127.0.0.11;
auth_request /verify/token;
proxy_pass http://sites:3000/$1/charts/$3;
}

HTTP ETags and HTTP Redirects

I have a webserver that issues the ETag header on responses and checks the If-None-Match header from the client, if present. The client in this case, is not a web browser but an extension of go's builtin net/http http.Client type.
Here is my code
package util
import "net/http"
import "net/url"
type HttpClient struct {
http.Client
etags map[url.URL]string
}
func (hc *HttpClient) Do(req *http.Request) (*http.Response, error) {
const ETAG_SERVER_HEADER = "ETag"
const ETAG_CLIENT_HEADER = "If-None-Match"
//Do not attempt to use ETags on non-GET requests
if req.Method != "GET" {
return hc.Client.Do(req)
}
//Check for an existing etag
etag, ok := hc.etags[*req.URL]
if ok { //If an ETag is present, send it along
if req.Header == nil {
req.Header = http.Header{}
}
req.Header.Add(ETAG_CLIENT_HEADER, etag)
}
//Do the response
response, err := hc.Client.Do(req)
//If the response is ok
if err == nil {
if hc.etags == nil {
hc.etags = make(map[url.URL]string)
}
//Check for an ETAG from the server, store it if present
etag = response.Header.Get(ETAG_SERVER_HEADER)
if len(etag) != 0 {
hc.etags[*req.URL] = etag
}
}
return response, err
}
It is working without issue as of present.
I am only storing and sending the ETag for GET requests. While it is valid to send them for other requests, it is not in my use case as of current so I'm not bothering with it. The ETags are stored by mapping the url.URL object to a string.
My question is this. I request "http://foo.com/bar.html". The server redirects me using 302 Found and the Location header to "http://foo.com/qux.html". I then request "http://foo.com/qux.html" and get a 200 OK along with an ETag header.
With what URL do I associate the ETag header from the last response?
Could the 302 Found itself include an ETag header?
An ETag is associated with the "selected representation" of the current request. The selected representation for a 302 Found response "usually contains a short hypertext note with a hyperlink to the different URI(s)." Therefore, if a 302 response contains an ETag, the ETag is associated with that hypertext.
However, if you include an If-None-Match (or other precondition) in a request to a resource that responds with a redirect, the server will ignore the precondition. According to Section 5 of RFC 7232 (my emphasis):
A server MUST ignore all received preconditions if its response to the same request without those conditions would have been a status code other than a 2xx (Successful) or 412 (Precondition Failed). In other words, redirects and failures take precedence over the evaluation of preconditions in conditional requests.
Thus, while a 302 response can contain an ETag, it is not useful because the server may not use it in further requests to that resource.
As you can see in /src/pkg/net/http/client.go on line 266 the request-response cycle will continue until either or:
The http.Client's redirect checker (CheckRedirect field) returns an error
The response received is not a redirect (according to shouldRedirectGet)
During the cycle , previous responses will be discarded and the headers the response that client.Do returns are those of the last response.
This means that you should associate the ETag with qux.html in your example.
Could the 302 Found itself include an ETag header?
Yes it could but since it's a redirect, the client will follow it and acquire a new set of response headers from the target URL.

How to get JSON back from HTTP POST Request (to another domain)

I'm trying to use the API on a website, here's the part of the manual:
Authenticated Sessions (taken from here)
To create an authenticated session, you need to request an authToken from the '/auth' API resource.
URL: http://stage.amee.com/auth (this is not my domain)
Method: POST
Request format: application/x-www-form-urlencoded
Response format: application/xml, application/json
Response code: 200 OK
Response body: Details of the authenticated user, including API
version.
Extra data: "authToken" cookie and header, containing the
authentication token that should be
used for subsequent calls.
Parameters: username / password
Example
Request
POST /auth HTTP/1.1
Accept: application/xml
Content-Type: application/x-www-form-urlencoded
username=my_username&password=my_password
Response
HTTP/1.1 200 OK
Set-Cookie: authToken=1KVARbypAjxLGViZ0Cg+UskZEHmqVkhx/Pm...;
authToken: 1KVARbypAjxLGViZ0Cg+UskZEHmqVkhx/PmEvzkPGp...==
Content-Type: application/xml; charset=UTF-8
QUESTION:
How do I get that to work?
I tried jQuery, but it seems to have problem with XSS. Actual code snippet would be greatly appreciated.
p.s.
All I was looking for was WebClient class in C#
You need to put application/json in your Accept header, this tells the server you want it to respond in that format - not xml.
I am using rails to extract the same authentication token cookie from stage.amee.com/auth as mentioned above. it took a bit of experimentation before I created and customised the correct request object that returned a 200 OK, with the authtoken as a cookie. i haven't found an effective method of reading the request object or I would post exactly what it looks like. here is my ruby code from the app's controller
#define parameters
uri=URI.parse('http://stage.amee.com')
#path = '/auth'
#login_details = 'username=your_username&password=your_password'
#headers = {'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json'}
#create request object
req = Net::HTTP.new(uri.host, uri.port)
#send the request using post, defining the path, body and headers
resp, data = req.post(#path, #login_details, #headers)
#print response details to console
puts "response code = " << resp.code
puts "response inspect = " << resp.inspect
resp.each do |key, val|
puts "response header key : " + key + " = " + val
end
puts "data: " + data

Resources