Any mechanism you come up with, that does not involve the user entering a secret for every request will be vulnerable to XSS. The solution is to try and eliminate XSS from your domain, not to have a solution that is secure in the presence of XSS vulnerabilities. A strong CSP policy will help you in browsers that support it.
Javascript can access cookies, as long as you don't mark them HttpOnly
, but you don't need it to as the cookies will be sent automatically with every request. So cookies are going to be more secure then LocalStorage as long as you mark them Secure
and HttpOnly
.
So I would go for Scenario 2, using cookies. If it needs to be stateless the cookie should be a signed and HMAC'ed token with a time expiry in addition to the cookie timeout, such that the servers stop accepting these tokens after the timeout. It will be hard to securely implement the logout functionality without storing state on the server, you can instruct the client to throw away the token, but without a blacklist or whitelist maintained on the server-side you will be vulnerable to the token being used after the user has logged out.
Response to Comments
CSP, is it something I need to implement on the load balancer in front of A? A is just static html and javascript files.
CSP will need to be served on the top level page (The url the user sees in the address bar), this is A in your case. You should be able to configure you static web server to add headers to every response. Doco. You could also do it at the load balancer but that seems more complex then is required.
If I add CSP on requests going between A and B, ...
CSP is added between A and the browser, not between A and B. In the CSP policy you can configure the browser to be able to communicate with B.
somebody could easily modify the javascript to send different headers.
They could only do this if your html/javascript is being sent over HTTP, not over a secure link e.g. HTTPS. If your top level URL is HTTP all scenarios will be susceptible to man-in-the-middle attacks. The communication between A and the browser and B and the browser need to be over HTTPS to have any security.
A and B are running on different domains, B adds a CORS filter to only allow JSON POST requests from a specific domain for A, should the CSP also be added alongside the CORS headers?
You will need both CORS headers, sent from B to allow communication from A. And CSP headers sent from A to restrict javascript execution as a mitigation strategy for any XSS vulnerabilities in your site.
Since the authentication token is generated, validated and invalidated in the Authentication Service, would HMAC really be needed
In most situations a HMAC makes tokens more secure. If you need to remain stateless you will need a HMAC. If you Authentication Service is state-full, that is it has a list of valid, current tokens, then HMACs are less important as long as your token id's are not guessable. If this is the case consider GUID's for your tokens.
The cookie should obviously be created at A as that is what the browser is interacting with.
My understanding is the browser is also interacting with B, via AJAX, hence the need for the CORS policy. As it is B that is managing the login state and requiring the authentication token the cookie should be set by B. Also as A is just serving static content it would be more elegant if A did not require to set cookies, a dynamic process.
Seeing as it's completely HTML and JavaScript, I'm guessing it's impossible to access data inside the cookie if HttpOnly and Secure is turned on?
Yes with HttpOnly turned on javascript on the browser will be unable to access the cookie. In your case it would be best if the cookie was set by B, with HttpOnly
and Secure
set, this should happen on a successful login call. Then all subsequent calls to B will automatically have the cookie added to them, and the javascript will not be able to access the cookies.
Timeline - As I would see it.
- User navigates to https://a/index.html
- A responds with a HTML page
including CSP headers, the HTML includes a
<script
src="https://a/code.js">
tag.
- The browser fetches https://a/code.js
- code.js puts up a login box, user enters username and password and instructs the browser to send an AJAX POST request to https://b/login
- The browser sends a HEAD request to https://b/login to ensure it can make posts.
- B responds with the CORS headers allowing POSTs from https://a
- The browser sends a POST request to https://b/login containing the username and password
- B checks the password and if successful returns a success message and sets a cookie which is
HttpOnly
and Secure
. This cookie contains a token such as a random GUID. This token is stored in B's state.
- The user clicks a button
- A request is sent to https://b/customer/5/update containing the cookie
- B validates the cookie value and proceeds to do the action requested e.g. updates the customer.
- The user click logout
- The browser sends an AJAX request to https://b/logout
- B invalidates the token contained in the cookie in it's state, and send a message back to the client clearing the cookie there also.