La gestione del login con PHP: cookies e sessione

Tra le nozioni elementari dello sviluppo del codice PHP troviamo spesso l'utilizzo della sessione HTTP, indispensabile per autenticare e riconoscere gli utenti. L'argomento va spesso di pari passo con la gestione del login, della registrazione di nuovi utenti e della condivisione di cookies tra client e server. La conoscenza di questi aspetti occupa un ruolo trasversale a molte applicazioni, siano esse realizzate in PHP, JSP, .NET o altro.

Tra le nozioni elementari dello sviluppo del codice PHP troviamo spesso l’utilizzo della sessione HTTP, indispensabile per autenticare e riconoscere gli utenti. L’argomento va spesso di pari passo con la gestione del login, della registrazione di nuovi utenti e della condivisione di cookies tra client e server. La conoscenza di questi aspetti occupa un ruolo trasversale a molte applicazioni, siano esse realizzate in PHP, JSP, .NET o altro.

Negli ultimi anni la gestione “manuale” (ovvero tramite codice realizzato ad hoc) di login e registrazione è diventata spesso superflua. Framework quali Zend e Symfony, per restare nell’ambito di PHP, offrono la possibilità di implementare sia l’autenticazione che la registrazione degli utenti in modo semplice, professionale e veloce. Lo svantaggio di ogni framework è però quello di non rendere immediata la personalizzazione delle funzionalità. Se dobbiamo modificare il processo di autenticazione o registrazione gestito dal framework, dobbiamo rispolverare le vecchie conoscenze di base, che riguardano quasi sempre la gestione dei cookies e della sessione.
Con questo articolo cercheremo di prendere due piccioni con una fava. Ci rivolgiamo sia agli sviluppatori meno esperti, che hanno bisogno di orientarsi nella metodologia PHP relativa all’utilizzo della sessione e dei cookies. Contemporaneamente cercheremo di offrire una guida sintetica, con le nozione essenziali, che gli sviluppatori più esperti potrebbero usare come riferimento quand’è necessario mettere le mani “sotto al cofano” di un framework PHP.

Considerato il target eterogeneo dell’articolo, inizieremo rispolverando velocemente i concetti base, per approfondire poi l’argomento nelle prossime pagine.

Concetti essenziali

Innanzitutto occorre ricordare che il protocollo HTTP è stateless, ovvero dopo ogni richiesta di una pagina da parte del browser, il server “dimentica” l’identità dell’utente. La soluzione tradizionale a questo inconveniente è l’utilizzo dei cookies, ovvero dei “biscottini” che client e server si scambiano ad ogni richiesta. In termini concreti, un cookie è un semplice file di testo che il browser allega ad ogni richiesta HTTP, un po’ come noi mostriamo un biglietto da visita quando ci presentiamo. Il meccanismo permette di distinguere i diversi browser che consultano la stessa pagina Web.
Il cookies permettono quindi di assegnare un’identità all’utente, che rimane però anonimo. Detta così, la questione può sembrare confusa. L’ambiguità è dovuta a cosa intendiamo con il termine “identità”. Tramite lo scambio di cookies il server è in grado di distinguere il browser A dal browser B ma questo, nella maggior parte delle applicazioni, non serve a molto. Pensiamo ad un servizio di home banking: l’utente Mario Rossi, possessore di un conto corrente, potrebbe collegarsi oggi dal browser A e domani dal browser B. È in questo senso che si parla di utente anonimo, nonostante egli sia già “identificato” grazie allo scambio di cookies.

Serve quindi un meccanismo che permetta al server di capire, anche se il browser è di volta in volta diverso, che abbiamo che fare con lo stesso utente. Questa esigenza è soddisfatta dalle variabili di sessione, che a differenza dei cookies sono memorizzate lato server. Si ottiene così un catena di collaborazione che permette di riconoscere il singolo individuo (e non solo il browser), allo scopo di autenticarlo nel sistema:

  1. Quando il server riceve una nuova richiesta, completamente sconosciuta e anonima, viene creato un nuovo oggetto sessione, il quale viene memorizzato lato server (ad esempio in un file). Tale oggetto si distingue dalle altre sessioni per mezzo di un ID unico
  2. Assieme all’oggetto sessione appena creato, il server crea anche un cookie, il cui valore è proprio l’ID della sessione creata al punto 1. Questo cookie viene inviato al browser assieme alla pagina richiesta
  3. Il browser, nel momento in cui riceve la pagina, riceve anche il cookie e lo memorizza, ad esempio all’interno del database SQLite integrato al browser stesso. Da questo momento in poi il browser spedirà il cookie tutte le volte che farà una nuova richiesta su quel server

La catena di eventi sopra elencata solitamente avviene in automatico, senza l’intervento dell’utente o dello sviluppatore. Abbiamo detto “solitamente” perché possono esistere delle eccezioni: se l’utente disabilita i cookies, o l’amministratore del server decide di gestire la sessione in maniera personalizzata, lo scenario potrebbe essere diverso. Quando qualcosa non funziona, ad esempio lavorando con un framework, è bene conoscere lo scenario di lavoro effettivo. Ciò significa sapere come il web server gestisce la sessione, se utilizza i cookies, dove vengono scritti gli eventuali files della sessione, controllare i permessi, e ovviamente verificare che il browser dell’utente sia abilitato all’invio dei cookies.

Login e autenticazione

Login e autenticazione

