Google App Engine Modules + HTTPS + Cross-Origin Resource Sharing
Recently I found myself in a situation where I don’t yet have a signed certificate for my website (I will have one, I promise) but I needed to publish a page with a form in it.
The form in question prompts the user for personal information (nothing crazy, name & email) to start a flow that schedules an event and sends a couple of emails (nothing fancy).
To give more context about the situation, it is important to mention that I am already using my domain to serve my website. There is nothing wrong with using the default {{app-id}}.appspot.com but I wanted to promote a sense of identity.
Obviously, I believe in privacy and information security so I realized I wouldn’t sleep comfortably knowing that some user might compromise his privacy. I needed to provide a temporal solution that will allow me to keep my site online using my domain while preserving the user information private and secure.
I gave it a thought and came up with a temporal work around using Google App Engine HTTPS configuration. To sum it up the idea was to serve the the site using my domain over HTTP but submit the information using GAE’s default domain over HTTPS. To accomplish my goal 2 things need to happen:
- Perform an AJAX request to another domain without violating the CORS policy enforced by the browser.
- Accept requests only from my website. One way to achieve it (but not the only one I used) is to validate the HTTP Origin request header to inform the browser if the client is allowed to consume the service.
For the sake of simplicity, I will explain it in the most standard way:
- Enable HTTPS in your appengine-web.xml
<ssl-enabled>true</ssl-enabled> - Enforce the use of HTTPS in you deployment descriptor (web.xml)
<security-constraint>
…
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint> - Do not set the form action property (<form action=”#”>…</form>) and intercept its submit event to disable it.
- Configure the handler to serve a signed URL upon request (GET) and to set the “Access-Control-Allow-Origin” response header to meet the “Origin” declared in the request.
At this point I should (and probably must) verify that the value of the “Origin” header complies to my domain.
The signed URL is the one I will use to submit the information I need. As you may have already noticed, I am using GAE Java runtime environment so in this case my request handler is a servlet. - The served URL will be constructed using GAE ModulesService to point to the correct version version of the desired module. Keep in mind that “google does not issue SSL certificates for double-wildcard domains hosted at appspot.com, therefore with HTTPS you must use the string “-dot-” instead of “.” to separate subdomains”.
Taking into account the above, the constructed URL should look like the following:
https://{{version}}-dot-{{module}}-dot-{{app-id}}.appspot.com?token={{one-time-signed-token}} - Gather the information from the form fields, pack it into a JSON object, stringify it, aim for the provided URL and shoot the request (POST).
At this point, almost certainly I will see an error regarding security.
Enter Preflighted Requests…
To sum it up, preflighted requests:
“will first send and HTTP(S) request by te OPTIONS method to the resource on the other domain in order to determine whether the actual request is safe and secure”.
A request is preflighted if:
“it uses POST and the Content-Type header is other than application/x-www-form-urlencoded, multipart/form-data, text/plain or if it sets custom headers”
The request I am trying to execute pretty much falls into both conditions so it is being preflighted. How to comply?, overriding the OPTIONS handler to set the following headers:
- [header=Access-Control-Allow-Origin, value={{http://your-domain}}]
- [header=Access-Control-Allow-Method, value=GET,POST,OPTIONS]
- [header=Access-Control-Allow-Header, value=Content-Type, X-Header]
response
.setHeader("Access-Control-Allow-Origin", origin);
response
.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
response
.setHeader("Access-Control-Allow-Headers", "Content-Type, ...");
… values may vary depending on the actual use case …
Notice how the Access-Control-Allow-Origin is set to the URL of the website without using http protocol (not https), this is important since protocols, domains and ports must exactly match the source of the request to be in compliance with the security policy enforced by the browser.
The POST handler must also set the Access-Control-Allow-Origin response header to contain the same value as mentioned above. Also, before processing the request it is important to validate that it is executed using one of your signed URLs and invalidate it immediately afterwards.
A good idea to invalidate the URLs is to do it non-blocking way (in another thread). Remember to use datastore transactions to ensure the URL in not used more than once !
Persist the URLs using AsyncDatastoreService, Memcache them, process them in a Queue, in short, make use of every resource available in the platform to speed things up and serve users as fast as you can because…
Fast is better than slow !
Test… (always test!)

Looking good !!
I must stress that this is a temporary work around and it is in no way a long term solution. I will be serving my website over HTTPS with a valid certificate for my domain.
See you in the comments !!