Session management still occurs via cookies, but do not disclose secrets or authorisation in a cookie because the security of WebStorage is best practice.
I won't bore you with sections that cover "what are Cookies" or "security of cookies", OWASP covers these very well here.
Secure Session Management
Before we tackle session management
What is a secure session?
A secure session has 1 simple characteristic;
Knowing the session value provides no information; it is not disclosing an identity, it's validity, it has no metadata, user data, secrets, or usable values of any type if read by anything other than the server that issued the value
A session does not represent identity, to have Identity you require assurance of Authentication because the challenge itself is the security characteristic of Authentication assurance.
The session is not Authentication, to have Authentication you require a challenge.
Knowing a session does not guarantee Authorisation, to have Authorisation you require assurance of Identity.
And so the circle is realised.
The question is; do you a) complete the circle and require a challenge. or; b) Challenge once, set a cookie, and hope everything works out. or; c) understand, plan, implement, and verify a secure session management solution?
Of course you chose option "c" so let's talk about when it is best-practice to use both cookies and WebStorage.
A cookie should only be set after the successful server-side Authentication challenge.
Never set a value in a cookie, the browser should never access the cookie. period; this is what WebStorage is for.
The only value a cookie should hold, is the server-side session management unique identifier. All other session management values (identity, expiry, authorisation, metadata, etc.) should never be included.
When to use WebStorage
There are 2 types;
Data stored in Local Storage is specific to the protocol of the page so that data stored by a script on a site accessed with HTTP is put in a different WebStorage object from the same site accessed with HTTPS. This applies to different origins too, all WebStorage is strictly same-origin. Basically an origin is the combination of domain and protocol, we covered protocol already and the domain is easy; example.com is a domain and www.example.com is a different origin, it is a subdomain of example.com and is it's own origin.
Once set, data in Local Storage doesn't expire. Local Storage is never disclosed over HTTP, it is never accessible remotely or shared between devices or users on the same machine. All access to data in Local Storage occurs on the device from scripts which were loaded in the context of it's own origin.
Use the Local Storage object to persist a value used for session management that lasts beyond browser restarts, machine restarts, and even the existence of a cookie.
This follows the same rules as Local Storage except one. Unlike Local Storage which doesn't expire, data in Session Storage is cleared when the page session ends.
A Session Storage object is entirely distinct from a Session based on a cookie. You can have many Session Storage objects with unique values and while they are independent Session Storage objects, they may be confused as one 'Session' with inconsistent user values based on a cookie.
A page session is a particular tab, not an entire browser. If a tab closes the session is purged. If 2 tabs are opened in the same browser (which shares a cookie, and session) there can be unique values in Session Storage per tab for the same 'user session' defined by and stored in a cookie.
Do not Session Storage for purposes related to a server-side user session.
The word 'Session' in Session Storage is not a traditional session. I have not encountered any use cases related to user session management.
Use the Session Storage for tab specific things, like tab naming, temporary styling, non-persistent form values (drafts), or when you require inter-communication between tabs but keep tabs distinct somehow, which can be useful for some types of games and chats.
Secure Cookies are signed
Using signatures, ergo signed cookie, adds a characteristic Cookies alone never provided; Integrity. But wait, cookies introduced a 'Secure' flag to prevent cookie sniffing (when a cookie is disclosed to a middle actor, traditionally (wo)man-in-the-middle MitM).
The 'Secure' attribute simply tells browsers to never disclose the cookie over non-HTTPS requests and the server should (usually never) ignore the cookie if it is sent over non-HTTPS. The 'Secure; flag can never assure a cookie was never tampered, it only sets conditions for it being sent and reading teh cookie, tampering when sent, or inclusion of a cookie when it was never sent are all still possible. Signing is cryptographic assurance of integrity, and signatures require a server-side secret to verify the integrity preventing tampering entirely while the secret remains protected.
A secure cookie also sets the HttpOnly attribute, naturally.
So if you use the domain cookie attribute, immediately remove domain attribute to limit cookie to origin host only - which is very likely your original intention.
Use the domain cookie attribute in the very rare cases it is needed, and only if you fully understand why you are using this and not the alternative secure cookie method for each origin.
Signatures must be generated one-way, and reproducible using a server-side secret combined with user attributes that may be used to validate or invalidate the user session.
For example; combining the user's source IP address allows you to invalidate the session if the cookie is stolen and used by another IP address. This may not be ideal on IPv4 because most users are assigned dynamic IP addresses. This doesn't need to destroy a session, it may simply indicate that the session validation would require the successful completion of a challenge. If the user attribute signed is appropriate, this is a powerful and secure solution.
Challenges are not always user actions
We've discussed challenges several times, but do they always require a human challenge? I like to argue that this is not necessary in every circumstance that requires a challenge given that at least 1 challenge was performed by a human and all subsequent challenges are performed by the machine strictly based on the result of the human challenge and with assurance the human identity has not changed.
Okay, let's unpack that one with some images
Essentially all public pages are returned and protected pages are forbidden.
Upon the successful challenge is verified by the server, protected pages are returned.
But the session is not secure, a bad actor may steal and replay the session or tamper with the session. This also allows bad actor to interact directly with a server freely.
Using a signed cookie where the server has validated a user unique attribute effectively prevents bad actors from leveraging a user session for malicious purposes, but all challenges are human actions and may be inappropriate with an some user attributes such as an IP Address;
Which requires a user to have the challenge fulfilled whenever the IP address changes. This is similar to using the User-Agent as an attribute because it has been known to change when browser updates are applied. There are many options for user attributes to sign and I have not yet come across a sufficiently secure, reliable, and constant attribute - but this is not an issue if the challenge can be fulfilled by the machine directly when required by the server instead of interrupting the user;
In the same way the bad actor was forbidden due to them not knowing the unique user attribute signed in the cookie when a machine challenge is used (such as HMAC) in combination with the signed cookie, the bad actor remains forbidden because the signed cookie is still in place.
When the user attribute changes or the bad actor makes an attempt, only the user continues to hold it's own unusable attribute value, the original challenge used to obtain the secret used for HMAC signing. The bad actor never fulfilled the original challenge so never obtain the secret used for HMAC signing and is unable to successfully perform the HMAC signing to make requests.
The user leverages the HMAC HTTP Signing on each request that fails cookie validation to maintain a valid session. A HMAC signed HTTP request is not capable of requesting and rendering HTML therefore the signed cookie still holds value in this context and cannot be abandoned upon establishing the HMAC. These solve separate problems and work together for best-practice secure session management.
Okay, I know I promised not to cover "what are Cookies" or "security of cookies", so I'll simply highlight the grey areas that tend to confuse and missed by most people.
The cookie will disclose itself on every request the browser sends out, for example a website hosting a image, a 3rd part style or script include, or a interactive API. Some cookies can be restricted to only be sent over page loads and interactive API requests (referred to as HTTPOnly) so that servers hosting images cannot obtain user cookies. The fact is even though the request is encrypted using TLS the cookie cannot be secret or hidden because they are freely shared over HTTP and even images which are proven trivial to remotely obtain.
Cookies are not encrypted using TLS when the requester is the attacker that makes the TLS request, read about how TLS was only ever intended to encrypt the transport, not protect the data from intended or unintended requesters.
WebStorage suffers few known issues, they can be summarised into the following;
- Inappropriate access by 3rd party code: scripts included directly on the page from the same domain of the page. Same issue as cookies
- CNAME collusion: Where analytics.mydomain.com has a CNAME record to tracker.company.com meaning tracker can read WebStorage as though it was analytics.mydomain.com. Same issue as cookies
- Insecure Implementations: where values are set or read in business logic flow that introduces security issues unrelated to the WebStorage technology itself. Same issue as cookies