A very simple example project to handle storing sessions in a redis store.
Generates a token, that can be sent to the user.
The token has 2 parts, structured like:
<tokenId>.<secret>
Rigt now, this version does not store "old" tokens, just deletes them on rotation. The reason being, is that if a user or attacker provides an expired token, or an invalid one, it should not matter, we should log them out anyways.
Currently this POC does not have a real logout funcitonallity, as that is out of the scope of this POC. Just delete the refresh_token cookie, if you want to be "logged out"
For now the session type is defined in /src/types/session.types.ts. This is just a placeholder for a session, not set in stone at all.
POST - /issue-refresh
payload:
{
userId: "123",
emailAddress: "demo@demo.com"
}This should issue a token, and store some metadata in the redis store. Also sends a refresh_token cookie to the client
GET - /refresh
Validates the value of the refresh-token cookie. If all is valid, issues a new token and rotates the stored valies in the redis store.
POST - /revoke/:tokenId
"Invalidate" a refresh token. Requires a tokenId as a URL param. Take the tokenId part from the cookie, and it should revoke it.
After this "/refresh" is going to return a 401.
Some problems I had with Redis and NestJS framework, that are solvable, just required some extra boilerplate
- In order to access the original Request and Response objects, we need to use the @Req and @Res decorators.
- Very important to import the correct types for the Request and Reponse objects, beacause, its valid syntax to just type it as
res: Responseand not import anything. These should be coming from express, so:import { Response, Request } from 'express'; - In order to keep Nest's response mechanics, you have to pass the
{ passthrough: true }flag to the @Res() decorator, so:@Res({ passthrough: true }) res: Response, - Not really sure about the cookie flags that are being set, this is just for testing with
cURL/Postmannot really meant to be used from a browser.
- I used
bcrypt, but also make sure@types/bcryptis also installed as a dev dep. cookie-parseris required to parse the cookies, also@types/cookie-parseris installed.- I used
uuidas a sessionId, andcuid2for the tokenIds. (Libs for generating this are installed)
- Since we are storing JSON-s in redis, we need to have some type safety about them. By default I just did
JSON.stringify()andJSON.parse()on-demand, but it wasn't safe at all. So I utilized a wrapper for redis, and exposed jsonGet and jsonSet methods. These have explicit return types, to let typescript know what we are setting, and what we are getting. - This avoids having to
JSON.parse()andJSON.stringify()on the fly. - I kept the default behavior, and return
undefinedif the key is not found in the store.