Ever since Redwood 0.1 launched in March of 2020, I’ve been on a mission. That mission was to be able to log in to my app without relying on a third-party service to host and manage user details (and bill my credit card for the privilege). I’ve got a database already—why can’t I use it to log in and sign up users, the way it’s been done for thousands of years? A couple of months ago I decided to do something about it.
With the release of Redwood 0.35, I can finally declare MISSION ACCOMPLISHED! Redwood now provides a local authentication option using your own database! We’ve got in-depth documentation available, but here are the basics:
Setup
There’s a new command that installs the api function required for dbAuth, as well as adding an environment variable for managing an encrypted cookie, and includes instructions for adding required fields to your local user database table:
yarn rw setup auth dbAuth
Be sure to read those post-install instructions as they contain all the steps you need to complete before auth will actually start working.
Technically you’re now able to authenticate with dbAuth! However, you still need a login and/or signup page. You didn’t think we’d leave you to have to write those by yourself, did you?
Scaffolding Login/Signup Pages
A new generator enters the scene and creates a simple login and signup page, copying the styling from the scaffold generator. You’re free to re-style as you see fit, or just use the logic in the pages to hand craft your own pages from scratch:
yarn rw g scaffold dbAuth
Again, read through the post-install instructions for a couple steps to take to customize these pages for your app.
How it Works
For those interested in the nitty gritty details, read on. Redwood’s dbAuth is modeled on the simple authentication systems recommended by, big surprise, Rails development before there were lots of packages (Ruby calls them “gems”) that would do it for you:
- Store a hashed password and salt along with the user record in the database.
- When a user tries to log in, grab them by the username they entered (probably an email address) and then salt and hash their submitted password. If it matches the hashed password in the database, then we trust that they are them.
- Create an encrypted cookie containing the user’s id (the cookie is also marked as HttpOnly, Secure and SameSite). The encryption key is an environment variable called
SESSION_SECRET
that’s created when thesetup
command is run. - On each and every request to the api side, make sure the cookie can be decrypted and double check that the user still exists in the database.
- On logout, remove the cookie by setting the expiration date to the epoch (Jan 1, 1970).
The cookie cannot be accessed via Javascript on the client side (HttpOnly) or via cross-site requests (SameSite). By default the cookie is only available on the same domain that set it, but if you need to access it from a subdomain, there’s a config option for that.
If we detect any shenanigans, like the cookie can no longer be decrypted properly, then we assume it was tampered with and we immediately log the user out.
The Nuclear Option
Due to the fact that the session cookie is encrypted and decrypted by a secret that lives in your environment variables, what happens if you change that secret between deploys? Every user is logged out of your app on their request.
While this may seem like an extreme measure, if you ever find yourself in this situation you’ll be glad you have the option!
The SESSION_SECRET
is stored in an .env
file which, by default, is added to .gitignore
so that it will NOT be committed to your repository. This is an absolute requirement, as anyone with access to that secret could decrypt the cookie. You should set a different secret for every developer on the team, and a separate one for each environment (qa, staging, production…).
Coming Soon
This implementation also takes the first steps towards CSRF protection—we’re setting a CSRF token in the cookie, as well as setting it in a header back to the client. We’re not sending the token back up the api-side on each request and verifying it, but we hope to add that functionality soon.
Comments?
Have you been looking forward to database-backed auth? Is there no way in hell you’d ever use this? Let us know!