No. 101

Titolo originale: The problem with passwords

Scritto da: Lyle Mullican

Pubblicato in: Browsers e Scripting.

passwordLa recente rubrica del ricercatore esperto di usabilità Jakob Nielsen promuove un cambio fondamentale nella progettazione del campo password nel web. Egli ritiene che sia giunto il momento di mostrare le password in chiaro mentre l'utente le scrive, abbandonando l'approccio tradizionale consistente nel mostrare una serie di asterischi o pallini al posto della password stessa.

La controversa proposta di Nielsen dimostra il principio secondo il quale la maggior parte delle decisioni progettuali sul web richiedono dei compromessi. Gli obiettivi degli utenti e gli obiettivi di business non si intersecano sempre. Questioni di sicurezza, usabilità ed estetica competono spesso tra loro. Dobbiamo darci delle priorità e bilanciare questi interessi per raggiungere i migliori risultati in qualunque situazione.

Le problematiche di sicurezza sono più difficili da gestire perché sono una seccatura. Tutto ciò che vorremmo è far sì che le persone raggiungano il fantastico tool che abbiamo sviluppato: invece dobbiamo costruire barriere tra l'utente e l'applicazione. Gli utenti devono dar prova della loro identità. Non possiamo accettare nessun dato da essi, a meno che questi non siano stati scrupolosamente puliti.

Sfortunatamente questa è la realtà. Una buona parte del traffico web è davvero maligno e i dati sensibili vengono rubati. Tipicamente, per accedere ad un'applicazione, chiediamo all'utente uno username (spesso un indirizzo email) ed una password ad esso collegata. Lo username identifica la persona, mentre la password prova che la persona che sta inserendo lo username è proprio quella persona che ha creato l'account. Questa è la teoria, basata su due assunzioni:

  1. una password non sarà mai visibile al di fuori della testa della persona che l'ha creata,
  2. sia lo username sia la password possono essere richiamate dalla memoria al bisogno.

Questo approccio mette un carico cognitivo significativo sulle persone che usano i siti web che richiedono l'autenticazione. In generale, ce la caviamo molto bene, ma è facile vedere le debolezze di questo sistema. Le password facili da ricordare sono anche facili da indovinare. Quando le persone sono costrette a scegliere password forti, molto probabilmente le scriveranno o se le dimenticheranno. La risposta tipica a questo problema è un meccaniscmo di reset, che naturalmente mina la solidità dell'intero sistema. Non importa quando la mia password sia criptata con il più forte codice conosciuto all'uomo: può semplicemente essere resettata da chiunque sappia quale scuola superiore io abbia frequentato.

Questa è una delle ragioni per cui Nielsen suggerisce di abbandonare il password masking: le persone si sentono frustrate e spesso resettano una password che non hanno effettivamente dimenticato, ma semplicemente sbagliato a digitare. Dare un feedback chiaro, senza lettere oscurate, ridurrebbe gli errori, aumentando la user experience e diminuendo il bisogno di alternative insicure.

Tuttavia, apportare un cambiamento così radicale ad una fondamentale interazione con l'utente potrebbe presentare dei seri problemi. Considerate una situazione in cui una password debba essere inserita di fronte ad un folto gruppo di persone, ad esempio mentre si sta usando un proiettore durante una conferenza. E molti anni di esperienza web hanno creato delle aspettative su come dovrebbero funzionare gli elementi di una form. Le persone hanno capito che il password masking è stato inventato per la loro sicurezza. Non riuscire a soddisfare queste aspettative può minare la fiducia e noi non possiamo permetterci di perdere la fiducia degli utenti.

C'è una via di mezzo, un modo per fornire un feedback e ridurre gli errori nelle password che non sacrifichi la user experience? Almeno due schemi di progettazione affrontano questa questione nelle applicazioni offline e, con un po' di JavaScript, possiamo portarli sul web.

Ora lo vedi, ora non lo vedi

La soluzione più semplice è quella di mascherare la password di default e contemporaneamente dare la possibilità agli utenti di cambiare la modalità del campo a testo in chiaro. Perfino Nielsen incidentalmente menziona questa opzione. Questo approccio permette ad una persona di avere la conferma che la password sia stata inserita correttamente, ma pone anche il controllo fermamente nelle mani dell'utente. Questo meccanismo di cambio modalità si incontra spesso nel pannello preference del WiFi, ma è raramente implementata altrove. (Nota: almeno un blog ha propugnato una simile tecnica mentre stavo scrivendo questo articolo.)

