

Authenticatie en Autorisatie
Authenticatie en autorisatie
INTRODUCTIE


Voorbereiding
-
We werken verder op de code uit week 5
https://github.com/pgmgent-pgm-3/validation-authentication-demo-start/tree/main-auth - BRANCH: main-auth



Installatie
Installeer bcrypt
npm install bcrypt
Installeer cookie-parser
npm install cookie-parser
Installeer jsonwebtoken
npm install jsonwebtoken

Voorbereiding
- Om cookies te kunnen lezen, hebben we de cookieParser nodig. Update je app.js.
app.use(cookieParser());
- Vergeet niet om de body-parser te importeren
import cookieParser from "cookie-parser";

Velden voor register form
- Binnen de register-action van de AuthController beschrijven we de velden die we nodig hebben voor register:
- inputs-array met...
- firstname, lastname, email, password
- inputs-array met...
- We halen ook alle beschikbare roles op
- inputs & roles sturen we als data mee naar de
register-view
Code-snippet op de volgende slide! -->

Velden voor register form
import Role from "../models/Role.js";
export const register = async (req, res) => {
const inputs = [
{ name: "firstname", label: "Voornaam", type: "text" },
{ name: "lastname", label: "Achternaam", type: "text" },
{ name: "email", label: "E-mail", type: "text" },
{ name: "password", label: "Password", type: "password" },
];
const roles = await Role.query();
res.render("register", {
layout: "layouts/authentication",
inputs,
roles,
});
}
Resultaat in browser, volgende slide -->

Velden voor register form

Authenticatie en autorisatie
REGISTRATIE


Validate Register Form
- Een nieuw formulier, betekent nieuwe validatie
- We valideren o.a. of het e-mailadres en wachtwoord conform de regels is.
- Maak in de folder validation een file AuthRegisterValidation.js


- Telkens wanneer men registreert, willen we de data controleren. Vul aan met meerdere validators.
import { body } from 'express-validator';
export default [
body("firstname").notEmpty().withMessage("Voornaam is een verplicht veld."),
body("lastname").notEmpty().withMessage("Achternaam is een verplicht veld."),
body("email")
.notEmpty()
.withMessage("E-mail is een verplicht veld.")
.bail()
.isEmail()
.withMessage("Onjuist e-mail adres"),
body("password")
.isLength({ min: 6 })
.withMessage("Wachtwoord moet bestaan uit minstens zes tekens."),
body("role").notEmpty().withMessage("De gebruiker heeft een rol nodig."),
];
Validate Register Form

- Voeg nu de validators middleware toe vlak voor we de controller postRegister ingaan.
app.post(
"/register",
AuthRegisterValidation,
AuthController.postRegister,
AuthController.register
);
- Let op: zorg dat je de validationAuthentication ook importeert in app.js
import AuthRegisterValidation from "./middleware/validation/AuthRegisterValidation.js";
Validate Register Form

- Eenmaal aangekomen in de postRegister controller kunnen we het resultaat van de validatie opvragen.
export const postRegister = async (req, res, next) => {
try {
const errors = validationResult(req);
// if we have validation errors
if (!errors.isEmpty()) {
console.log("We\'ve got some errors, dude...");
}
} catch(e) {
next(e.message);
}
};
Validate Register Form

- Er zijn nu twee opties
- De validatie loopt fout, dan moeten we opnieuw de de registratie pagina tonen via next()
- De validatie loopt goed, dan registreren we de gebruiker en navigeren we naar de login-pagina
- In het eerste geval gaan hebben we dus nog een extra schakel in de middleware (register):
app.post(
"/register",
AuthRegisterValidation,
AuthController.postRegister,
AuthController.register
);
Validate Register Form

- We parsen de fouten en geven de foutmeldingen per input veld door naar de register route via de next() functie.
if (!errors.isEmpty()) {
// set the form error fields
req.formErrorFields = {};
errors.array().forEach((error) => {
req.formErrorFields[error.path] = error.msg;
});
// set the flash message
req.flash = {
type: "danger",
message: "Er zijn fouten opgetreden",
};
return next();
}
Validate Register Form

