Învățarea modului de autentificare a utilizatorilor într-o aplicație este unul dintre primele lucruri pe care le înveți în orice curs axat pe tehnologii de backend.

Și este unul dintre primii pași pe care îi faci atunci când construiești o aplicație de socializare, o aplicație pentru învățarea de la cursuri online și așa mai departe.

În acest articol, vom analiza conceptele de bază de autentificare într-un mod adecvat pentru începători.

Cum se autentifică un utilizator cu cookie-uri

În primul rând, să construim o aplicație simplă care autentifică utilizatorii folosind metoda clasică de nume de utilizator și parolă – nu ne vom îngrijora acum de conexiunile la baza de date.

Ori de câte ori încercăm să încărcăm o pagină, browserul trimite o cerere către server, care răspunde în consecință.

Inițial, atunci când un utilizator accesează site-ul, i se solicită numele de utilizator și parola înregistrate. Odată date, ele se fac cunoscute serverului, astfel încât solicitările ulterioare către server să nu implice reafirmarea identității lor.

Dar cum va urmări serverul despre ce utilizatori sunt deja autentificați și care nu? Aici intră cookie-urile.

Potrivit w3.org, cookie-urile sunt definite ca:

„Bucăți de text stocate pe computerul client și trimise împreună cu solicitarea HTTP către site-ul web pentru care au fost create.”

Cookie-urile sunt create și stocate după conectarea utilizatorului și sunt consultate înainte de a satisface cereri succesive. Apoi expiră în conformitate cu o limită de timp specificată.

let express=require('express')
let cookie_parser=require('cookie-parser')
let app=express()
app.use(cookie_parser('1234'))

Mai întâi, configurăm aplicația noastră Express și includem cookie-parser middleware. Analizează antetul cookie al cererii și îl adaugă la req.cookies sau req.signedCookies (dacă se folosesc chei secrete) pentru procesare ulterioară.

cookie-parser ia ca argument o cheie secretă, care va fi utilizată pentru a crea un HMAC cu valoarea curentă a cookie-ului. Dacă valoarea este modificată ulterior, aceasta este detectată deoarece semnătura făcută la momentul creării nu se potrivește cu semnătura curentă.

Apoi, când utilizatorul vizitează adresa URL adecvată (cum ar fi / login sau ceva similar), trebuie să efectuăm câteva verificări. Să presupunem că utilizatorul se conectează pentru prima dată.

let cookie_Stuff=req.signedCookies.user
//But the user is logging in for the first time so there won't be any appropriate signed cookie for usage.
if(!cookie_Stuff)//True for our case
    {
        let auth_Stuff=req.headers.authorization
        if(!auth_Stuff)//No authentication info given
        {
            res.setHeader("WWW-Authenticate", "Basic")
            res.sendStatus(401)
        }

Folosim antetul de răspuns WWW-Authenticate pentru a defini metoda de autentificare care ar trebui utilizată pentru a avea acces la o resursă (metoda „de bază”).

Răspunsul de la client constă în numele de utilizator și parola separate prin două puncte. Este codificat în bază64 și este atașat la antetul de autorizare al cererii.

Utilizatorului i se solicită informații de autentificare, care sunt extrase și verificate. De fapt, trebuie să verificăm dintr-o bază de date, dar facem o verificare naivă pentru simplitate chiar acum.

Dacă sunt date valorile corecte, configurăm un cookie adecvat. Dacă nu, atunci îi solicităm utilizatorului din nou. Următorul fragment de cod efectuează acești pași:

else
        {
            step1=new Buffer.from(auth_Stuff.split(" ")[1], 'base64')
 //Extracting username:password from the encoding Authorization: Basic username:password
            step2=step1.toString().split(":")
//Extracting the username and password in an array
            if(step2[0]=='admin' && step2[1]=='admin')
            {
//Correct username and password given
                console.log("WELCOME ADMIN")
//Store a cookie with name=user and value=username
                res.cookie('user', 'admin', {signed: true})
                res.send("Signed in the first time")
            }
            else
            {
 //Wrong authentication info, retry
                res.setHeader("WWW-Authenticate", "Basic")
                res.sendStatus(401)
            }
        }
    }

Ce zici de data viitoare când utilizatorul nostru face o cerere? Din acel moment până când cookie-ul este șters sau expiră, verificăm valoarea cookie-ului pentru autentificare.

