L’autenticazione a due fattori (2FA) prende il nome dal fatto che per verificare la tua identità sono richiesti due elementi: qualcosa che conosci – come una password – e qualcosa che hai, come un codice di verifica sul tuo dispositivo mobile, o un token fisico.
Aggiungere la 2FA alla tua app non è un compito difficile. Questo tutorial ti mostrerà come implementare la 2FA nelle tue applicazioni web con l’aiuto dell’API Vonage Verify, ottenendo così un ulteriore livello di sicurezza. Costruiremo una semplice applicazione in Koa.js, per capire come funziona il meccanismo di base. In questo modo saprai come adattarlo ai tuoi progetti esistenti, anche se non stai usando Koa.js.
Questo tutorial ti mostrerà come implementare un sistema di token di verifica con l’API Vonage Verify e Koa.js. Esiste un tutorial simile che utilizza Node.js ed Express.js – puoi trovarlo qui – in inglese.
Inizialmente, una pagina di login chiederà al tuo utente un numero di cellulare. Dopodiché, gli verrà chiesto di inserire il codice di verifica che gli sarà inviato tramite SMS. Una volta inserito, l’utente potrà accedere all’applicazione.
Prerequisiti
- Una conoscenza di base di Javascript
- Node.js installato sul tuo computer
Vonage API Account
Per completare questo tutorial avrai bisogno di un account Vonage API. Se non ne hai già uno, puoi registrarti ora e iniziare subito a sviluppare, grazie al credito gratuito. Una volta creato l’account, troverai la tua API Key e il tuo API Secret nella parte superiore della Vonage API dashboard.
Questo tutorial ti guiderà attraverso l’intero processo partendo da zero. Se desideri vedere il codice finito, puoi clonare il repository git. Abbiamo anche una versione Glitch, che ha un design più ricercato, e che potrai anche remixare. Considera che nell’implementazione Glitch ci sono lievi differenze, dovute al modo in cui i progetti sono salvati su questa piattaforma.
Inizio di un progetto Koa.js da zero
Crea una nuova cartella sul tuo computer, quindi esegui il seguente comando per configurare un nuovo progetto Node.js:
1 2 |
npm init |
Questo attiverà una serie di prompt che genereranno il file package.json
. Puoi scegliere di lasciare le risposte vuote e utilizzare i valori predefiniti, se lo desideri.
Poi, installa Koa.js. Tieni presente che Koa richiede Node v7.6.0 (o superiore) per ES2015 e il supporto della funzione async.
1 2 |
npm install koa --save |
Crea un file server.js
nella cartella del progetto.
1 2 |
touch server.js |
Incolla il codice seguente nel file che hai appena creato.
1 2 3 4 5 6 7 8 9 10 11 12 |
const Koa = require('koa') const port = process.env.PORT || 3000 const app = new Koa() app.use(async ctx => { ctx.body = 'Hello Unicorn 🦄' }) const listener = app.listen(port, function() { console.log('Your app is listening on port ' + listener.address().port) }) |
Esegui il file server.js
.
1 2 |
node server.js |
Se accedi a http://localhost:3000
dal tuo browser, dovresti vedere una pagina vuota con il testo “Hello Unicorn 🦄”.
È inoltre necessario installare dotenv, che consente di caricare le variabili di ambiente memorizzate in un file .env
nel process.env
.
1 2 |
npm install dotenv --save |
E ora puoi creare il file .env
, che dovrebbe contenere almeno le seguenti variabili:
1 2 3 |
NEXMO_API_KEY='' NEXMO_API_SECRET='' |
Per accedere alle variabili d’ambiente, dovrai inviare una richiesta, idealmente all’inizio del tuo file server.js
.
1 2 |
require('dotenv').config() |
Struttura del progetto
In questo momento, il tuo progetto probabilmente è costituito solo da un package.json
, un file server.js
e un file .env
. Impostiamo ora la struttura del progetto in modo da poter avere un frontend di base con cui gli utenti possono interagire.
1 2 3 4 5 6 7 8 9 10 |
PROJECT_NAME/ |-- public/ | |-- client.js | `-- style.css |-- views/ | `-- index.html |-- .env |-- package.json `-- server.js |
Fatto ciò, dovrai apportare alcune modifiche al file server.js
per servire il file index.html
e le risorse correlate, invece che semplicemente una riga di testo. Koa.js è un framework abbastanza basilare, quindi qualsiasi funzionalità aggiuntiva per il routing o il servizio di risorse statiche deve essere installata separatamente. Ecco l’elenco dei moduli aggiuntivi e dei loro usi:
– koa-static
for serving static assets
– koa-bodyparser
for handling data sent over via POST requests
– koa-router
for routing
– koa-views
to render templates
Questo esempio utilizza anche Nunjucks per il rendering dei file modello. L’API Vonage Verify verrà utilizzata per attivare il codice di verifica tramite SMS, quindi sarà necessario installare anche la library Vonage Node.js SDK.
1 2 |
npm install koa-static koa-bodyparser koa-router koa-views nunjucks nexmo --save |
Servire asset statici e file HTML
Per consentire all’applicazione di servire asset statici come i fogli di stile e il Javascript lato client, fuori dalla cartella /public , puoi aggiungere quanto segue al file server.js
:
1 2 3 |
const serve = require('koa-static') app.use(serve('./public')) |
Per servire i file HTML fuori dalla cartella /views, puoi usare koa-views
, che fornisce una funzione render()
. Il template engine utilizzato in questo esempio è Nunjucks, ma puoi scegliere quello che funziona meglio per te.
1 2 3 |
const views = require('koa-views') app.use(views('./views', { map: { html: 'nunjucks' }})) |
Il prossimo step è impostare alcune routes di base per servire le pagine dell’applicazione.
1 2 3 4 5 6 7 8 9 |
const Router = require('koa-router') const router = new Router() router.get('/', (ctx, next) => { return ctx.render('./index') }) app.use(router.routes()).use(router.allowedMethods()) |
Per questo esempio, avrai bisogno di 3 pagine: index.html
come pagina di destinazione principale, verify.html
per consentire agli utenti di inserire il loro codice di verifica e result.html
per mostrare se la verifica è andata a buon fine oppure no.
La struttura del modulo è abbastanza semplice. Ovviamente sei libero di abbellirla con l’uso dei CSS.
1 2 3 4 5 |
<form method="post" action="verify"> <input name="phone" type="tel" placeholder="+6588888888"> <button>Get OTP</button> </form> |
Questo modulo pubblicherà gli input dell’utente nella route /verify
; potrai utilizzare il numero di telefono nell’input per attivare la richiesta del codice di verifica. Un modulo simile può essere utilizzato anche per le altre 2 routes /check
e /cancel
.
1 2 3 4 5 6 |
<form method="post" action="check"> <input name="pin" placeholder="Enter PIN"> <input name="reqId" type="hidden" value="{{ reqId }}"> <button>Verify</button> </form> |
1 2 3 4 5 |
<form method="post" action="cancel"> <input name="reqId" type="hidden" value="{{ reqId }}"> <button class="inline">Cancel verification</button> </form> |
Gestione degli input dell’utente
Quindi, per gestire gli input degli utenti tramite i moduli web, avrai bisogno di alcune routes per gestire anche le richieste POST
. Assicurati di dichiarare bodyparser()
prima di ogni route.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const bodyParser = require('koa-bodyparser') /* Questo dovrebbe apparire prima di ogni route */ app.use(bodyParser()) router.post('/verify/', async (ctx, next) => { const payload = await ctx.request.body /* Qui va la funzione per attivare il codice di verifica */ }) router.post('/check/', async (ctx, next) => { const payload = await ctx.request.body /* Qui va la funzione per controllare il codice di verifica */ }) router.post('/cancel/', async (ctx, next) => { const payload = await ctx.request.body /* Qui va la funzione per cancellare il codice di verifica */ }) |
Ora che sei in grado di ricevere il numero di telefono del tuo utente, dovrai utilizzare la Verify API per inviargli un codice PIN. Avvia una nuova istanza Nexmo con le tue credenziali API Vonage.
1 2 3 4 5 6 |
const Nexmo = require('nexmo'); const nexmo = new Nexmo({ apiKey: YOUR_API_KEY, apiSecret: YOUR_API_SECRET }); |
Ci sono 3 funzioni di cui dobbiamo occuparci. La prima è attivare il codice di verifica con la funzione nexmo.verify.request()
. Questa richiede il numero di telefono dell’utente e una stringa per il brand name che verrà visualizzato dall’utente come mittente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
async function verify(number) { return new Promise(function(resolve, reject) { nexmo.verify.request({ number: number, brand: process.env.NEXMO_BRAND_NAME }, (err, result) => { if (err) { console.error(err) reject(err) } else { resolve(result) } }) }) } |
Una volta che l’utente ha ricevuto il codice PIN tramite SMS, dovrà inviarlo alla funzione nexmo.verify.check()
, in modo che possa essere verificato. Noterai un parametro request_id
. Questo valore si ottiene quando il codice PIN è stato attivato con successo. Esistono diversi modi per passare l’ID della richiesta alla funzione nexmo.verify.check()
; questo esempio utilizza un campo nascosto nel modulo di controllo (check).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
async function check(reqId, code) { return new Promise(function(resolve, reject) { nexmo.verify.check({ request_id: reqId, code: code }, (err, result) => { if (err) { console.error(err) reject(err) } else { resolve(result) } }) }) } |
L’ultima funzione offre all’utente la possibilità di annullare la verifica qualora avesse cambiato idea. Questa utilizza la funzione nexmo.verify.control()
e, ancora una volta, richiede l’ID richiesta generato dall’attivazione del codice PIN e un valore stringa cancel
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
async function cancel(reqId) { return new Promise(function(resolve, reject) { nexmo.verify.control({ request_id: reqId, cmd: 'cancel' }, (err, result) => { if (err) { console.error(err) reject(err) } else { resolve(result) } }) }) } |
Ora è necessario utilizzare queste 3 funzioni nelle routes che abbiamo specificato in precedenza, iniziando da quella che serve per attivare il codice di verifica.
1 2 3 4 5 6 7 8 9 10 |
router.post('/verify/', async (ctx, next) => { const payload = await ctx.request.body const phone = payload.phone const result = await verify(phone) const reqId = result.request_id ctx.status = 200 return ctx.render('./verify', { reqId: reqId }) }) |
Il ctx.request.body
sarà più o meno così:
1 2 |
{ phone: '+40987654321' } |
Puoi prendere il numero di telefono e passarlo alla funzione verify()
. Finché si tratta di un numero di telefono valido, il codice di verifica verrà attivato e riceverai una risposta contenente un request_id
e status
.
1 2 3 4 5 |
{ request_id: '1bf002ecd1e94d8aa81ba7463b19f583', status: '0' } |
Da lì, puoi inviare l’ID della richiesta al frontend per utilizzarlo quando l’utente inserisce il codice di verifica.
Quando l’utente invia il PIN corretto, sarà necessario inserire sia il PIN che l’ID della richiesta nella funzione check()
.
1 2 3 4 5 6 7 8 9 10 11 |
router.post('/check/', async (ctx, next) => { const payload = await ctx.request.body const code = payload.pin const reqId = payload.reqId const result = await check(reqId, code) const status = result.status ctx.status = 200 return ctx.render('./result', { status: status }) }) |
Ancora una volta, entrambi questi valori possono essere ottenuti dal ctx.request.body
e se il PIN è validato come corretto, riceverai una risposta simile a questa:
1 2 3 4 5 6 |
{ request_id: '1bf002ecd1e94d8aa81ba7463b19f583', status: '0', event_id: '150000001AC57AB2', price: '0.10000000', currency: 'EUR' } |
È quindi possibile utilizzare il codice di status per determinare quale messaggio si desidera far visualizzare all’utente. Questo esempio utilizza Nunjucks, quindi il markup nella pagina dei risultati potrebbe essere simile a questo:
1 2 3 4 5 6 7 |
{% if status == 0 %} <p>Code verified successfully. ¯\_(ツ)_/¯</p> {% else %} <p>Something went wrong… ಠ_ಠ</p> <p>Please contact the administrator for more information.</p> {% endif %} |
In questo articolo abbiamo visto in maniera approfondita ogni parte del codice, ma se vuoi vedere come appare l’applicazione nella sua interezza, controlla il codice sorgente su GitHub.
Alcune cose da tenere in considerazione
Questo tutorial è una versione ridotta, che evidenzia solo i passi necessari per l’implementazione dell’autenticazione a due fattori. Ma ci sono molte cose di cui tener conto in un’applicazione reale. Uno dei più importanti è la gestione degli errori. La Verify API restituisce un valore di stato 0 per query riuscite, ma qualsiasi altro valore indica un errore.
Questi errori dovrebbero essere gestiti e l’interfaccia utente sul frontend dovrebbe riflettere tutti i potenziali errori che impediscono la corretta verifica. Potrebbe anche essere una buona idea implementare una sorta di convalida del frontend o persino utilizzare l’API Number Insight di Vonage per garantire che alla Verify API vengano passati solo numeri di telefono validi.
Saperne di più
Se desideri fare di più con queste API, ecco alcuni link che potrebbero esserti utili::
- Documentazione della Verify API sul portale per developers
- Serie di tutorials su diverse API di Vonage
- Se hai bisogno di noi, contattaci sul canale Slack Vonage Developer Community
- Facci sapere cosa pensi twittando a @VonageDev
Puoi leggere l’articolo originale in inglese qui How to Add Two-Factor Authentication with Node.js and Koa.js