export const register = async (req, res) => {
// input fields
const inputs = [
{
name: "firstname",
label: "Voornaam",
type: "text",
value: req.body?.firstname ? req.body.firstname : "",
err: req.formErrorFields?.firstname ? req.formErrorFields.firstname : "",
},
{
name: "lastname",
label: "Achternaam",
type: "text",
value: req.body?.lastname ? req.body.lastname : "",
err: req.formErrorFields?.lastname ? req.formErrorFields.lastname : "",
},
{
name: "email",
label: "E-mail",
type: "text",
value: req.body?.email ? req.body.email : "",
err: req.formErrorFields?.email ? req.formErrorFields.email : "",
},
{
name: "password",
label: "Password",
type: "password",
value: req.body?.password ? req.body.password : "",
err: req.formErrorFields?.password ? req.formErrorFields.password : "",
},
];
// get the roles
const roles = await Role.query();
const flash = req.flash || {};
// render the register page
res.render("register", {
layout: "layouts/authentication",
inputs,
roles,
flash,
});
};
Afhandelen errors in register

- Zijn alle velden correct, dan valideren we ook of de gebruiker al dan niet al bestaat.
[...]
else {
const user = await User.query().findOne({ email: req.body.email });
const role = await Role.query().findOne({ id: req.body.role });
// validate if the role exists in the database
if (!role) {
req.flash = { type: "danger", message: "Deze rol bestaat niet." };
req.formErrorFields = { role: "Deze rol bestaat niet." };
return next();
}
// validate if the user already exists
if (user) {
req.flash = { type: "danger", message: "Dit e-mail adres is al in gebruik." };
req.formErrorFields = { email: "Dit e-mail adres is al in gebruik." };
return next();
}
// temp res.send
res.send('no errors, registrate the user')
}
[...]
Extra validatie Register Form
Authenticatie en autorisatie
HASHING



Hashing refers to the process of generating a fixed-size output from an input of variable size using the mathematical formulas known as hash functions. This technique determines an index or location for the storage of an item in a data structure.
Hashing turns plain text into a unique code, which can't be reverted into a readable form

- Encryptie: tweerichting
- Decryptie m.b.v. een sleutel
- Hashing: eenrichting
- Hash collision is onwaarschijnlijk
- De input vinden o.b.v. de hash is onwaarschijnlijk
- Output heeft een vaste lengte
Hashing ≠ encryptie

- Checksum-verificatie van downloads
- Digital signatures
- Git commit ID (SHA-1)
- Wachtwoorden opslaan in een database
- ...
Hashing-toepassingen

- Wachtwoorden zullen we eerst onleesbaar maken, voor we ze opslaan in de database
- We gebruiken daarvoor de bcrypt library
- https://www.npmjs.com/package/bcrypt
Hashing met bcrypt


- Via de .hash() methode kan je van een wachtwoord een hash maken
- eerste argument = wachtwoord
- tweede argument = aantal salt rounds
const hashedPassword = bcrypt.hash("geheim123", 10);
Hashing met bcrypt
- Salt rounds: performance vs. security
- Factor 10: calculation is done 1000 times (2 ^ 10)

- Eens je de hash hebt, bewaren we de volledige user veilig in de database door een nieuwe record aan te maken.
Hashing met bcrypt
import bcrypt from "bcrypt";
export const postRegister = async (req, res, next) => {
[...]
const hashedPassword = bcrypt.hashSync(req.body.password, 10);
await User.query().insert({
firstname: req.body.firstname,
lastname: req.body.lastname,
email: req.body.email,
password: hashedPassword,
role_id: parseInt(req.body.role),
});
res.redirect("/login");
};

- Hashing is niet omkeerbaar, dus je kan het niet "dehashen" om de waarde van het wachtwoord te achterhalen.
- Om later (bij een login) te weten of het wachtwoord correct is, zullen we telkens de ingegeven waarde hashen en vergelijken met de bewaarde, originele hash
- In bcrypt doe je dat met de .compare() functie
Hashing met bcrypt
// Load hash from your password DB.
const isPassCorrect = bcrypt.compare("secret789", hash);
// --> false
// // Load hash from your password DB.
const isOtherPassCorrect = bcrypt.compare("geheim123", hash);
// --> true

- Log-in pagina
- Velden toevoegen: e-mail, paswoord
- Nieuwe middleware toevoegen
- Valideer of een gebruiker met dit e-mailadres bestaat
- Nee: toon foutboodschap
- Ja: vergelijk hash van paswoord met de hash uit DB
- Niet gelijk: toon foutboodschap
- Gelijk: redirect naar homepagina
- Valideer of een gebruiker met dit e-mailadres bestaat
Oefening - login
Validatie, authenticatie en autorisatie
AUTHENTICATIE