Dovrebbe essere semplice scrivere un controllo che cambi da password a text l'attributo type di un elemento di input HTML. Sfortunatamente però non lo è. Internet Explorer non permette a JavaScript l'impostazione di questo particolare attributo, quindi dobbiamo essere un po' più creativi. Le due funzioni seguenti dovrebbero fare al caso nostro:

(Le righe che vanno a capo sono segnate con » ,ndr.)

window.onload = function() {
if(document.getElementsByTagName) {
var inputs = document.getElementsByTagName('input');
for(var i in inputs) {
var input = inputs[i];
if(input.type == 'password') {
toggle_control = document.createElement('label');
toggle_control.innerHTML = "<input type=\"checkbox\ »
" "+ "onclick=\"toggle_password('"+ input.id+"',this »
.checked) \" />"+" Show password";
input.parentNode.insertBefore(toggle_control, input »

.nextSibling);
}
}
}
}

function toggle_password(element_id, show_text) {
if(document.getElementById) {
var password_input = document.getElementById(element_id);
var new_input = document.createElement('input');
with(new_input) {
id = password_input.id;
name = password_input.name;
value = password_input.value;
size = password_input.size;
className = password_input.className;
type = show_text ? 'text' : 'password';
}
password_input.parentNode.replaceChild(new_input, »
password_input);
}
}

La prima funziona fa una scansione del documento per cercare tutti gli elementi input e mette da parte quelli con type password. Si tenga presente che questo codice ha il solo scopo di dimostrare il concetto e che il processo potrebbe essere migliorato usando un framework JavaScript come Prototype.

Dopo ogni input, la funzione inserisce un checkbox con un label che permette di cambiare il campo da testo oscurato e testo in chiaro. La seconda funzione controlla il comportamento stesso del cambio: quando l'utente clicca sul controllo per il cambio, la funzione crea un nuovo elemento input e lo scambia con quello esistente, passando il valore e le altre proprietà da un elemento input all'altro.

Un approccio alternativo è quello di creare il testo di input una sola volta e di scambiare le proprietà di display per mostrare o nascondere il campo appropriato. Uno svantaggio di questo metodo, tuttavia è che l'id dell'elemento deve essere unico. Dal momento che il testo di input parallelo avrà il suo proprio id, non erediterà alcuna regola CSS che faceva riferimento all'elemento originale mediante l'ID.

Guardate l'esempio uno per vedere questa tecnica in azione. Questa soluzione è facile da implementare e segue il principio del progressive enhancement: in mancanza di JavaScript, i campi password manterrano il loro solito comportamento. Il controllo per lo scambio dà il potere all'utente di poter scegliere tra il mostrare una password o il nasconderla in circostanze particolari. Il principale svantaggio è che potrebbe ancora minare il concetto di un utente del campo password come "black box" (scatola nera). Siamo così totalmente abituati a pensare alle nostre password come ad un segreto che il solo offrire l'opzione di mostrarla in chiaro potrebbe sconvolgerci.

Seconda alternativa

Gli errori di battitura sono particolarmente comuni sui dispositivi touchscreen come gli iPhone, dove le dita non possono sentire al tatto i confini di un tasto. Prevedendo che l'inserimento di password senza un feedback visivo potrebbe causare dei problemi, Apple ha adottato un approccio interessante: l'ultima lettera inserita nel campo rimane visibile per un paio di secondi prima di trasformarsi in un pallino. Ciò crea la possibilità di trovare gli errori senza mostrare l'intera password tutta insieme.

Possiamo riprodurre questo oscuramento progressivo della password con HTML e JavaScript, sebbene ci vorrà un po' più di codice che per il precedente esempio. Considerate quanto segue:

