Questions
- What is CSRF?
- How to prevent CSRF? Is Same-Site Cookie the silver bullet?
- What is JWT authentication? Is using JWT enough to prevent CSRF?
- What are the considerations when implementing JWT authentication?
CSRF
- Cross-Site Request Forgery is a type of attack in which attackers cause the user's browser to perform an unwanted action on a trusted site when the user is authenticated.
- CSRF simply takes advantage of the fact that the browser sends the cookie to the web application automatically with every request.
- There are normally 2 parts in a CSRF attack:
- first, tricking the victim into clicking a link or loading up a malicious page. This is normally done through social engineering.
- second, sending a made-up request to the victim’s browser. The request will be sent with the values that the attacker wants and any cookies that the victim has associated with that website.
- Example:
- user logins to the trusted site www.mybank.com, the bank's server authenticates and returns an authentication cookie.
- without logging out, the user visits a malicious web site. This malicious site contains the following HTML form:
<h1>You won an iPhone!</h1> <form action=”http://mybank.com/api/account" method=”post”> <input type=”hidden” name=”Transaction” value=”withdraw” /> <input type=”hidden” name=”Amount” value=”1000000" /> <input type=”submit” value=”Click Me”/> </form>
- user click on Click Me button, the forged request is sent to the bank's server along with the legitimate authentication cookie.
- The bank's server accepts the request and performs the request.
CSRF prevention
Token-Based Mitigation
- Most of the modern web frameworks have build-in functions to support this.
- The basic idea is that when user logins, the server creates a unique token for that user session and sends it back to user. The client then includes that token in every subsequence request so the server can validate the token. Because malicious site cannot get this unique token, it cannot perform any request.
- this token-based approach can be achieved either with the state (synchronizer token pattern) or stateless (encrypted based token pattern).
- Synchronizer token pattern
- CSRF token is generated in server and stored in session storage.
- CSRF token should be:
- unique per user session
- secret
- unpredictable
- When a request is issued by the client, the server-side component must verify the existence and validity of the token in the request compared to the token found in the user session storage.
=> This method is stateful, i.e server has to maintain a session storage => not convenience for stateless authentication mechanism like JWT.
- Encrypted based token pattern (ETP)
- Encrypted Token Pattern leverages encryption, rather than comparison method of Token-validation
- ETP token is generated in server:
- session ID
- timestamp (prevent replay attack)
- secret key
- Similarly, this token is returned to the client and embedded in a hidden field for forms, in the request-header parameter for AJAX requests. On receipt of this request, the server reads and decrypts the token value with the same key used to create the token.
- Once decrypted, the users' session ID and timestamp contained within the token are validated. The session ID is compared against the currently logged in user, and the timestamp is compared against the current time to verify that it's not beyond the defined token expiry time. If session-id matches and the timestamp is under the defined token expiry time, the request is allowed.
SameSite cookie
- read about SameSite cookie here
JWT authentication
- based on the fact that CSRF is only possible if the authentication cookie was sent along with the forged request, we can use JWT authentication to avoid CSRF attack.
- JWT - JSON Web Token
- is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.
- This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA.
- Structure:
[Base64(HEADER)].[Base64(PAYLOAD)].[Base64(SIGNATURE)]
- Header: contains metadata: the hashing algorithm being used (e.g., HMAC SHA256 or RSA) and the type of the token (JWT).
{ "alg": "HS256", "typ": "JWT" }
- Payload: contains verifiable security statements, such as the identity of the user and the permissions they are allowed.
{ "sub": "1234567890", "name": "Quang", "admin": true }
- Signature: used to validate that the token is trustworthy and has not been tampered with
Signature = HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload), secret)
- Because 3 parts of JWT was encoded by Base64 encoding, we should not store any sensitive data in JWT. Use jwt.io to decode JWT.
- Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
- JWT Authentication

- we can see that JWT is a stateless authentication mechanism. Server does not need to maintain a session storage because all authentication information is stored in JWT, which is stored in client side (browser).
- because JWT is sent along with the request in the
request header
, it eliminates the possibility of CSRF. - However, JWT authentication has its issues.
- Consideration when using JWT authentication
- JWT storage in client-side:
- cookie with Secure, HttpOnly, SameSite: can avoid XSS, but potentially be attacked by CSRF.
- session storage: can avoid CSRF, but potentially be attacked by XSS. To avoid the XSS attack, we can add a fingerprint:
- when creating JWT, server creates a random and unique cookie (fingerprint) and sent back to user. This fingerprint is also stored in the payload of JWT.
- when server receives a subsequence request from the client, it can verify that the fingerprint in JWT matches the value in the cookie.
- because the fingerprint is stored in Secure, HttpOnly cookie => it can avoid an XSS attack.
- No Built-In Token Revocation by the User:
- JWT only become invalid when it expires. The user has no built-in feature to explicitly revoke the validity of a token
- The solution would be creating a database to maintain a revocation list. This would add complexity to the system and eliminate the stateless property of JWT authentication.
Ref: