User Authentication

Overview

  • Authentication vs authorization.

  • Password storage.

  • Demo following technologies:

    • JSON

    • Asynchronous programming using async and await.

    • Mongo db

  • Testing.

Authentication vs Authorization

  • Authentication: Who we are. Example: user-id and password.

  • Authorization: What we can do. Example: Unix file system permissions.

  • 2-Factor Authentication: Authentication based on 2 independent factors: for example, what we know and what we have. Examples: user-id password and fingerprint; user-id password and cellphone; user-id password and iris scan.

  • Authentication and authorization are orthogonal concepts, but authorization usually requires some kind of earlier authentication.

  • Access control: control access to resources based on authentication and authorization.

Password Storage

  • A responsible programmer should never store a plaintext password.

  • Typically, passwords are hashed (not encrypted) using a one-way hashing algorithm like SHA256 or bcrypt. Only hash is stored. When user logs in, entered password is hashed and compared with stored hash; if they match, then login is allowed, else denied.

  • Simple hash is amenable to a dictionary attack where hashes are precomputed for many common passwords; if precomputed hash matches stored hash (got by accessing the password file), then password is dictionary word.

More on Hashing

  • Map variable length content to fixed-length hash string.

  • Good hashing algorithms ensure that changing the content slightly (even by a single bit) will result in a different hash.

  • The uses of hashing include hash tables, summarizing content, cryptographic signatures.

  • Cryptographically secure hashing algorithms SHA-256, and bcrypt. The latter is purposely slow. Note that MD5 is no longer regarded as cryptographically secure.

$ echo hello | sha256sum 
5891b5b522d5df086d0ff... (64 hexets)
$ echo hellp | sha256sum 
bf8c83416f31143ee2fa5... (64 hexets)
$

Password Storage Continued

  • One way of cracking passwords is to have pre-built maps mapping common "dictionary words" to their hash values and simply find a match in the stored hashes for the hash of the password being cracked.

  • Dictionary attacks can be made harder by adding a random salt to each password before hashing. The salt is stored along with the hash to allow matching an entered password. So a 2 character salt with 64 possibilities for each salt character would increase the number of combinations for a dictionary attack by 4096.

  • In 2000's, brute-forcing passwords becoming possible because of extremely fast hardware (sometimes using GPUs).

  • Normal hashing algorithms like SHA-256 were designed to be fast.

  • Current best practice for password hashing is to use purposely slowed up hash algorithms like bcrypt to make it harder for crackers.

Using bcrypt

In bcrypt-hash.mjs:

import bcrypt from 'bcrypt';

async function go() {
  const PW = 'asdf';
  const h1 = await bcrypt.hash(PW, 10);
  const h2 = await bcrypt.hash(PW, 10);
  console.log(`hash1: ${h1}`);
  console.log(`hash2: ${h2}`);
  console.log('cmp1', await bcrypt.compare(PW, h1));
  console.log('cmp2', await bcrypt.compare(PW + 'a', h1));
}

go().catch(err => console.error(err));

Using bcrypt: Log

$ node code/auth/bin/bcrypt-hash.mjs
hash1: $2b$10$wXRP2XE8q3czM8mHe9Fi2.h... (truncated)
hash2: $2b$10$0zqbCRkJomsoXaL3oSo4De2... (truncated)
cmp1 true
cmp2 false

MongoDb

  • One of many nosql databases. No rigid relations need to be predefined.

  • Allows storing and JS data serialized in "documents" having _id as primary key (when storing a document without an _id property, it is created automatically with a suitable ID value).

  • Allows indexing.

  • Provides basic Create-Read-Update-Delete (CRUD) repertoire.

Mongo CRUD

All operations asynchronous. Set up to return a Promise when called without a handler.

Create

insertOne() and insertMany().

Read

find() returns a Cursor. Can grab all using toArray().

Update

updateOne() and updateMany(). Also, findOneAndUpdate() and findOneAndReplace(). Can combine insert and update functionality using upsert option.

Delete

deleteOne() and deleteMany().

Asynchronous DB Operations

  • As mentioned, most DB operations are asynchronous.

  • Usually a good idea to cache DB connection as opening a DB connection is an expensive operation.

  • Create a Data Access Object (DAO) which wraps a database. Cache database connection within object when object is first created.

  • DAO should provide dumb persistence operations and not contain domain logic.

  • Getting a connection to a DB is an asynchronous operation.

  • Can we build object using an async constructor??

  • Use a static factory method instead.

Setting up a Mongo Project

$ npm init -y #creates package.json
...
$ npm install mongodb    #old versions required --save
npm notice created a lockfile as package-lock.json...
...
$ ls -a
.gitignore node_modules package.json package-lock.json ...
$

Mongo Shell Log

Allows interacting with mongo db. Following log assumes that collection userInfos in db users is loaded with simpsons data.

$ mongosh
Current Mongosh Log ID: ...
...
...
test> help
...
> use users   //assume populated with Simpsons
switched to db users
users> db.userInfos.find({})
{ "_id" : "bart", "id" : "bart", ... }
{ "_id" : "marge", "id" : "marge", ... }
{ "_id" : "lisa", "id" : "lisa", ... }
{ "_id" : "homer", "id" : "homer", ... }
users> db.userInfos.find({"firstName": "Bart"})
{ "_id" : "bart", "id" : "bart", ... }
users> db.userInfos.find({}).length()
4

Mongo Shell Log Continued

users> db.userInfos.deleteOne({"firstName": "Bart"})
{ "acknowledged" : true, "deletedCount" : 1 }
> db.userInfos.find({}).length()
3
users> db.userInfos.deleteMany({})
{ "acknowledged" : true, "deletedCount" : 3 }
users> db.userInfos.find({}).length()
0

User Auth Features

  • Store user-info objects.

  • User info objects can contain any fields.

  • Demonstrate use of insert*(), find*(), delete*().

  • Sample log.

TS Implementation

auth-dao.ts

Data Access Object which implements db operations.

user.ts

User interface.

auth-services.js

Authentication services: shows validation using validator module.

auth-mem-dao.js

Memory based mongo double. Instantiate actual user DAO, but using an in-memory mongo db instance. Advantages include speed and that clearing of in-memory db before each test.

test/auth-dao.js

Auth dao tests.

test/auth-services.js

Auth service tests.