Il meccanismo del riconoscimento della sessione HTTP per mezzo dei cookies permette di distinguere i diversi browser. Quel che manca, adesso, è il riconoscimento del singolo utente, indipendentemente dal browser utilizzato. Ciò avviene attraverso il processo di login, che di solito si concretizza nella compilazione di una form HTML, dove l’utente inserisce nome e password. Quando il server riceve la form inviata dall’utente, inizia il processo di autenticazione. L’entità responsabile di questo processo, sia essa una pagina Web, una classe, o un’applicazione CGI, viene solitamente detta controller.

Il controller si occupa di verificare che i dati di autenticazione siano corretti, di solito incrociandoli con quelli memorizzati nel database. A questo punto dobbiamo memorizzare l’esito del processo di autenticazione, altrimenti l’utente dovrebbe inserire username e password ogni volta che richiede una pagina. La soluzione più comoda, e tuttora molto diffusa, è quella di memorizzare l’ID dell’utente (così come restituito dal database) all’interno della sessione, ad esempio

qui le scelte sono molteplici. Possiamo memorizzare nella sessione soltanto l’ID, come nell’esempio qui sopra, e poi usare tale ID tutte le volte che dobbiamo recuperare le informazioni caratteristiche dell’utente (esempio: nome, impostazioni preferite, numero di accessi ecc.). Un’altra scelta potrebbe essere quella di memorizzare nella sessione tutte le informazioni che ci servono, per evitare di dover consultare il database ogni volta, ad esempio

ecc…

dove le variabili user_id, user_name sono chiaramente valorizzate da una query SQL, eseguita solo durante l’autenticazione.
Se ci piace la seconda scelta, ovvero l’idea di memorizzare nella sessione un vasto set di informazioni dell’utente, probabilmente dobbiamo considerare alcune questioni 

  • Approccio OOB: non è bene memorizzare molte variabili come nell’esempio qui sopra, perché ciò rende il codice poco mantenibile. Possiamo ottenere lo stesso risultato memorizzando nella sessione un unico oggetto PHP, che faccia da wrapper alle variabili che ci interessano. In tal caso possiamo passare l’oggetto da una pagina all’altra come spiegato qui
  • Sicurezza: come detto a pagina precedente, il meccanismo di identificazione tramite sessione e cookies richiede solamente lo scambio di un cookie con all’interno l’ID della sessione. Non corriamo quindi nessun pericolo nel memorizzare i dati sensibili all’interno della sessione, perché tali dati saranno comunque memorizzati solo lato server. Ovviamente stiamo assumendo che il server remoto sia protetto da intrusioni e che le directories sensibili del server non siano pubblicamente raggiungibili
  • Performance: l’inserimento di molti dati nella sessione va valutato nell’ambito delle performance e delle risorse assegnate al server. Ciò significa conoscere il peso della singola sessione, che in generale dipenderà da quante variabili memorizziamo al suo interno. Tale peso (ad esempio in KB) va moltiplicato per il numero di utenti attesi. Se il risultato è troppo vicino alla memoria massima disponibile del server, forse è il caso di sfoltire il numero di variabili che vogliamo memorizzare nella sessione, o aumentare le risorse assegnate al server

Esempi pratici

Esempi pratici

Vediamo adesso alcuni snippet che potrebbero risultare utili per implementare le funzionalità discusse nelle pagine precedenti. Iniziamo dal controller responsabile dell’autenticazione, che potrebbe usare una riga come

allo scopo di procedere con l’autenticazione solamente quando la richiesta arriva effettivamente dalla pagina di login, dove dovrebbe trovarsi un input HTML di nome “Entra”. In tal modo l’azione passa al metodo checkUser, che si occupa dell’autenticazione vera e propria. Prima di procedere con il confronto tra i dati di autenticazione e quelli presenti nel database, è bene aggiustare i parametri di ingresso

sola questo punto potremo verificare che i dati dell’utente siano corretti, confrontandoli con quelli nel database, ad esempio

dove la funzione getUserData si occupa di leggere un array associativo dal database, mediante una query che abbia come parametri di ingresso i dati inseriti dall’utente. La funzione setSessionVars si occuperà invece di impostare nella sessione le variabili o l’oggetto che identificano l’utente, come discusso a pagina precedente.

Dopo aver autenticato l’utente, solitamente si inserisce un controllo in tutte le pagine dell’applicazione, per verificare se nella sessione sono contenute le variabili previste, ovvero quelle impostate dalla funzione setSessionVars. Se non troviamo tali variabili, assumiamo che l’utente non sia autenticato nel sistema, e mostriamo una pagina di cortesia che rimandi al login o alla registrazione.

L’ultima funzionalità che riguarda spesso la procedura di login è la funzione ricordami. Questa può essere implementata in molti modi. Una soluzione semplicissima potrebbe essere quella di impostare un cookie temporaneo al momento dell’autenticazione, con il valore della checkbox associata alla casellina “Ricordami al prossimo accesso”. Ciò permette di riconoscere un utente che abbia spuntato la casellina al collegamento precedente, ad esempio

in questo caso, se il server trova già l’ID dell’utente memorizzato nella variabile di sessione customerID, ma non trova né il cookie “temporaneo”, né l’opzione “ricordami” nella variabile di sessione rememberMe (eventualmente impostata nel collegamento precedente), si provvede con l’azzeramento della sessione, forzando così l’utente ad effettuare una nuova autenticazione.

Queste le nozioni di base da ricordare quando gestiamo un sistema di autenticazione. Oltre questi aspetti, dobbiamo sempre tenere a mente che i cookies hanno una scadenza, che può essere di minuti, ore o addirittura anni. Anche questo è un aspetto da controllare, sia quando realizziamo il codice a mano, sia che lavoriamo appoggiandoci a un framework.