Security and Authentication
Examples of encryption, hashing, salting, cookies, sessions, 0Auth, and more.
Secrets Needed in Code
A lot of times you will need access to secrets in your code. These could be things like your encryption key, and API keys.
If someone gets access to your keys it is very bad news.
Good news is, there are ways to protect them.
Environment Variables
Environment variables allow you to keep track of secrets. You should never upload these to github.
You put them in a file .env
. You can access them in your code using dotenv.
import dotenv from "dotenv";
// gets the .env data for use with process.env.
dotenv.config();
// now it is used like this. The name of environment varialbe in .env is "ENC_KEY" in this case
const secret = process.env.ENC_KEY;
.gitignore
You can use the .gitignore
file to keep various files or folders from being uploaded to github. Always set this up! An example file is below.
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Security Levels
- Username and Password Only
- Encryption
- Hashing (with md5)
- Hashing and Salting (with bcrypt)
- Cookies and Sessions
- OAuth Authentication
1. Username and Password Only
This is the most basic - simply storing username and password in plaintext in a database.
If the database is hacked then all user and password combinations are visible - bad!
2. Encryption
We can encrypt the password field as the next level of security. The password is obviously the most important thing to encrypt.
Downside of this is it doesn't protect against someone with admin access to the website from finding out user passwords, because they can get the encryption key.
With MongoDB and Mongoose, we can use a package mongoose-encryption.
import encrypt from "mongoose-encryption";
// schema
const userSchema = new mongoose.Schema({
email: {
type: String,
required: [true, "ERROR: You need a username."],
},
password: {
type: String,
required: [true, "ERROR: You need a password."],
},
});
// setup database encryption
const secret = process.env.ENC_KEY;
userSchema.plugin(encrypt, { secret: secret, encryptedFields: ["password"] });
// model: mongoose will auto make it plural "users"
const User = mongoose.model("User", userSchema);
3. Hashing with md5
Passwords can still be hacked form level 2. So what about another method. Here, we don't store the password or encrypted password on DB, but instead store a hash off the password.
When a user inputs a password, you hash it, and compare that hash to the hash in the DB. If they match then the password is correct.
We can use package md5 for this.
import md5 from "md5";
// this runs the md5 hash function on password. This value is what you should store in the DB
md5(password);
4. Hashing and Salting with bcrypt
bcrypt is a slower hashing algorithm than md5 (much slower) so it's more secure. This is because it would take an attacker much longer to brute force the passwords.
Salting is appending a random string to the end of the password, before hashing. This makes it so that even if two users have the same password, the hash will be different.
Notes:
- You store the salt in the DB
- You can increase security by increasing the number of salt rounds
- Each increase in salt rounds increases the time it takes to has by 2
import bcrypt from "bcrypt";
const saltRounds = 10;
bcrypt.hash(myPlaintextPassword, saltRounds, function (err, hash) {
// Store hash in your password DB.
});
// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash, function (err, result) {
// result == true
});
5. Cookies and Sessions
For this you can use passport, passport-local, express-session, and with MongoDB / mongoose use passport-local-mongoose.
For an usage example, see Secrets
on branch passport-cookies-sessions
here. This includes authentication, registration, logon, and logoff.
further documentation on passport.js usage details here and here.
6. OAuth Authentication
OAuth is short for "Open Authentication" and allows us to add the capability for users to log on with trusted platforms, such as Google, Twitter, Facebook, etc. Users are redirected to login with the service in question, and an Auth Code is sent back to our website (like a one time access). We can also get an Access Token which is valid for a lot longer than an Auth Code (like a year pass).
The Access Token is what we can use to access data from people on that application (to which they have granted us access).
Passport requires a findorcreate
function to work with your DB. With MongoDB and Mongoose this is a useful package mongoose-findorcreate.
6a. Google OAuth 2.0 Authentication
For this, use Passport strategy passport-google-oauth20.
6b. Facebook OAuth 2.0 Authentication
For this, use Passport strategy passport-facebook.
Authentication Example Project
This is an example project I made using salting, hashing, and OAuth. You can test out the registration and login, although the 0Auth is limited to test accounts.
Code can be found here.
Below is what the database entries look like for the app:
Other Authentication Resources
Node.js passport login system tutorial (36 min) link.
Local authentication using passport in Node.js (docs) link.
User authentication in web apps with passport, node, express (6 hours) link.
User authentication with passport and express 4 (docs) link.
Permissions / access control in web apps (docs) link.