
Teilen Sie:
Hui Jing ist Developer Advocate bei Nexmo. Sie hat eine übermäßige Liebe zu CSS und Typografie und ist allgemein leidenschaftlich über alle Dinge im Web.
Erstellen einer Check-In App mit Nexmo's Verify API und Koa.js
Lesedauer: 12 Minuten
Nehmen wir an, Sie betreiben ein Spiel wie The Amazing Race, bei dem es mehrere Kontrollpunkte gibt, die die Spieler physisch erreichen müssen, bevor sie das Spiel beenden können. Mit dieser Anwendung können Sie verfolgen, ob ein Spieler einen Kontrollpunkt erreicht hat oder nicht.
Ein bisschen was über 2FA
An einem typischen Zwei-Faktor-Authentifizierungsablauf ist nur eine Partei beteiligt. Sie geben ihre Handynummer ein, um eine SMS mit dem Verifizierungscode zu erhalten, und geben dann diesen Code in die Benutzeroberfläche ein, um ihre Identität zu bestätigen. Einfach-einfach-einfach-einfach.
Etwas interessanter wird es, wenn Sie die Sache zu einer Angelegenheit für zwei Parteien machen wollen. Ein Checkpoint-Administrator verfügt über eine Liste mit allen Spielern und den entsprechenden Telefonnummern. Wenn die Spieler den Kontrollpunkt erreichen, löst der Administrator einen Verifizierungscode aus, der an das Telefon des Spielers gesendet wird.
Der Spieler gibt dann diesen Verifizierungscode über ein Online-Webformular ein oder antwortet per SMS, um seine Anwesenheit am Kontrollpunkt zu bestätigen. Da der Administrator keine Möglichkeit hat, auf den Verifizierungscode zuzugreifen, kann er theoretisch keine Spieler verifizieren, die nicht am Kontrollpunkt anwesend sind.
Bevor Sie auf die vielen Möglichkeiten hinweisen, wie Spieler immer noch mit Administratoren kollidieren können, möchte ich Ihnen versichern, dass ich mir dessen bewusst bin, aber dies ist ein MVP und wir werden die Betrugsprävention (und eine Vielzahl anderer Funktionen) in zukünftigen Versionen wieder aufgreifen. Vielleicht.
Verwendete Bibliotheken
Nexmos Verify API
Nexmo's Messages API
Koa.js (und relevante Middleware)
Nexmos Verify API wird normalerweise für die Zwei-Faktor-Authentifizierung oder für die passwortlose Authentifizierung verwendet. Anstatt selbst eine sichere OTP-Generierungsfunktion zu schreiben, habe ich stattdessen diese für meine Verifizierungscodes verwendet.
Koa.js ist das Framework hinter der Anwendung, für die Bereitstellung, das Routing, die Bearbeitung von API-Anfragen und -Antworten usw. Da das Koa.js-Kernframework recht einfach aufgebaut ist, müssen bei Bedarf verschiedene Middlewares hinzugefügt werden.
Nunjucks ist die Template-Engine für die Darstellung von Daten auf dem Frontend, während lowdb eine sehr einfache JSON-Datenbank ist, die sich hervorragend für Prototypen wie diese Anwendung eignet. Alle datenbankbezogenen Funktionen können leicht gegen eine andere, "ernstere" Datenbank ausgetauscht werden.
Vonage API-Konto
Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.
Eine einfache Koa.js-Anwendung auf Glitch
Wenn Sie bereits mit Glitchbenutzen, bitte überspringe dies alles. Für alle, die die erstaunliche Plattform Glitch noch nicht entdeckt haben: Wenn Sie das erste Mal landen, können Sie wählen, welche Art von Projekt Sie erstellen möchten. Es gibt 3 Voreinstellungen, eine einfache Website (ohne Backend), eine Node-Anwendung und eine Node-Anwendung mit einer SQlite-Datenbank. Für diese Demo können Sie sich für die zweite Option entscheiden.
Starting a new Node project on Glitch
Um sicherzustellen, dass Ihr Projekt bestehen bleibt, sollten Sie sich für einen Glitch Account anmelden. Glitch hat ziemlich häufig Verbesserungen an den Funktionen vorgenommen. Das kann sich also ändern, wenn Sie in ferner Zukunft lesen, aber zum Zeitpunkt der Erstellung dieses Artikels unterstützen sie die Anmeldung über Facebook, GitHub, E-Mail oder einen Anmeldecode.
Sign in to a Glitch account
Standardmäßig laufen Node Applications auf Glitch auf Express, was völlig in Ordnung ist. Dieses spezielle Projekt verwendet Koa.js, so gibt es ein paar mehr Schritte zu gehen für diese.
Default package.json on a fresh Glitch Node project
Wenn Sie unten links auf dem Bildschirm auf Tools klicken, werden einige Optionen wie Logs, Console, Container Stats usw. angezeigt.
Tools options on Glitch
Logs ist sehr nützlich, wenn Sie Ihre Anwendung entwickeln, denn hier wird alles angezeigt, was Sie console.log() hier auftaucht.
Viewing logs on Glitch
Um die npm-Module, die Sie in Ihrem Projekt verwenden möchten, anzupassen, können Sie auf die Kommandozeile zugreifen, wie Sie es von Ihrem lokalen Rechner oder einem Remote-Server gewohnt sind. Zu beachten ist, dass anstelle von npmverwendet Glitch pnpm als Paketmanager verwendet.
Accessing the Glitch console
Entfernen Sie express mit folgendem Befehl:
Installieren Sie dann Koa.js, indem Sie Folgendes ausführen:
Um die npm-Module zu verifizieren, die in Ihrem Projekt verwendet werden, müssen Sie die Umgebung aktualisieren:
Sobald Sie das getan haben, sollten Sie einen "Fehler"-Indikator neben Tools sehen. Das ist in Ordnung, denn in der server.js Datei benötigen wir das Express-Framework, das nicht mehr vorhanden ist.
Als Nächstes müssen Sie den grundlegenden Servercode umschreiben, um Koa.js zu verwenden. Sie können dies selbst tun oder den folgenden Code in Ihre neu erstellte Datei einfügen.
const Koa = require('koa')
const port = process.env.PORT || 3000
const app = new Koa()
app.use(async ctx => {
ctx.body = 'Hello Dinosaur 🦖'
})
const listener = app.listen(port, function() {
console.log('Your app is listening on port ' + listener.address().port)
})
Wenn alles gut gelaufen ist, sollte Ihre Anwendung durch Klicken auf die Schaltfläche "Zeigen" in der oberen Navigationsleiste in einem neuen Fenster mit dem Text "Hallo Dinosaurier 🦖" angezeigt werden.
Check that Koa.js is running fine
Die Struktur der Anwendung
Von Rechts wegen sollte es eine angemessene Benutzerverwaltung für Administratoren geben. Wenn Sie auf die Live-Demo auf Glitch zugreifen, werden Sie sehen, dass es einen gewissen Anschein von Passwortschutz gibt, aber es ist eine sehr naive Implementierung für diesen einfachen Prototyp. Der Prototyp zeigt lediglich die Idee, wie die Schnittstelle funktionieren würde.
Das bedeutet, dass die Anwendung 3 Seiten haben wird: die Anmeldeseite, die Administratorseite und die Seite zur Eingabe des Verifizierungscodes.
Rough screen sketches for login page, administrator page and verification code entry page
Wie bereits erwähnt, müssen einige zusätzliche Middlewares installiert werden. Dieses Projekt verwendet die folgenden:
koa-staticfür die Bereitstellung statischer Inhaltekoa-bodyparserfür die Behandlung von Daten, die über POST-Anfragen gesendet werdenkoa-routerfür Routingkoa-viewszum Rendern von nunjucks-Vorlagen (erfordert ebenfalls die Installation von nunjucks)koa-sessionfür einfachen Passwortschutz (nicht für die Produktion)
Statische Assets bereitstellen
const serve = require('koa-static')
app.use(serve('./public'))Dies ist wahrscheinlich der am wenigsten komplizierte Teil, den man abdecken muss, nämlich die Bereitstellung von statischen Assets wie CSS und clientseitigem Javascript von der /public Ordner.
Grundlegendes Routing und Rendering
Der Plan ist, alles ohne client-seitiges Javascript zum Laufen zu bringen. Benutzereingaben werden also über HTML-Formulare übermittelt, und wenn nötig, wird nach der Übermittlung eine Weiterleitung auf die entsprechende Seite vorgenommen. Jede Seite ist eine eigene HTML-Datei und wird mit koa-viewsgerendert, die eine render() Funktion bietet.
const Router = require('koa-router')
const views = require('koa-views')
const router = new Router()
app.use(views('./views', { map: { html: 'nunjucks' }}))
router.get('/login', (ctx, next) => {
return ctx.render('./login')
})
router.get('/', (ctx, next) => {
return ctx.render('./index')
})
router.get('/verify/:phone', (ctx, next) => {
const phone = ctx.params.phone
return ctx.render('./verify')
})
router.get('/result/:phone', (ctx, next) => {
const phone = ctx.params.phone
return ctx.render('./result')
})
koa-router bietet auch eine Möglichkeit, auf URL-Parameter über ctx.paramszugreifen, die für */verify/* und /result/**, die verwendet werden, um Telefonnummern mit dem generierten Verifizierungscode abzugleichen. Dies macht mehr Sinn, wenn wir uns die Funktionsweise der Verify API ansehen.
Verify API
Bei der Verwendung der Verify API sind 2 Schritte erforderlich. Der erste besteht darin, eine SMS mit dem Einmalpasswort (OTP) an die Telefonnummer des Empfängers zu senden.
nexmo.verify.request({
number: RECIPIENT_NUMBER,
brand: NEXMO_BRAND_NAME
}, (err, result) => {
if (err) {
console.error(err)
} else {
const verifyRequestId = result.request_id
console.log('request_id', verifyRequestId)
}
})
Die HTTP-Antwort der Verify API sieht wie folgt aus:
{
"request_id": "aaaaaaaa-bbbb-...",
"status": "0",
"error_text": "error"
}Wenn die status einen anderen Wert als 0ist, war die Anfrage erfolglos, und die Gründe dafür finden Sie unter error_text. Andernfalls sollte der Empfänger ein OTP per SMS erhalten. Er muss dann dieses OTP in Ihre Anwendung eingeben, die mit dem Original verifiziert werden kann request_id.
nexmo.verify.check({
request_id: REQUEST_ID,
code: CODE
}, (err, result) => {
if (err) {
console.error(err)
} else {
console.log(result)
}
})
Die HTTP-Antwort von der Verify API sieht folgendermaßen aus:
{
"request_id": "aaaaaaaa-bbbb-...",
"event_id": "0A00000012345678",
"status": "0",
"price": "0.10000000",
"currency": "EUR",
"error_text": "error"
} Umgang mit mehreren Spielern
Der Checkpoint-Administrator muss den Überblick über alle Spieler behalten, daher könnte es eine gute Idee sein, eine Datenbank zu verwenden, um diese Informationen zu speichern. Diese Demo verwendet lowdb, eine kleine lokale JSON-Datenbank, die auf Lodash basiert, aber Sie können natürlich jede beliebige Datenbank verwenden.
Es gibt eine Reihe von datenbankbezogenen Funktionen zum Hinzufügen von Spielern, zum Abrufen von Informationen über Spieler und zum Aktualisieren von Spielerinformationen. Wenn Sie die Funktionen auf diese Weise organisieren, wird es einfacher, Datenbanken auszutauschen, da die Logik immer gleich bleibt.
function dbAddPlayer(data) {
db.get('players')
.push({ name: data.name, phone: data.phone })
.write()
console.log('New user inserted in the database')
}
function dbGetPlayers() {
return db.get('players').value()
}
function dbPlayerCount() {
return db.get('players').size().value()
}
function dbAddId(phone, requestId, mode, status) {
db.get('players')
.find({ phone: phone })
.assign({ id: requestId, delivery: mode, status: status })
.write()
}
function dbUpdateStatus(requestId, status) {
db.get('players')
.find({ id: requestId })
.assign({ status: status })
.write()
}
function dbFindPlayer(phone) {
return db.get('players').find({ phone: phone }).value()
}
function dbClear() {
db.get('players')
.remove()
.write()
console.log('Database cleared')
}Sie können ein Webformular verwenden, um die erforderlichen Informationen für die Erstellung eines neuen Spielers zu erfassen. Für diese Demo sind nur 2 Felder erforderlich: der Name des Spielers und seine Telefonnummer.
<form id="addPlayerForm" action="add" method="post">
<h2>Add player</h2>
<div class="inputs">
<label>
<span>Name</span>
<input name="name" required>
</label>
<label>
<span>Phone</span>
<input type="tel" name="phone" required>
</label>
</div>
<button id="addPlayer">Add</button>
</form>
Das Absenden dieses Formulars sendet eine POST-Anfrage an /addgesendet, was bedeutet, dass Sie eine Route erstellen müssen, um diese eingehenden Daten zu verarbeiten und sie dann in der Datenbank zu speichern.
router.post('/add', (ctx, next) => {
const payload = ctx.request.body
dbAddPlayer(payload)
ctx.status = 200
ctx.response.redirect('/')
})
Sie können dann mit Nunjucks Informationen aus der Datenbank auf der Seite darstellen. Nunjucks bietet eine render() Funktion, mit der Sie Daten an Ihre Nunjucks-Vorlage übergeben können. Ändern Sie die GET Route für / so, dass Sie Spielerinformationen aus der Datenbank auf der Seite darstellen können.
router.get('/', (ctx, next) => {
const players = dbGetPlayers()
return ctx.render('./index', { players: players })
})
Sie können dann Spielerwerte in die Vorlage einfügen und sie so darstellen, wie Sie Ihre Spielerdaten strukturiert haben möchten.
Displaying player data on the frontend
Auslösen des OTP
Die Verify API benötigt die Telefonnummer des Spielers, um die OTP-Anforderung auszulösen. Sie können diese Informationen über ein weiteres Webformular an das Backend senden, dieses Mal an */verify/{{ player.phone }}*Damit können Sie die Telefonnummer über ctx.params.phone abrufen und an die Anforderungsfunktion der Verify API übergeben können.
router.post('/verify/:phone', async (ctx, next) => {
const phone = ctx.params.phone
const result = await verify(phone)
dbAddId(phone, result.request_id, payload.delivery, 'pending')
ctx.status = 200
ctx.response.redirect('/')
})
// Verify API's request function
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)
}
})
})
}
// Add request ID to the database
function dbAddId(phone, requestId, mode, status) {
db.get('players')
.find({ phone: phone })
.assign({ id: requestId, delivery: mode, status: status })
.write()
}
Die request_iddie Teil der API-Antwort ist, wird für die nachfolgende check() Funktion benötigt. Diese Funktion dient zur Verifizierung des request_id mit dem vom Spieler eingegebenen Code. Dieser wird in der Datenbank über die dbAddId() Funktion gespeichert.
Trigger the OTP for each phone number
Überprüfen des OTP
Die Spieler können den Verifizierungscode unter der eindeutigen URL eingeben, */verify/{{ player.phone }}*. Sie müssen die Telefonnummer an die Seite zurückgeben, damit Ihre GET Route der folgenden ähneln würde /ähnelt, die die render() Funktion enthält.
router.get('/verify/:phone', (ctx, next) => {
const phone = ctx.params.phone
return ctx.render('./verify', { phone: phone })
})
Das Webformular, in das die Spieler ihr OTP eingeben können, sieht etwa so aus:
<form method="post" action="/check" id="checkPinForm">
<input name="pin" type="number">
<input type="hidden" name="phone" value="{{ phone }}">
<button>Submit</button>
</form>
Web form for entering the OTP sent to players' phones
Wenn der Spieler das OTP abgibt, können Sie das Original aus der Datenbank abrufen request_id aus der Datenbank abrufen und sowohl die request_id und die PIN an die Verify API check() Funktion übergeben.
router.post('/check', async (ctx, next) => {
const payload = await ctx.request.body
const phone = payload.phone
const code = payload.pin
const requestId = dbFindPlayer(phone).id
const result = await check(requestId, code)
dbUpdateStatus(requestId, result.status)
ctx.status = 200
ctx.response.redirect('/result/' + payload.phone)
})
// Verify API's check function
async function check(requestId, code) {
return new Promise(function(resolve, reject) {
nexmo.verify.check({
request_id: requestId,
code: code
}, (err, result) => {
if (err) {
console.error(err)
reject(err)
} else {
resolve(result)
}
})
})
}
// Update player status in the database
function dbUpdateStatus(requestId, status) {
db.get('players')
.find({ id: requestId })
.assign({ status: status })
.write()
}
Eine erfolgreiche Überprüfung liefert einen Statuscode von 0zurück, den Sie verwenden können, um zu bestimmen, welche Statusmeldung dem Benutzer auf der Ergebnisseite angezeigt werden soll.
<main>{% raw %}
{% if status == 0 %}
<p>Code verified successfully.</p>
<p>You can close this window now.</p>
{% else %}
<p>Something went wrong…</p>
<p>Please contact the administrator for more information.</p>
{% endif %}{% endraw %}
</main>
Verification results page displayed to the user
Zusätzliche Dinge, die Sie tun können
Nexmo's Messages API kann verwendet werden, um zusätzliche Funktionen zu diesem Projekt hinzuzufügen. Sie könnten z.B. eine zusätzliche SMS mit dem Link zur Verifizierungsseite auslösen, um die Benutzerfreundlichkeit zu erhöhen.
Oder vielleicht findet Ihr Spiel an einem ziemlich abgelegenen Ort statt, zum Beispiel Pulau Perhentian (siehe Bild unten), oder Ihre Spieler haben aus irgendeinem Grund keine mobile Datenverbindung. Anstatt den Code über eine Weboberfläche zu überprüfen, können die Spieler per SMS antworten.
Pulau Perhentian
Die Version, die auf Glitch deckt die 2 oben genannten Szenarien ab, du kannst sie also gerne ausprobieren und neu mischen.

