Redirecting and preventing cache in Symfony2 - symfony

I am doing this:
domain.com/route-name/?do-something=1
..which sets a cookie and then redirects to this using a 302 redirect:
domain.com/route-name/
It allows an action to take place regardless of the page viewing (cookie stores a setting for the user).
I am using the default Symfony2 reverse-proxy cache and all is well, but I need to prevent both the above requests from caching.
I am using this to perform the redirect:
// $event being a listener, usually a request listener
$response = new RedirectResponse($url, 302);
$this->event->setResponse($response);
I've tried things like this but nothing seems to work:
$response->setCache(array('max_age' => 0));
header("Cache-Control: no-cache");
So how do I stop it caching those pages?

You have to make sure you are sending the following headers with the RedirectResponse ( if the GET parameter is set ) AND with your regular Response for the route:
Cache-Control: private, max-age=0, must-revalidate, no-store;
Achieve what you want like this:
$response->setPrivate();
$response->setMaxAge(0);
$response->setSharedMaxAge(0);
$response->headers->addCacheControlDirective('must-revalidate', true);
$response->headers->addCacheControlDirective('no-store', true);
private is important and missing in coma's answer.
The difference is that with Cache-Control: private you are not allowing proxies to cache the data that travels through them.

Try this on your response:
$response->headers->addCacheControlDirective('no-cache', true);
$response->headers->addCacheControlDirective('max-age', 0);
$response->headers->addCacheControlDirective('must-revalidate', true);
$response->headers->addCacheControlDirective('no-store', true);
You can use annotations too:
http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html
And take a look at:
Why both no-cache and no-store should be used in HTTP response?

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.

Symfony request not returning Date header

I'm working on an API and I can't get Sf2 to catch the "Date" parameter from the request header... Demo is below. I'm testing my API via Postman.
$date = $this->request->headers->get('Date');
$auth = $this->request->headers->get('Authorization');
echo $date; // NULL
echo $auth; // whatever i pased.
A very strange behaviour indeed ! Could anyone know why ?
The Date header is a restricted one and it's not possible to overwrite it unless you use the Interceptor Chrome Extension (see this official link for details).
This is a list of restricted headers:
Accept-Charset
Accept-Encoding
Access-Control-Request-Headers
Access-Control-Request-Method
Connection
Content-Length
Cookie
Content-Transfer-Encoding
Date
Expect
Host
Keep-Alive
Origin
Referer
TE
Trailer
Transfer-Encoding
Upgrade
User-Agent
Via
That's why you're getting an empty header.
change
$date = $this->request->headers->get('Date');
to
$date = $this->request->headers->get('X-Date');

After logging in, being redirected to https://site/favicon.ico

Something very odd is happening with my ASP.NET application where I've created a login portal, and whenever the login is complete about 75% of the time I get redirected to favicon.ico.
For example, here is the login page that I sign in to:
Then after I enter my e-mail and password, I somehow end up here:
The ReturnUrl is being used as a redirect after login, and %2f being the forward slash, it should take the user to the default. Instead it somehow ends up at favicon.ico.
I really don't understand this, especially since it does not happen all of the time. I do see the behavior across Firefox, Chrome, and IE11. In the cases where it does not happen, I land on https://172.16.0.20, the default.
Here is how I route in the controller:
[HttpPost]
public ActionResult Login(Models.LoginViewModel login)
{
User u = null;
if (ModelState.IsValid)
{
if (authProvider.Authenticate(login.Email, login.Password, login.RememberMe, out u))
{
return Redirect((TempData["ReturnUrl"] == null) ? Url.Action("Index", "Home") : (string)TempData["ReturnUrl"]);
}
else
{
ModelState.AddModelError("", "Incorrect username or password");
return View();
}
}
else
{
return View();
}
}
Here is my only route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
EDIT: From using Fiddler, as suggested, it looks like at some point it tries to get favicon.ico and the server responds with its location, changing the ReturnUrl:
GET http://172.16.0.20/favicon.ico HTTP/1.1
Host: 172.16.0.20
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:34.0) Gecko/20100101 Firefox/34.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
Cookie: ASP.NET_SessionId=gp1osbxvm5uawvfmsu3is5db
Connection: keep-alive
HTTP/1.1 302 Found
Location: /Account/Login?ReturnUrl=%2ffavicon.ico
Server: Microsoft-IIS/8.5
X-Powered-By: ASP.NET
Date: Mon, 15 Dec 2014 15:51:29 GMT
Content-Length: 156
What is the proper way to handle this?
The reason favicon.ico is called is because that is the standard thumbnail image url. Browsers use the image data to populate the little website logo in the left of your chrome tabs (other browsers may put the logo elsewhere).
Additionally, if you don't specify a mobile-optimized logo, when you save a web page to your home screen in iOS (and probably android too), they will use the favicon.ico as the icon. Additionally, different browsers have different rules for how often they refresh the file so you can't really predict when it will get requested, and all the major browsers check it.
You should solve this problem generally. That is, if your code sticks the last requested URL into the TempData["ReturnUrl"], you will also have issues if a user happens to request any images as well from a tag like
<img src="/someOtherReturnUrl.jpg" />
So one solution is to change the place where you populate TempData["ReturnUrl"] to only store if it's a Controller that is serving a page as opposed to a static file. There are ways to check this in an HttpModule or in an ActionFilter which is probably what you're using.
Alternatively, and this is probably simpler to do, change your code to be:
if (authProvider.Authenticate(login.Email, login.Password, login.RememberMe, out u))
{
return Redirect((!string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]))
? Request.QueryString["ReturnUrl"]
: Url.Action("Index", "Home");
}
Because it looks like your login page is of the url pattern: /account/login?ReturnUrl=SomeUrl and the query string is more consistent than whatever the previous request happened to be. Seriously, on the web you cannot guarantee request order, ever. (or at least it's usually a bad idea to assume a rational order because users and browsers do all sorts of wacky and inventive things).

How can I remove 'no-cache="Set-Cookie"' when I add a cookie to a HttpResponse?

I'm currently returning a cookie from a web service with code like this:
HttpResponse response = ...;
var cookie = new HttpCookie(cookieName)
{
Value = cookieValue,
Expires = expiresDate,
HttpOnly = true,
Path = "/",
Secure = true,
};
response.Cookies.Add(cookie);
This results in the automatic addition of a no-cache directive in my Cache-Control header:
Cache-Control: public, no-cache="Set-Cookie", must-revalidate, max-age=60
My client happens to handle this directive by straight up not caching the response at all. If I manually remove the no-cache directive before it hits the client, caching works great.
How can I prevent .NET from automatically adding this directive to responses containing cookies?
HttpResponse determines whether it should add this directive based on whether the Cookies collection is non-empty. Therefore, if you add the header manually you can hide its presence from .NET:
response.AddHeader("Set-Cookie", String.Format(
"{0}={1}; expires={2}; path=/; secure; HttpOnly",
cookieName, cookieValue, expiresDate.ToString("R")));

Resources