else
    {//Signed cookie already stored
        if(req.signedCookies.user=='admin')
        {
            res.send("HELLO GENUINE USER")
        }
        else
        {
     //Wrong info, user asked to authenticate again
            res.setHeader("WWW-Authenticate", "Basic")
            res.sendStatus(401)
        }
    }
})

Acum știi cum să autentifici un utilizator cu cookie-uri!

Puteți verifica modulul cookie stocat navigând la secțiunea Stocare a instrumentelor pentru dezvoltatorii browserului dvs. și accesând fila Cookies. Valoarea cookie-ului și valorile analizate sunt afișate separat în două secțiuni (în Firefox, de exemplu).

Cum se autentifică un utilizator cu sesiuni

Să analizăm o analogie care să ne ajute să înțelegem sesiunile în comparație cu cookie-urile. Imaginați-vă că sunteți o persoană absentă, care uită tot timpul numele prietenilor dvs.

O soluție este să oferiți fiecărui prieten un card cu numele și imaginea acestuia. De fiecare dată când îi întâlnești, întreabă pur și simplu să vezi cardul pe care i l-ai dat pentru a-ți reîmprospăta memoria.

Problema este că prietenii tăi pot pierde acest card. Sau doi dintre ei pot schimba cărți și vă pot juca o farsă. Sau poate prietenul tău nu are suficient spațiu pentru a stoca un alt card.

În ambele cazuri, mecanismul de autentificare prezintă semne de slăbiciune. Dar aceasta este în esență ceea ce fac cookie-urile – acestea sunt stocate în partea clientului și de fiecare dată când clientul face o cerere către site-ul său, cookie-ul este accesat pentru autentificare. Mănâncă puțin spațiu sau poate că sunt manipulate.

În loc să folosiți cookie-uri, să presupunem că creați un card pentru fiecare prieten și îl păstrați la dvs. Când le vedeți, atribuiți o modalitate de asortare a cardului cu persoana respectivă pentru o identificare ușoară. În acest fel, informațiile nu aparțin clientului și, prin urmare, sunt mai sigure.

Acest scenariu ne arată cum funcționează sesiunile. Informațiile despre un utilizator nou autentificat se află pe server și numai informațiile minime sunt transmise clientului. În acest fel, clientul poate fi mapat cu informațiile stocate.

express-session creează un middleware de sesiune care vă permite să configurați cu ușurință sesiunile și să le manipulați.

Partea de server implicită de stocare este MemoryStore. Pentru a stoca informații despre sesiune ca fișiere JSON, aveți nevoie de sesiune-fișier-stocare. Codul de mai jos face următoarele:

  • Configurează aplicația Express
  • Acesta spune middleware-ului să solicite autentificare dacă nu este specificat niciunul și, în caz contrar, verifică dacă numele de utilizator și parola se potrivesc.
  • Dacă nu, trebuie să facă din nou aceeași cerere de autentificare, altfel sesiunea este stabilită.
  • Apoi adaugă numele de utilizator ca atribut de utilizator și apoi îl verifică.

Din nou, acesta este doar un exemplu simplu – informațiile care trebuie comparate cu datele date ar trebui cel puțin stocate într-o bază de date.