Dieser Prototyp ist eine Demonstration dessen, was mit der Verify API möglich ist, aber auch hier gibt es zahlreiche Dinge, die beachtet werden müssen, damit daraus eine vollwertige Anwendung wird. Einer der wichtigsten Punkte ist die Fehlerbehandlung. Die Verify API gibt einen Statuswert von 0 für erfolgreiche Abfragen zurück, aber jeder andere Wert bedeutet einen Fehler.
Diese Fehler sollten behandelt werden, und die Benutzeroberfläche am Frontend sollte alle potenziellen Fehler, die eine erfolgreiche Überprüfung verhindern, widerspiegeln. Es könnte auch eine gute Idee sein, eine Art von Frontend-Validierung zu implementieren, oder sogar Nexmos Number Insight API um sicherzustellen, dass nur gültige Numbers an die Verify API übergeben werden.
Wie geht es weiter?
Wenn Sie mehr mit diesen APIs machen wollen, finden Sie hier einige Links, die Ihnen helfen könnten:
Dokumentation für die Verify API auf dem Entwicklerportal
Reihe von Anleitungen für verschiedene Nexmo APIs
Wenn Sie uns brauchen, versuchen Sie den Nexmo Community Slack-Kanal
Lassen Sie uns wissen, was Sie denken, indem Sie tweeten unter @NexmoDev
