Authentication Options - Realtime Thought Process

Somehow I find myself in the world of authentication again! I thought I’d struggled through it enough in the past that surely I’d be able to add auth to a new app I’m working on without much trouble. WRONG. 😂

I’m making a private website that needs to be password protected. It used to be a Wordpress site, and apparently this is a little more straightforward on apache servers with an .htaccess file? (That might be wrong, but it’s what I understand now, and a rabbit hold I decided not to go down!) But this new site is on a Node server so I would need to find another way to implement auth.

How to approach it this time?

Just copy what I did before—this was my first instinct. I’ve been through auth on a number of projects and got it to work several ways, so this should work, right? Well…

Passport could work with the passport-local strategy. But must I associate a username with the password (yes)? And so I really need to create a new database User model to store it (yes)? And wait, now I need a session store (not technically but it’s best practice and we can’t avoid those!)? And OMG look at the Passport documentation, it explains nothing. Even the helpful articles are tomes! 😩

Still I tried this anyway, and found that the best documented way to implement passport-local seems to be combining a MongoDB data store and passport-local-mongoose. Well my app already has a Postgres data store. So then I look at previous projects where I implemented passport-local-sequelize, check the docs, and see that it hasn’t been kept up to date with the latest releases of sequelize.

And isn’t all of this overkill anyway? I think so, so I look for a simpler approach!

I could go the roll your own route, and looked at a few ways to do this. This article and repo used a similar method to what I used in the value app where you store a hashed password and send cookies to the client. I also looked at a new-to-me package express-basic-auth which seemed to do the trick with the benefit of being bare-bones, only it’s not extendable to authenticating the user on specific routes, which for me is the whole purpose of this…another dead end.

Honestly going through all of these options was a lot! So then I thought I could go back to the old faithful Auth0…that is, until I re-read some of my old posts detailing all of the nitpicky issues I had on previous projects. Knowing I will deploy this app on Heroku (and we know how that turned out), I decided it wasn’t worth the trouble or cost again.

JWT Try Out

All of the above led me to try a new method I’ve not used before: JSON Web Tokens! This article and repo gave a relatively simple walk-through of how to implement JWTs using the jsonwebtoken package to encrypt the tokens, and argon2 to hash the passwords.

I adapted the code from this walk-through successfully and was able to register a user and give them a JWT. That user was also able to log in with a valid JWT.

Then I got to the part of protecting routes…roadblock. This method will work for the API where I can attach headers to each request, but to protect routes on a normal GET or POST app-level route, I’d still need to implement some kind of session store. There were also some gotchas around invalidating a token, for example if you want the user to be able to log out (as opposed to just letting their token expire).

Lesson learned!

Back to the Tried & True Method

I’m tired of working on this and want to actually work on the app now. I can see why people default to a tool like Auth0 or Okta!

So I decided to use a separate database for the user & session store following this step-by-step which uses Passport, MongoDB and passport-local-mongoose, and it worked great in the end.

The only issue was getting Express to serve the file directory of the Vue app’s static files…

Update 6 Weeks Later!

About a week after finishing this I got an email from Heroku saying that their mLab add-on was being discontinued 😵 In order to keep using MongoDB & passport-local-mongoose I would need to migrate that database to a new MongoDB Atlas cluster (and make it first!). This is definitely overkill for a database with 1 document in 1 collection, so I decided to figure out how to make Passport work with Postgres after all.

In the end I went ahead with passport-local-sequelize even though it’s not using an up-to-date version of Sequelize. It works, and that’s good enough for now.

There was one difference for registering users which I’ll save for future reference (I run this once from app.js and then delete the code, so it won’t be maintained in version control):

db.User.register('username', 'password', function (err, usr) { if (err) console.log(err); if (usr) console.log(usr); });

The passport-local-sequelize package has no documentation for the register() method but these are the parameters it expects.

Serving Protected Static Files

I could serve the login page (and Express EJS template) and the Vue app by setting up two static file locations:

// Serve client static files
app.set('view engine', 'ejs');
app.use(express.static(__dirname + '/public'));
app.use(express.static(require('path').join(__dirname, './views/client/dist')));

The problem was that the Vue app would render whether or not the user was logged in, even if the client/dist/index.html file was only served from a protected route.

I learned how to layer in the authentication by stacking my loggedIn middleware instead:

app.use('/home', isLoggedIn());
app.use('/home', express.static(require('path').join(__dirname, './views/client/dist')));

Same exact line of code, but called this way it only rendered the client/dist/index.html file, but ignores all of the other static files. The JavaScript file of a Vue app is kind of important 😑

The solution was to update the root path of the Vue app—with this setup, it was looking for JavaScript like this:

localhost:5000/js/bundlepack.js

…when it’s actually now located here:

localhost:5000/home/js/bundlepack.js

So adding some Vue config to the client’s package.json file did the trick:

...
"vue": {
"publicPath": "/home/"
},
...

Note: depending on how your Vue app was originally set up, you might need to add this setting to the vue.config.js file instead. See docs for all settings.

And that’s it!

I now have auth running successfully, and the static pages can only be accessed if the user is logged in. 🎉🎉🎉