window.onload = function() {
if(document.getElementsByTagName) {
var inputs = document.getElementsByTagName('input');
var password_inputs = Array();
for(var i in inputs) {
if(inputs[i].type == 'password') {
password_inputs.push(inputs[i]);
}
}
for(var i in password_inputs) {
var input = inputs[i];

var masking_element = document.createElement('input');
with(masking_element) {
style.position = 'absolute';
id = input.name + '_mask';
type = 'text';
size = input.size;
className = input.className;
}
masking_element.onfocus = function(){this.nextSibling »
.focus()};
input.parentNode.insertBefore(masking_element, input);

input.onchange = function() {

if(this.timer){
clearTimeout(this.timer);
}

var mask_character = "\u2022";
var last_character = this.value.charAt(this »
.value.length-1);

var masked_text = this.previousSibling.value;
var password_text = this.value;

if(masked_text.length < password_text.length) {
this.previousSibling.value = password_text.substr(0,
password_text.length-1).replace(/./g,
mask_character)+last_character;
} else {
this.previousSibling.value = password_text »

.replace(/./g,mask_character);
}
this.timer = setTimeout("with(document.getElement »
ById('"+masking_element.id+"')){value=value »
.replace(/./g,'"+mask_character+"')}",2000);

}
input.onkeyup = input.onchange;
input.onchange();

}
}
}

Questa volta, abbiamo creato un secondo text input che si mette direttamente sopra ogni password input (gli avvertimenti sull'ereditarietà dei CSS dell'esempio precedente si applicano a questo). Manipolando il suo valore al mutare del campo originale, possiamo controllare quello che l'utente vede. Analizziamo ogni step dello script.

window.onload = function() {
if(document.getElementsByTagName) {
var inputs = document.getElementsByTagName('input');
var password_inputs = Array();
for(var i in inputs) {
if(inputs[i].type == 'password') {
password_inputs.push(inputs[i]);
}
}
for(var i in password_inputs) {
var input = inputs[i];
...
}
}
}

Ancora una volta, il nostro primo task fa una scansione della pagina cercando i password input così che noi si possa poi modificare il loro comportamento. Tuttavia, c'è una differenza sostanziale tra questa funzione e quella del primo esempio: in Internet Explorer, document.getElementsByTagName() non ritorna una semplice lista di elementi che vanno bene nel momento in cui lo script parte, ma ritorna un riferimento alla collezione di elementi che vanno bene. Se creiamo un nuovo elemento input mentre facciamo il loop sui risultati, aumenteremmo la dimensione di tale collezione ad ogni passo, ed il loop continuerebbe all'infinito. Questo fa bloccare istantaneamente Internet Explorer (e senza grazia!). Quindi, abbiamo bisogno di copiare i risultati iniziali della funzione in un array e fare un loop su questo.

var masking_element = document.createElement('input');
with(masking_element) {
style.position = 'absolute';
id = input.name + '_mask';
type = 'text';
size = input.size;
className = input.className;
}
masking_element.onfocus = function(){this.nextSibling.focus()};
input.parentNode.insertBefore(masking_element, input);

Con il nuovo input inserito direttamente prima di quello esistente, mettere la sua position ad absolute dovrebbe piazzarlo direttamente sopra. Dovrebbe funzionare nella maggior parte dei layout, ma ci possono essere delle eccezioni dove dei CSS aggiuntivi sono richiesti per posizionarlo correttamente. Ovviamente, ora che stiamo comprendo l'input con un altro elemento, abbiamo anche bisogno di essere sicuri che cliccando sulla maschera si attivi l'input. l'aggiunta di un handler onfocus sistema questo aspetto. Dobbiamo mettere questo handler all'esterno della dichiarazione with affinché funzioni correttamente in Firefox 2.

input.onchange = function() {
...
}
input.onkeyup = input.onchange;

Con il nuovo elemento al suo posto, costruiremo una funzione che mostri il testo della password progressivamente nascosto. Abbiamo bisogno che questo testo risponda ai cambiamenti nel contesto del campo password. Solitamente, questo implica che un utente sta scrivendo con una tastiera, ma potrebbe benissimo non essere così. Qualcuno potrebbe copiare il testo nel campo usando un menu contestuale, per esempio. Attaccare il nostro codice sia all'evento change sia all'evento keyup dovrebbe coprire tutte le situazioni.

var mask_character = "\u2022";
var last_character = this.value.charAt(this.value.length-1);

