Skip to content

ezkomoly/opague-token-poc

Repository files navigation

Opaque refresh token POC

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.

Operations

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.

Hoops to jump through

Some problems I had with Redis and NestJS framework, that are solvable, just required some extra boilerplate

Controllers

  • 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: Response and 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/Postman not really meant to be used from a browser.

Libraries

  • I used bcrypt, but also make sure @types/bcrypt is also installed as a dev dep.
  • cookie-parser is required to parse the cookies, also @types/cookie-parser is installed.
  • I used uuid as a sessionId, and cuid2 for the tokenIds. (Libs for generating this are installed)

Redis

  • Since we are storing JSON-s in redis, we need to have some type safety about them. By default I just did JSON.stringify() and JSON.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() and JSON.stringify() on the fly.
  • I kept the default behavior, and return undefined if the key is not found in the store.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published