PayPal: Use stateless CSRF to secure your JS application.


Hey! You may have found this post because you are interested in safeguarding your JS applications, or you may have heard about other open source projects we have.

jwt-csrf, a stateless CSRF solution for safeguarding JavaScript apps, read it today!

We built and tested this when we built PayPal Checkout last year. In addition to discussing jwt-csrf, I'd like to share our experiences and learnings from re-architecting PayPal Checkout.

Why are there no states?

We reached a snag with too much state on the server side around a year ago, forcing us to take a step back and rethink several parts of our architecture.

If you've used Express before, you'll be familiar with req.session, which is essentially a global variable scoped to the user's session and hung out of the request object. This is normally not a problem for lone developers or very small development teams, but as more developers are added to the mix, the majority of the problems become connected to managing state.

"Shared mutable state is the root of all evil". —intelligent individuals

Here are a few examples of our anguish:

Slow Experimentation - We couldn't evolve our client side without modifying the server code.

Fragile middleware - Our commonly used middleware functions must be stacked in a precise order to work. Some of these middlewares will be dependent on req.session settings that are set at random by other middlewares. This phone should ring if you're using Express.

So, global mutability is terrible. But what are the alternatives?

We made the decision to be entirely stateless. We develop atomic APIs that perform one thing and one thing alone, rather than "god" endpoints that do a lot of orchestration and rely on status flags in req.session. Get cart details, add credit card, and so on.

In addition, we have to adapt the method we approve users with CSRF. If you're unfamiliar with CSRF, here's a quick primer.

What exactly is CSRF?

CSRF (or "Cross-Site Request Forgery") assures that server-side requests are legitimate and originate from your application. The last sentence is crucial.

If the user is currently logged in to PayPal, session cookies with the scope of paypal.com will be stored in their browser. If the user then visits a compromised website, the affected website can make requests to paypal.com on the user's behalf (through the cookie). Consider an attacker linking their bank account to a user's PayPal account and then moving the user's PayPal balance to the attacker's account. oops!

Fortunately, CSRF protection can prevent this exploit.

The Synchronizer pattern is the most prevalent CSRF pattern, in which the CSRF token is generated server-side, dumped on the initial page render, then sent back to the server-side on subsequent requests (usually as a hidden form parameter or AJAX request token header). When the token is validated on subsequent requests, it is validated against the key in req.session that is pending.

By the way, nothing is wrong with this! Under the krakenjs umbrella, we open sourced lusca. If you're creating a session-enabled Express app, it's a great choice for CSRF prevention. However, we are on the verge of being stateless.

So you're wondering how you can provide CSRF protection without sessions. You claimed to have become stateless! Enter jsonwebtoken here (JWT).

jsonwebtoken

JSON Web Token (JWT) is an open standard (RFC 7519) that offers a concise and self-contained method for securely transmitting information as JSON objects between parties. Because it is digitally signed, this information can be checked and trusted.

We will save our state in the token because there is no state on the server side to compare with.

JWT example encrypted —

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Encrypt an example JWT —

type:'JWT', alg:'HS256'.

{time:timestamp,logged in:true}.

signature

When decrypted, this token has a header, payload, and signature. Payload and signature are the most critical.

The payload comprises the user's assertion that they are who they claim to be. In the payload, you can define whatever you wish. However, it's usually a good idea to include a timestamp, something individually recognisable, such as the user's account ID, and some indicator of whether or not they're logged in. When validating the token, you can check if it has expired, if the user account ID matches, and if they are logged in (for example: if they are accessing an API that requires authentication).

The signature confirms that the sender is who they claim to be and that the message was not altered with in route. It is created by digitally signing the header, payload, and secret (through secret).

The payload is legal if the signature is valid.

wonderful news! JWTs are generated and validated using multiple CSRF techniques by jwt-csrf. This is how it works.

jwt-csrf

jwt-csrf includes server-side and client-side (optional) functionality for generating/validating JWTs using the CSRF schema.

If you use Express on the server, you can utilise it as middleware. You can use it programmatically even if you don't utilise Express! We use three ways to generate and validate tokens. It is set to dual commit mode by default.

We give some code on the client side (optional) to alter the XHR to deliver the token with every request.

If you're asking if this can be used in production, the answer is yes! For over a year, we've been serving millions of users every day.