Skip to content

Password and JSON Webtokens

Brian Bawuah edited this page Jun 23, 2020 · 16 revisions

Password hashing

Bij het registreren van gebruikers is het van belang dat eventuele opgevraagde wachtwoorden worden gehashed.

Waarom?

Stel dat op de een of andere manier ons database wordt gehacked.. De hacker heeft op dat moment de mogelijkheid om wachtwoorden te bekijken van gebruikers.

Onze dating is dan niet zo belangrijk, maar er zijn best veel mensen die op het internet dezelfde wachtwoorden gebruiken. Hackers zouden deze mensen harder kunnen raken door op platformen in te loggen.

Het is eigenlijk niet goed dat mensen dezelfde wachtwoorden gebruiken. Maar het is dat onze taak als developers om een manier te zoeken om die mensen te beschermen.

Wat is het verschil tussen Encryption en Hashing

Tijdens mijn onderzoek naar het hashen van wachtwoorden ben ik tegen gekomen wat het verschil is van Encryption en Hashing.

Encryption Hashing
✳️ Encryption is het altijd mogelijk om de original value terug te krijgen ✳️ Hashing algorithms zijn one-way algorithms en maken het niet mogelijk om original values terug te krijgen

Hoe werkt hashing

Allereerst heb ik een package geinstalleerd genaamd bcrypt. Dit is een library die helpt met het hashen van wachtwoorden. Het is ontworpen door Niels Provos en David Mazières.

De bcrypt function verwacht een promise .hash verwacht twee argumenten.. Het wachtwoord, het tweede argument geeft aan hoevaak het algoritme moet worden gerund. Ik heb voor 8 gekozen omdat het goed in balans is met security en snelheid 8 wordt ook aangeraden door de makers van het algoritme. Een te hoge waarde is zal veel tijd nemen om de algoritme uit te voeren. Een te lage waarde zal een zwak wachtwoord genereren.

Omdat hashing one-way is moeten we deze functie gebruiken om de ingevoerde wachtwoord te vergelijken Met wachtwoord in onze database

const bcrypt = require('bcrypt');

const myFunc = async () => {
  const password = 'pixbyIsLit';
  
  const hashedPassword = await bcrypt.hash(password, 8);

  console.log(password);
  console.log(hashedPassword);

  const isMatch = await bcrypt.compare(password, hashedPassword);

  console.log(isMatch); // Returns a boolean
};

myFunc();

Hetgeen wat ik meegeef aan de model. Dat is dus de user met de data van req.body zie server.js voor de kleine api route waar ik users aanmaak

user.isModified('password') Alleen wanneer het wachtwoord wordt gewijzigt willen we de hash functie toepassen. Dus niet elke keer als de user inlogt!

userSchema.pre('save', async function (next) {
  const user = this


  if (user.isModified('password')) {
    user.password = await bcrypt.hash(user.password, 8) // Zie playground brcypt.js
  }

  // Net als met middlewares in express, wordt next geroepen als
  // de middleware is afgerond en model in dit geval kan worden opgeslagen
  // Als next niet wordt geroepen, blijft de middleware hangen
  next()
})

In deze userSchema zoek ik dus in het database of het wachtwoord dat de gebruiker meegeeft, overeenkomt met het gehashte wachtwoord van de desbetreffende gebruiker.

userSchema.statics.findByCredentials = async (name, password) => {
  const user = await User.findOne({ name })

  if (!user) {
    throw new Error('This user does not exist..')
  }
  const isMatch = await bcrypt.compare(password, user.password)
  if (!isMatch) {
    throw new Error('Incorrect password..')
  }
  return user
}

JSON Webtokens

JWT gebruik ik om een authentication systeem te bouwen. JSON web tokens kunnen voor meerdere dingen worden gebruikt maar worden het meest gebruikt voor Authentication.

De package 'jsonwebtoken' wekrt als volgt.

Om een token te makent dient te functie .sign() te worden afgeroepen. De functie vraagt als eerste argument een unique identifier waarmee de gebruiker gevonden kan worden en als tweede argument een wachtwoord.

Met jwt.verify() kunnen we tokens verifieren. Deze functie vraagt de een jwt token en hetzelfde wachtwoord waarmee de tokens worden gesigned.

Als de functie is gelukt dan returned deze functie een gebruiker terug en zo niet dan krijg je een error in de console die aangeeft dat er een Invalid Signature is.

JSON webtoken

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiJicmlhbmJhd3VhaCIsImlhdCI6MTU5MDc3MDU5Mn0.4wLvmgPKrfX4AFr4B-WOzCKplW9Oo1eoEpIfE746mhA

const jwt = require('jsonwebtoken');

const myFunction = async () => {

  const token = jwt.sign({ _id: 'brianbawuah' } /* Unique identiefier! van object. (_id dus) */, 'projectTechIsLeuk', { expiresIn: '2 days' });
  console.log(token);

  const data = jwt.verify(token, 'projectTechIsLeuk');
  console.log(data);
};

myFunction();

JSON Webtokens bestaan uit drie delen.

  • Header
  • Payload
  • Signature

1ST PART

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. Base64 string bestaat uit meta data

2ND PART

eyJfaWQiOiJicmlhbmJhd3VhaCIsImlhdCI6MTU5MDc3MDU5Mn0. === {"_id":"brianbawuah","iat":1590770592}

De payload/ body van jsonwebtoken. Zoals te zien is dit de data

3RD PART

4wLvmgPKrfX4AFr4B-WOzCKplW9Oo1eoEpIfE746mhA

Dit is de signature waarmee de token mee geverifieerd kan worden.

"The point of jsonwebtokens is not to hide the data.." The whole point is om een token te creeeren dat we kunnen verifieren met onze jwt secret

Hier laat ik zien hoe we JWT gebruiken in onze applicatie. Dit is een userSchema die we gebruiken op het moment van registreren en inloggen. Met deze schema maken we dus een jwt token aan voor een user. We geven een unique identifier mee zoals de functie verwacht. In dit geval de _id van de gebruiker.

Daarna return ik met de concat functie een nieuwe array terug met de oude tokens en die nieuw gegenereerde token en die sla ik op in de user.tokens array.

//  Here I generate a new token for the user
userSchema.methods.generateAuthToken = async function () {
  // Makes it easier to refer to
  const user = this 
  const token = jwt.sign(
    {
      _id: user._id.toString(),
    } /* id is ObjectId(5ed0ef97405ebd524ada62d8).. jwt verwacht een string */,
    process.env.JWT_SECRET,
  )

  // Concat returned a new array with all the values
  user.tokens = user.tokens.concat({ token }) // See user mode

  await user.save() // Save token in mongo

  return token
}

Recourses