Authentication and Authorization using Postgres and Node.js
what we're building
we're going to build a server that lets users make "posts", we'll add peristence, authentication, and authorization to it as we go
Setting Up the Express Server
first we need to set up our package.json
and install some dependencies
first import and initialize express
add our logging middleware first
in order to handle parsing the body of an http request we need to add some middleware
and also configure the express-session
middleware
we can add some routes that update an in-memory list of "posts"
this usually isn't a great idea but we'll fix it later
and finally, store the user name in a session
first import and initialize express
add our logging middleware first
in order to handle parsing the body of an http request we need to add some middleware
and also configure the express-session
middleware
we can add some routes that update an in-memory list of "posts"
this usually isn't a great idea but we'll fix it later
and finally, store the user name in a session
we can test our server by running npm run dev:server
and sending some requests to it, here for example using curl
:
# send a post with a name and store the cookiecurl -v -c sess.cookie localhost:$PORT/posts -d 'name=you' -d 'body=hello world!'# use the cookie to retrieve the namecurl -v -b sess.cookie localhost:$PORT/currentUser
how do cookie sessions work?
- we put some randomly generated and very hard to guess gibberish (sometimes called an opaque token) in a [signed] cookie for a user
- you put that token, along with some data (eg their user id) into a table with an expiration
- on every request you look at the token in the cookie [and check it's signature], see if it exists in the db and isnt expired, and return the data (eg the user id)
- passing around that random, hard-to-guess token makes it very hard to try to impersonate another user, and storing the data (user_id) and expiration in the db means you can revoke the session whenever you want since you own the db
the code above uses in-memory data structures to store posts and sessions, which may work fine during development, but isn't suitable for a production deployment since it has no persistence or cache eviction strategy and so memory usage will scale linearly with new sessions and posts and eventually cause the process to use up all available memory and crash with no way to recover the data afterwards
Persisting Data with Postgres
To persist our data we'll need a database, and what better choice is there than Postgres, the world's most advanced open source database?
There are many ways to run Postgres, one of the easiest ways to manage Postgres for development and production is to use Docker. I've provided a docker-compose.yml
file that will run a Postgres 16 server and expose it on port 5432, and a script that will create a database and user for our application.
now that we have a running postgres cluster first we need to create some tables
now that we have a posts
table we can update our application code and store our posts in it
the Postgres adapter we're using provides a tagged template to run our SQL queries. it's important to note the syntax for calling tagged template functions doesn't use parentheses, which allows the tagged function to send the query separately from the user input and avoid SQL injection.
persist our sessions in postgres
our store needs a method to set
sessions
and a method to retrieve sessions
a method to update the session expiration
and a method to delete sessions
persist our sessions in postgres
our store needs a method to set
sessions
and a method to retrieve sessions
a method to update the session expiration
and a method to delete sessions
typically most apps would use an existing library like connect-pg-simple, but as of writing one did not exist that uses the
postgres
adapter we are using, and I also wanted to explicitly show what astore
does under the hood
this is suddenly a lot of different code in one file, so let's break it up into some smaller files
actually adding the auth routes
create router and add some simple validations
add the /register
route
add some basic field validations
some error handling on conflicting usernames
now we can start on the login route
and again with some basic validations
plz use zod
the last piece is the logout route
don't forget to add the new auth router to the express instance
create router and add some simple validations
add the /register
route
add some basic field validations
some error handling on conflicting usernames
now we can start on the login route
and again with some basic validations
plz use zod
the last piece is the logout route
don't forget to add the new auth router to the express instance
authorization
so far a user can register, login, logout, and make post, but there are no restrictions on a user's name, so anyone can post as any user.
in order to leverage Postgres Row-Level-Security we need a visitor
role with limited permissions, and an authenticator
role that we can log-in to
the rest of this article is still missing explanations and context, but the code is complete and should work as expected
check back later for more details!
PostGraphile is a huge inspiration for this post and some of the ideas and code are directly lifted from and the graphile-starter. Big thanks to Benjie for the incredible work he does on the Graphile suite!