To start, we need to stop comparing JWT and Cookie! They are not the same. Instead, the right comparison would be:
- Token-based authentication
- Session-based authentication
In this PoC, we will only talk about token-based authentication (specifically using JWT). You should check out the tradeoffs between using token-based authentication and session-based authentication.
When using token-based authentication, the most asked question is "where to store JWT in the browser?".
Generally, there are 2 common ways to store authentication tokens in the browser:
- Cookie - vulnerable to XSS attacks
- Local Storage - vulnerable to to CSRF/XSRF attacks
The goal of this PoC is to demonstrate and implement a "modified" version of the "Cookie-to-header token" approach to protect your site against XSS & CSRF attack (credits: 100% Stateless with JWT) when storing authentication tokens in Cookie.
Some other CSRF protection approaches are:
This is vulnerable to CSRF attacks because our jwt
would be automatically sent by our browser (client) to the server.
sequenceDiagram
autonumber
actor Client
participant Server
Client->> Server: POST /login
Note right of Server: sign <jwt>
Server-->>-Client: 200 Logged in
Note over Server,Client: Set-Cookie: <jwt>
Note left of Client: stores <jwt> in cookie
Client->> Server: POST /hello
Note over Client,Server: Cookie: <jwt>
Note right of Server: verify <jwt>
Server-->>-Client: 200 OK
While our jwt
is automatically sent by our browser, the server requires the client to also send along a csrfToken
for verification.
sequenceDiagram
autonumber
participant Local Storage
actor Client
participant Server
Client->> Server: POST /login
Note right of Server: sign <jwt> with generated <csrfToken> as claim
Server-->>-Client: 200 Logged In
Note over Server,Client: Set-Cookie: <jwt> & X-CSRF-Token: <csrfToken>
Note left of Client: stores <jwt> in cookie
Client-->>Local Storage: setItem("csrf-token", <csrfToken>);
Local Storage->>Client: getItem("csrf-token");
Client->> Server: POST /hello
Note over Client,Server: Cookie: <jwt> & X-CSRF-Token: <csrfToken>
Note right of Server: verify <jwt> & compare <jwt> <csrfToken> & X-CSRF-Token <csrfToken>
Server-->>-Client: 200 OK
- A Cloudflare account
- Install Wrangler CLI for Cloudflare Workers deployment
❯ npm ci
# ...omitted for brevity...
❯ wrangler login
# ...omitted for brevity...
❯ wrangler kv:namespace create "USERS"
# ...omitted for brevity...
Add the following to your configuration file in your kv_namespaces array:
{ binding = "USERS", id = "bd445a5887f6437cb4ec9adb11a19106" }
❯ wrangler secret put SALT
# ...omitted for brevity...
✨ Success! Uploaded secret SALT
wrangler dev --remote
To test a CSRF attack, check out this little tool (credits: Shrikant).