Authentication
How It Works
To authenticate that a user and their client are who they say they are, a few steps need to occur:
- User sends credentials (e.g. username and password) to server
- Server verifies these credentials are accurate by checking their data store, usually via a test of the password's hash in their database[3]
- Server returns to the client whether or not these credentials are accurate
This is easy enough, but becomes unwieldy if the application needs you to verify your identity for various actions, as it will have to repeat these three steps for every request, which is very costly.
Token
One solution is to use a token protocol.
- User sends credentials to server
- Server verifies the credentials are accurate by checking against the existing data in the database
- Server creates a unique token with an optional expiration timestamp and stores it in a table of session rows
- Server returns new token to client either via JSON body, as a
Bearer
token, or via cookie
Cookie
If using a cookie for authentication, it is best to set an HttpOnly
flag to avoid being accessed via remote or malicious Javascript.
Cookies have a particular format for their expires
field, using the HTTP-date
or RFC5322[10]. This can also be accessed on a Javascript Date
object via toUTCString()
. The date is formatted as follows:
foo=bar; expires=Sun, 06 Nov 1994 08:49:37 GMT; HttpOnly
JWT[4]
Another option is something like JWT, but note that this does have its issues[6]. This method stores three elements within it:
- Header - Identifies which algorithm is used to generate the signature
- Payload - Contains data to be used for verification (e.g. username)
- Signature - Securely validates the token
This token takes a JSON structure and puts it into a string via base64 encoding. So now instead of having to send the username and password and check with the database every single request, we can verify that the token is accurate and contains the necessary information for the user to act (e.g. their username). The steps that occur now are a little different:
- User sends credentials (e.g. username and password) to server
- Server verifies these credentials are accurate by checking their data store, usually via a test of the password's hash in their database
- If accurate, server creates a JWT that proves this user is who they say they are
- Server returns the JWT to the client
For all further interactions:
- User sends JWT
- Server verifies JWT is valid
- Server performs requested action
This is much simpler and less costly, as we only talk to the database one time. We don't need the password for every interaction since we verified it once that it is accurate and created a token that proves that on the server side.
Cross Site Request Forgery (CSRF)
In a CSRF attack, the attacker's goal is to cause an innocent victim to unknowingly submit a maliciously crafted web request to a website that the victim has privileged access to.[7]
This is done by utilizing a user's credentials or authorization tokens for site X while user is browsing site Y. Since the user has credentials for site X, site Y could have a form that will delete all privileged data that the user has access to, and if the user submits the form, then the request will be perceived as legitimate by the server, because the credentials are valid.
Some ways to avoid a CSRF attacki[8]:
- Use only JSON APIs (There is no way for a simple
<form>
to send JSON, so by accepting only JSON, you eliminate the possibility of the above form) - Disable CORS If you're going to allow CORS, only allow it on OPTIONS, HEAD, GET as they are not supposed to have side-effects. This doesn't apply to simple online forms, as they use no Javascript, however.)
- Check the referrer header (You could always block requests whose referrer headers are not from your site. This really isn't worth the trouble)
- GET should not have side effects (GET requests should never do anything but retrieve data)
- Avoid using POST (Because
<form>
s can only GET and POST, by using other methods like PUT, PATCH, and DELETE, an attacker has fewer methods to attack your site) - Don't use method override! (Many applications use method-override to use PUT, PATCH, and DELETE requests over a regular form. This, however, converts requests that were previously invulnerable vulnerable!)
- Don't support old browsers (Old browsers do not support CORS or security policies. By disabling support for older browsers [which more technologically-illiterate people use, who are more (easily) attacked], you minimize CSRF attack vectors)
- CSRF Tokens
CSRF Tokens[8]
How do CSRF tokens work?
- Server sends the client a token.
- Client submits a form with the token.
- The server rejects the request if the token is invalid.
An attacker would have to somehow get the CSRF token from your site, and they would have to use JavaScript to do so. Thus, if your site does not support CORS, then there's no way for the attacker to get the CSRF token, eliminating the threat.
Make sure CSRF tokens can not be accessed with AJAX! Don't create a /csrf route just to grab a token, and especially don't support CORS on that route!
The token just needs to be "unguessable", making it difficult for an attacker to successfully guess within a couple of tries. It does not have to be cryptographically secure. An attack is one or two clicks by an unbeknownst user, not a brute force attack by a server.
References
- https://kevin.burke.dev/kevin/things-to-use-instead-of-jwt/
- https://frontegg.com/blog/token-based-authentication
- https://auth0.com/blog/hashing-passwords-one-way-road-to-security/
- https://jwt.io/
- https://en.wikipedia.org/wiki/JSON_Web_Token
- https://redis.com/blog/json-web-tokens-jwt-are-dangerous-for-user-sessions/
- https://en.wikipedia.org/wiki/Cross-site_request_forgery
- https://github.com/pillarjs/understanding-csrf
- https://github.com/pillarjs/csrf
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString
Last modified: 202401040446