Authenticatie is het proces waarbij iemand nagaat of een gebruiker, een andere computer of applicatie daadwerkelijk is wie hij beweert te zijn.

Authenticatie - inloggen
- Zorg er eerst voor dat ook je login formulier wordt voorzien van validatie door je login en postLogin controller verder aan te vullen.
- Controleer daarbij ook of de gebruiker bestaat.

JWT
- Om ervoor te zorgen dat een user de homepagina te zien krijgt op voorwaarde dat deze is ingelogd, gebruiken we een zogenaamd JSON Web Token ofwel JWT.
Client
Aanmelden
Server
JWT

JWT
- Een token wordt bewaard op de client als een http-only cookie, dat betekent dat ze niet door client-side javascript kan worden onderschept (en dus ook niet door malafide scripts).
- Bij elke request wordt de token verstuurd naar de server en kan de server er van uitgaan dat een gebruiker correct is aangemeld.

JWT
- We maken een token met de jsonwebtoken module.
// create the webtoken
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.TOKEN_SALT,
{ expiresIn: '1h' }
);
// add token to cookie in response
res.cookie('token', token, { httpOnly: true });
// redirect to home page
res.redirect('/');
- Salt: encryption key (in this case)


Validatie, authenticatie en autorisatie
AUTORISATIE


Authenticatie
- Bewijzen wie je bent bv.
- E-mailadres, wachtwoord
- Single sign-on
- Passkeys
- ...
Autorisatie
- Machtigingen verlenen aan een geauthenticeerde gebruiker bv.
- Rollen: admin, super admin, manager, standaard gebruiker, lezer ...
- Operaties: lezen, schrijven, verwijderen ...

Autoriseren
- Telkens wanneer er een actie is dat een anonieme
( = niet ingelogde) gebruiker niet mag uitvoeren, doen we aan autorisatie- = controleren of de gebruiker ingelogd is en de nodige rechten heeft
- vb profiel bekijken 👀, bestelling maken 💰, ...
- We bekijken de JSON WEB TOKEN (opgeslagen in een cookie 🍪 )
- Daarvoor schrijven we een stukje middleware, we kiezen in dit voorbeeld voor de naam: jwtAuth

jwtAuth middleware
- We schrijven een nieuwe middleware functie die we chainen vòòr we de controller aanspreken.
- Maak een nieuwe file in de middleware folder, jwtAuth.js.
import jwt from 'jsonwebtoken';
export const jwtAuth = (req, res, next) => {
const token = req.cookies.token;
try {
const userPayload = jwt.verify(token, process.env.TOKEN_SALT);
const user = await User.query()
.findOne({id: userPayload.userId,})
.withGraphFetched("role");
// set this in client's request
req.user = user;
next();
} catch(e) {
res.clearCookie('token');
return res.redirect('/login');
}
}
app.get('/', jwtAuth, ShopController.index);

jwtAuth middleware
- We controleren of gebruiker correct aangemeld
-
jwt.verify()
-
- Indien correct aangemeld, dan kunnen we door naar de volgende schakel in de middleware ketting met next().
- Indien de token expired is,
dan komen we terecht in de catch blok.- We verwijderen de token op de client
- We navigeren terug naar de login pagina
Validatie, authenticatie en autorisatie
OEFENINGEN


Oefening
In deze oefening breid je je applicatie uit met een gastenboek voor Georgette in de bloemetjes te zetten.
Trouwe klanten kunnen wel of niet, (naargelang hun rol) een berichtje nalaten
- Je maakt een entity Message aan.
- Primary Key: id
- Subject (varchar)
- Comment (text)
- Foreign Key user_id
- een many-to-one relatie met de
User-entity
- een many-to-one relatie met de


Oefening
- Gebruik de rol van de user, render énkel hetgeen uit wat de ingelogde user mag zien, conform diens rol.
- Gebruik hiervoor Handlebars, schrijf eventueel een custom block helper 💪🏻.
- Controleer bij elke POST/GET/PUT of de gebruiker toegang heeft om de HTTP method uit te voeren, indien niet, geef je de respectievelijke HTTP error code terug:
- Bijv. 401: Niet geautoriseerd
- Controleer deze lijst op specifiekere error codes.
PGM3/6 - Authenticatie & Authorisatie
By Frederick Roegiers
PGM3/6 - Authenticatie & Authorisatie
- 72