https://d226lax1qjow5r.cloudfront.net/blog/blogposts/building-a-check-in-app-with-nexmos-verify-api-dr/Build-a-Check-In-App_1200x675.jpg

Erstellen einer Check-In App mit Nexmo's Verify API und Koa.js

Zuletzt aktualisiert am April 26, 2021

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 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 GlitchStarting 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 accountSign 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 projectDefault 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 GlitchTools 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 GlitchViewing 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 consoleAccessing the Glitch console

Entfernen Sie express mit folgendem Befehl:

pnpm uninstall express

Installieren Sie dann Koa.js, indem Sie Folgendes ausführen:

pnpm install koa --save

Um die npm-Module zu verifizieren, die in Ihrem Projekt verwendet werden, müssen Sie die Umgebung aktualisieren:

refresh

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 fineCheck 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 pageRough 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-static für die Bereitstellung statischer Inhalte

  • koa-bodyparser für die Behandlung von Daten, die über POST-Anfragen gesendet werden

  • koa-router für Routing

  • koa-views zum Rendern von nunjucks-Vorlagen (erfordert ebenfalls die Installation von nunjucks)

  • koa-session fü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 frontendDisplaying 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 numberTrigger 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' phonesWeb 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 userVerification 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 Pulau Perhentian

Die Version, die auf Glitch deckt die 2 oben genannten Szenarien ab, du kannst sie also gerne ausprobieren und neu mischen.

remix this

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:

Teilen Sie:

https://a.storyblok.com/f/270183/384x384/46621147f0/huijing.png
Hui Jing ChenVonage Ehemalige

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.