Possiamo designare qualunque carattere ci piaccia per mascherare le password. Tradizionalmente, la maggior parte dei sistemi usa asterischi o pallini, così in questo esempio abbiamo definito l'entità Unicode 2022, ossia il carattere del punto elenco come carattere che nasconderà la password. Sulla seconda linea, identifichiamo l'ultimo carattere dell'attuale valore della password, così da poterlo rendere in chiaro.

var masked_text    = this.previousSibling.value;
var password_text = this.value;

if(masked_text.length < password_text.length) {
this.previousSibling.value = password_text.substr(0,
password_text.length-1).replace(/./g, »
mask_character)+last_character;
} else {
this.previousSibling.value = password_text.replace(/./g,
mask_character);
}

Adesso possiamo prendere il valore del campo password, rimpiazzare ogni carattere con un bullet (carattere punto elenco) tranne l'ultimo e mettere quel testo nel campo che stiamo usando come maschera. Comunque, possiamo farlo solo mentre una persona sta inserendo i caratteri procedendo in avanti. In altre parole, se l'utente schiaccia il tasto backspace (delete) non riveliamo un'altra volta il carattere precedente. Una volta nascosto, il carattere dovrebbe rimanere nascosto. Così, prima di fare la sostituzione del carattere, dobbiamo controllare per verificare che il valore della password non sia più lungo del testo oscurato. La sostituzione stessa può essere fatta usando una semplice espressione regolare. L'espressione /./ eguaglia ciascun carattere del campo password. Aggiungendo al lettera g alla fine, (/./g) viene fatta una scansione dell'intera stringa di testo invece che fermarsi al primo che eguaglia.

this.timer = setTimeout("with(document.getElementById('"+
masking_element.id+"')){value=value.replace(/./g,'"+
mask_character+"')}",2000);

Dopo un ritardo di due secondi, vogliamo che l'intera password sia oscurata. Tuttavia, i nostri utenti probabilmente non si fermeranno per due secondi dopo aver inserito ogni singola lettera. Pertanto vogliamo che il comportamento auspicato abbia effetto solo quando il campo password non è cambiato affatto per tale intervallo di tempo. Ogni volta che chiamiamo la funzione setTimeout in JavaScript, ci ritorna come risultato un ID che possiamo usare per referenziare quel particolare timer.

if(this.timer){
clearTimeout(this.timer);
}

Memorizzando l'ID del timer in una variabile ed aggiungendo il codice di cui sopra all'inizio della nostra funzione, possiamo cancellare il countdown fintanto che osserviamo che il campo sta cambiando.

input.onchange();

L'ultimo step consiste nel far girare la funzione che abbiamo appena definito. Questo assicura che la maschera mostrerà il testo corretto se il campo password era stato riempito in anticipo prima che la pagina si caricasse.

Per vedere l'intero script in azione, guardate l'esempio due. E' stato testato in Internet Explorer 6-8, Firefox, Safari e Chrome. Ribadisco che in assenza di JavaScript questa tecnica degrada bene: il campo password semplicemente funzionerà in maniera normale.

Procedete con cautela

Quando si ha a che fare con un'area così importante dell'esperienza web, dobbiamo prestare molta attenzione perché abbiamo a che fare con aspettative molto radicate. Il metodo username/password usato per mettere in sicurezza le applicazioni web non è perfetto, ma ci sono poche buone alternative ed è diventato un approccio standard. Possiamo affrontare meglio le preoccupazioni riguardanti l'usabilità dei campi password testando dei cambi incrementali per estendere il comportamento di default, senza compromettere le basi dell'esperienza e non perdere la fiducia degli utenti.

Pubblicato in: Browsers e Scripting.

Share/Save/Bookmark
 

Discutiamone

Ti sembra interessante? Scrivi tu il primo commento


Cenni sull'autore

Lyle Mullican

foto di Lyle MullicanLyle Mullican è un web solutions architect presso la Mayo Clinic in Rochester, Minnesota, dove realizza applicazioni web per i professionisti di laboratorio. Potete trovarlo sui soliti social networks. In passato, Lyle ha gestito le operazioni di una azienda di consulenze di sviluppo web. Ha due bambini piccoli e di tanto in tanto produce delle lavori artistici. Tutti i punti di vista espressi sono, ovviamente, solo dell'autore.