Most websites need a way to verify who is making requests
Generally this involves providing some secret only the user could know: often called a password
This is fine as a one-off, but websites need to keep users logged in
You could ask the user to provide their password on every request.
This would be a terrible user experience though.
HTTP is a “stateless” protocol.
This means each request has no knowledge of any others
So by itself HTTP has no real way to store information (or “state”).
“Cookies” were invented in 1994 to maintain state between requests.
E.g. for keeping track of what users add to their shopping carts.
Cookies are just HTTP headers.
If a response contains the set-cookie
header the browser stores it.
The browser will send this info on all future requests to that domain.
E.g. this response sets a cookie containing test=123
HTTP/1.1 200 Ok
content-type: text/html
set-cookie: test=123
<h1>Hello</h1>
If the user visits another page (sending a request), the browser automatically adds a cookie
header.
GET /other-page HTTP/1.1
accept: text/html
cookie: test=123
This allows the server to save info from one request and retrieve it on later requests from the same browser.
Cookies have attributes to control how they behave
By default cookies last until the user closes the page. This is known as a “session” cookie.
The server can make a cookie last longer by setting the Max-Age
attribute.
set-cookie: test=123; Max-Age=60
This cookie will last for 60 seconds.
It’s important to protect cookies, since they often contain authentication related information
The HttpOnly
attribute prevents JavaScript from accessing the value.
This is important to avoid malicious scripts stealing cookie data (XSS attacks).
set-cookie: test=123; Max-Age=60; HttpOnly
The Same-Site
option prevents cookies from being sent on requests from other domains.
This is important to avoid other sites impersonating your users (CSRF attacks).
set-cookie: test=123; Max-Age=60; HttpOnly; Same-Site=Lax
The Secure
option stops the cookie being set on un-encrypted connections.
This is important to stop hackers intercepting requests and stealing cookies (MITM attacks).
set-cookie: test=123; Max-Age=60; HttpOnly; Secure; Same-Site=Lax
So how do we actually track whether a user is logged in?
The simplest way is to put all the info you need into cookies.
set-cookie: user={"id":"1","name":"oliverjam","role":"admin"}
The server can read this JSON string and parse it into an object to find out info about which user is logged in.
This is called “stateless” as no state is stored on the server.
Assuming the server can trust the cookie it can trust this user previously logged in.
But can you trust cookies?
Cookies are just HTTP headers, so they’re easy to fake.
curl https://example.com -H 'cookie: user={"id":1,"name":"oliverjam","admin":true}'
You can even edit them using dev tools.
We can use cryptography to create a value that we can verify later.
Cryptography
The practice and study of techniques for secure communication
A “hash function” takes a value and a secret, and returns a “hash”.
hash("hello", "secret123");
// "iKqz7ejTrflNJquQ07r9SiCDBww7zOnAFO4EpEOEfAs="
Hashing is a one-way process. There’s no way to get the value back.
Hashing the same value with the same secret will always produce the same result.
hash("hello", "secret123");
// "iKqz7ejTrflNJquQ07r9SiCDBww7zOnAFO4EpEOEfAs="
The only way to recreate this value is to know the secret.
We can use this to “sign” our cookies.
set-cookie: hello.iKqz7ejTrflNJquQ07r9SiCDBww7zOnAFO4EpEOEfAs=
When the server receives the cookie it can re-hash the value (“hello”) using the secret only it knows.
If the hash matches the one in the cookie the server knows it hasn’t been tampered with.
You won’t have to implement this yourself.
It’s safer to rely on battle-tested libraries for security features.
Cookies have a 4kb size limit.
You’ll often want to store more user info than that.
The server cannot control who is logged in.
If a user presents a non-expired cookie they are authenticated.
This is the opposite of stateless auth.
Store a single “session ID” in the cookie.
Then use that ID to look up the user data on the server.
set-cookie: sid=abc123
The server reads the cookie, then uses it to look up the saved session data.
This would usually be stored in a database.
Since the session ID is basically a password it must be secure.
Session IDs should be long random strings.
This prevents people from guessing them.
They should also still be signed with a hash, so you can trust them.
It’s important to treat security very seriously.
The web has been plagued by serious breaches.
As web developers you have a responsibility to safeguard your users’ private data.
The average user re-uses the same password for most sites.
If you half-ass your site’s security and get hacked that has wider repercussions.
Don’t roll your own crypto
The golden rule of security
If in doubt, look it up.
Find out what is popular and recommended by experts
Building a good secure user-experience takes time and effort.
Here are some features we haven’t covered:
If you don’t have the time or ability to do it properly (e.g. you’re hired for a 2 week freelance project)
use a trusted 3rd party service.