let app=express()
app.use(session({
  store: new File_Store,
  secret: 'hello world',
  resave: true,
  saveUninitialized: true
}))
app.use('/', (req,res,next)=>{
  if(!req.session.user)
  {
    console.log("Session not set-up yet")
    if(!req.headers.authorization)
    {
      console.log("No auth headers")
      res.setHeader("WWW-Authenticate", "Basic")
      res.sendStatus(401)
    }
    else
    {
      auth_stuff=new Buffer.from(req.headers.authorization.split(" ")[1], 'base64')
      step1=auth_stuff.toString().split(":")
      console.log("Step1: ", step1)
      if(step1[0]=='admin' && step1[1]=='admin')
      {
        console.log('GENUINE USER')
        req.session.user="admin"
        res.send("GENUINE USER")
      }
      else
      {
        res.setHeader("WWW-Authenticate", "Basic")
        res.sendStatus(401)
      }
    }
  }

Cum se autentifică utilizatorii cu Passport.js Middleware

Până acum, am văzut cum să autentificăm utilizatorii cu cookie-uri și sesiuni. Acum vom vedea o a treia metodă de autentificare.

Passport.js este un middleware de autentificare pentru Node care vă permite să autentificați utilizatorii utilizând sesiuni și OAuth. De asemenea, vă permite să creați strategii personalizate și multe altele.

let passport=require('passport')
let bcrypt=require('bcrypt-nodejs')
let User_Obj=require('./Set_Up_Database_Stuffs')
const local_strategy=require('passport-local').Strategy

Acest cod stabilește toate modulele necesare pentru definirea unei strategii locale adecvate. passport-local strategia permite autentificarea numai cu un nume de utilizator și o parolă.

Asigurați-vă că numele elementului de introducere a formularului pentru numele de utilizator este „nume de utilizator”, iar pentru parolă este „parolă”. Deși sună foarte intuitiv, mi-a cauzat multe probleme, deoarece trecusem cu vederea această parte. Acum este unul dintre acele lucruri pe care probabil nu le voi trece cu vederea (cel puțin pentru viitorul apropiat).

De asemenea, puteți schimba numele implicite ale câmpurilor trecând JSON înainte de funcția de apel invers în apelul către local_strategy, unde este structura JSON usernameField: “Un nume nou pentru acest câmp” și passwordField: “Un nume nou pentru acest câmp”.

passport.use(new local_strategy(
    async (username, password, done)=>{
        console.log("Here inside local_strategy" ,username, password)
    
    try
    {
        let row1=await User_Obj.findOne({username: username})
        console.log(row1)
        //row1 should be the tuple from database where the username field matches the username supplied.
        if(row1==null)
        {
            console.log("NO RECORDS FOUND")
            return done(null, false)
        }
        else
        {
            console.log("Record found")
            console.log(row1)
            if(bcrypt.compareSync(password, row1.password))//Compare plaintext password with the hash
            {
                console.log("The passwords match")
                console.log("Finished authenticate local")
                return done(null, row1)
            }
            else
                {
                    console.log("The passwords don't match")
                    return done(null, false)
                }
        }
        
    }
    catch(err){
        console.log("Some error here")
        return done(err)}
    }
  ));

Liniile de mai sus sunt o implementare simplistă a local-strategy unde datele sunt verificate dintr-o bază de date cu numele de utilizator ca câmp.

app.post('/auth', passport.authenticate('local', {successRedirect: 'articles', failureRedirect: '/failurepage'}))
//Triggers the local strategy. If successful, redirect to articles page else show failure page
app.post('/donesignup', objForUrlencoded, async (req,res)=>{
    console.log(req.body)
    try
    {
        let row1=await User_Obj.findOne({username: req.body.username})
        console.log(row1)
        if(row1!=null)
        {
            console.log("That username already exists")
            res.render('signup')
        }
        else
        {
            console.log(bcrypt.hashSync(req.body.password[0], bcrypt.genSaltSync(8), null))//Get the hash of the password to store it in the database
            let save_this=User_Obj({username: req.body.username, password: bcrypt.hashSync(req.body.password[0], bcrypt.genSaltSync(8), null)})
            console.log(save_this)
            save_this.save()
            console.log("SAVED IT")//Save it to database
        }
    }
    catch(err){}
})

Ori de câte ori utilizatorul accesează ruta / auth, acesta declanșează strategia locală care se execută conform specificațiilor. Dacă există un eșec în timpul autentificării, acesta redirecționează către o pagină de eșec. În caz contrar, se redirecționează către o pagină de articole (sau orice pagină aveți nevoie).

La o cerere de postare către / donesignup, verifică dacă numele de utilizator există deja. Dacă nu, atunci îl adaugă ca un tuplu la baza de date, unde câmpurile sunt numele de utilizator și un hash al parolei date.

Încheierea

Deci, acest lucru îmi înfășoară rezumatul diferitelor metode de autentificare în nod.

Codul folosit aici este departe de a fi ideal, dar sper că ajută pe cineva care tocmai a început să învețe despre autentificare și se simțea copleșit.

Dacă ați citit până aici, vă mulțumesc foarte mult și vă rugăm să corectați eventualele bug-uri care s-ar fi putut strecura, în ciuda eforturilor mele. Vă mulțumesc încă o dată și codificare fericită.