
Teilen Sie:
Oluwatobi is a software developer and writer. He loves to simplify complex topics, making them easy for anyone to understand
Implementierung der Multi-Faktor-Authentifizierung in Go mit Verify
Lesedauer: 10 Minuten
Unabhängig davon, ob Sie an einem Projekt mit Millionen von Nutzern oder nur an einem kleinen Nebenprojekt arbeiten, ist es von größter Bedeutung, dass die von Ihnen erstellten Anwendungen sicher sind. Anwendungen, die nicht sicher sind, könnten Nutzerdaten an Hacker preisgeben, was zu einem Verlust von Geld und Vertrauen führt. Es ist die Aufgabe eines Softwareentwicklers, dafür zu sorgen, dass bei der Erstellung des Codes die Sicherheit an erster Stelle steht und alle losen Enden berücksichtigt werden. Eine Möglichkeit, die Sicherheit Ihrer Applications zu gewährleisten, ist die Integration eines Multi-Faktor-Authentifizierungssystems, allgemein als MFA bezeichnet.
Die Multi-Faktor-Authentifizierung wird verwendet, um Anwendungen zusätzliche Sicherheit zu verleihen und die Identität eines Benutzers zu verifizieren. In diesem Artikel lernen wir, wie man ein MFA-System mit der Programmiersprache Go und der Vonage Verify API.
Voraussetzungen
Um diesem Artikel folgen zu können, benötigen Sie Folgendes:
Go (Version 1.14 oder höher)
Go-Module aktiviert
Ein Texteditor Ihrer Wahl
Grundkenntnisse in Go
Vonage API-Konto
Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch kein Konto 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.
Projekt einrichten
Wir werden eine einfache Anwendung erstellen, die einen Verifizierungscode generiert und ihn an eine vom Benutzer angegebene Telefonnummer sendet. Der Benutzer gibt den Code auf einer Bestätigungsseite ein und die Anwendung bestätigt, ob er gültig ist.
App workflow
Das Bild oben ist eine grobe Skizze des Arbeitsablaufs für die Anwendung, die wir in diesem Artikel erstellen werden.
Erstellen Sie zunächst die Dateien und Ordner, die der unten stehenden Baumstruktur entsprechen:
├── static/ │ ├── index.html │ ├── form.html ├── .env ├── utils/ │ ├── verify.go └── server.go
Der statische Ordner enthält zwei HTML-Seiten (Formulare), die zur Erfassung der Telefonnummer bzw. des Bestätigungscodes des Benutzers verwendet werden.
Env-Dateien werden verwendet, um Umgebungsvariablen und Werte zu speichern, die geheim gehalten werden sollen. Die Datei .env Datei enthält den Vonage API-Schlüssel und das Geheimnis, die Sie über das Dashboard.
Das Verzeichnis utils Verzeichnis enthält eine verify.go Datei, die den gesamten Code enthält, der direkt mit der Vonage Verify API zusammenhängt. Die Datei server.go Datei im Stammverzeichnis unseres Projekts enthält die Logik in Bezug auf den Server, der die HTML-Dateien bereitstellt.
Konfigurieren Sie die Ansichten
Der erste Schritt bei der Erstellung unserer Webanwendung besteht darin, einen einfachen Webserver zu erstellen, der die HTML-Formulare ausgibt. In der Datei server.go Datei fügen Sie das Folgende ein:
func main() {
//Create the fileServer Handler
fileServer := http.FileServer(http.Dir("./static"))
//Create a New Serve Mux to register handler
mux := http.NewServeMux()
mux.Handle("/", fileServer)
//Create the server on Port 8080 and print start message!
fmt.Printf("Starting server at port 8080\n")
log.Fatal(http.ListenAndServe(":8080", mux))
}Wir importieren die net/http package und rufen seine FileServer Funktion auf und übergeben die Route zum static Verzeichnis als erstes Argument. Die Funktion http.FileServer Funktion gibt einen Handler zurück, den wir für ein URL-Muster registrieren. Um einen Handler zu registrieren, müssen wir einen neuen Serve-Mux mit der Funktion http.NewServeMux Funktion erstellen und seine Handle Methode aufrufen und dabei / als Muster zusammen mit dem Dateiserver-Handler, den wir oben erzeugt haben. Als nächstes erstellen wir den Server mit der http.ListenAndServe Methode. Wir übergeben den Port und den Serve-Mux, den wir oben erstellt haben, und geben dann eine Nachricht auf dem Terminal aus, um uns mitzuteilen, dass der Server in Betrieb ist und läuft.
Wenn Sie die Datei server.go Datei ausführen, erhalten Sie eine Startmeldung, die anzeigt, dass der Server gestartet wurde. Wenn Sie jedoch die Datei localhost:8080/ im Browser aufrufen, sehen Sie nur eine leere Seite. Das liegt daran, dass unsere index.html Datei leer ist.
In der index.html fileerstellen Sie ein Formular mit dem Titel Registrieren mit einem Eingabefeld für die Telefonnummer des Benutzers und einer Schaltfläche zum Absenden:
<form method="POST" action="/form">
<h2>Register</h2>
<label>Please Input your phone number:</label><br/>
<input name="phone" type="tel" required value="" >
<input type="submit" value="Submit">
</form>
Die Methode des Formulars ist POST, da der Server die Daten erhält, die der Benutzer bereitstellt. Wir setzen die Aktion des Formulars auf eine Route, /formdie wir zur Verarbeitung der vom Registrierungsformular zurückgegebenen Daten bereitstellen werden.
Nun, da die erste Registrierungsseite fertig ist, können wir eine zweite Seite für die Bestätigung des Codes erstellen, der an den Benutzer gesendet wird. Die Bestätigungsseite ähnelt der Registrierungsseite, mit nur wenigen Änderungen bei den Bezeichnungen der Felder und Eingaben:
<form method="POST" action="/confirm">
<h2>Confirm Phone Number</h2>
<label for="phonenum">Please Input the confirmation code sent to your phone:</label><br/>
<input id="phonenum" name="confirmation" type="tel" required value="" >
<input name="phone" type="hidden" value="{{ .Phone }}">
<input name="requestId" type="hidden" value="{{ .Id }}">
<input type="submit" value="Submit">
</form>
Den vollständigen Text für beide HTML-Dateien finden Sie in der Beispiel-Repos. Nachdem Sie beide HTML-Dateien eingerichtet haben, starten Sie den Server neu. Wenn alles gut gelaufen ist, sollte die index.html Datei erscheinen, wenn Sie die Seite localhost:8080 im Browser aufrufen.
Registration form
Wenn Sie eine Telefonnummer eingeben und auf die Schaltfläche "Senden" klicken, werden Sie feststellen, dass eine Weiterleitung zu localhost:8080/form weiterleitet, wie zuvor mit dem HTML-Aktionsattribut angegeben. Die /form Route ist derzeit nicht für die Bereitstellung der form.html Datei. Im nächsten Abschnitt werden wir einen Handler für die /form Route erstellen, der die Telefonnummer als Argument akzeptiert und die confirm.html Datei.
Implementierung der Multi-Faktor-Authentifizierung
In diesem Abschnitt werden wir den Kern unserer Anwendung - die Multi-Faktor-Authentifizierung - besprechen und programmieren. Wir werden die Vonage Verify API verwenden, um die Multi-Faktor-Authentifizierung zu implementieren.
Der Prozess der Implementierung von MFA mit der Verify API ist ein zweiseitiger Prozess:
Der erste Schritt besteht darin, eine Verify-Anfrage zu starten. In dieser Phase wird ein Code an das Telefon des Benutzers gesendet.
Im zweiten Schritt wird geprüft, ob der vom Nutzer angegebene Verifizierungscode korrekt ist.
Wenn eine Verify-Anfrage gestartet wird, wird eine Request_id automatisch aus der Telefonnummer des Benutzers generiert. Diese ID wird bei der Überprüfung in Schritt zwei verwendet.
Die Entwickler von Vonage haben ein Go-Paket für die Interaktion mit einer Reihe von Vonage-APIs, einschließlich Verify. Um zu beginnen, installieren Sie das Paket in Ihr Projekt, indem Sie go get github.com/vonage/vonage-go-sdk im Terminal ausführt.
Nun, da wir alle Vorbereitungen getroffen haben, können wir gleich loslegen!
In der Datei verify.go Datei erstellen Sie vier Funktionen:
1. createClient
Diese Funktion enthält die Logik für die Erstellung eines Clients. Ein Client ist notwendig, damit wir mit der Verify API interagieren können. Um einen Client zu erstellen, rufen wir die NewVerifyClient Funktion auf, die das vonage-go Paket, das wir zuvor importiert haben. Die Funktion NewVerifyClient Funktion erfordert einen Authentifizierungssatz, den wir durch Aufrufen der CreateAuthFromKeySecret Funktion aufrufen und den API-Schlüssel und das Geheimnis übergeben
Es ist eine gute Praxis, sensible Informationen als Umgebungsvariablen zu speichern, um zu verhindern, dass sie in die falschen Hände geraten. In der .env Datei, die oben erstellt wurde, fügen Sie Ihren API-Schlüssel und Ihr Geheimnis im Format API_KEY=0000000.
Zu diesem Zeitpunkt sieht die createClient Funktion wie folgt aus:
func createClient() *vonage.VerifyClient{
Key, _ := os.LookupEnv("API_KEY")
Secret, _ := os.LookupEnv("API_SECRET")
auth := vonage.CreateAuthFromKeySecret(Key, Secret)
client := vonage.NewVerifyClient(auth)
return client
}2. die Funktion init
Die Funktion init Funktion ist von Go vordefiniert und wird zur Initialisierung unserer Anwendung verwendet. Wir werden sie benutzen, um unsere Umgebungsvariablen aus der Datei .env Datei zu laden, bevor der Rest unseres Codes ausgeführt wird, aber zuerst müssen wir ein Paket installieren, das üblicherweise zum Laden von Umgebungsvariablen verwendet wird. Führen Sie im Terminal go get github.com/joho/godotenv' to install the package. Next, add the corresponding import. Theinitfunction will contain just a few lines of code which invokes thegodotenv` Funktion laden.
func init() {
// loads values from .env into the system
if err := godotenv.Load(); err != nil {
log.Print("No .env file found")
}
}3. VerStart Funktion
Mit dieser Funktion beginnen wir die Überprüfungsanforderung. Die Funktion wird in das Hauptpaket exportiert, daher schreiben wir den ersten Buchstaben der Funktion groß, wie es in Go üblich ist. In der VerStart Funktion starten wir die Anfrage durch den Aufruf der Vonage SDK Request Methode auf einem Client aufrufen. Wir rufen die createClient Funktion auf, die wir oben erstellt haben, um einen Client zu erhalten, auf dem wir die Request Methode aufrufen können:
func VerStart(phoneNumber string) string{
client := createClient()
verification, _, err := client.Request(phoneNumber, "Go-Tut MFA", vonage.VerifyOpts{
CodeLength: 6,
})
if err != nil {
log.Fatal(err)
}
return verification.RequestId
}Die Methode Request Methode nimmt drei Parameter entgegen:
Die zu überprüfende Rufnummer
Der Markenname
Eine Optionsstruktur, mit der das OTP, das an den Benutzer gesendet wird, angepasst werden kann.
In unserer Anwendung soll die Telefonnummer dynamisch sein, daher geben wir sie als Parameter für die Request Funktion an, der beim Aufrufen der Funktion übergeben wird. Der Markenname ist eine kurze Zeichenfolge, die angibt, welche Marke die SMS versendet - in diesem Beispiel habe ich "Go-tut MFA" verwendet. Wenn die erforderlichen Parameter an die Request Methode übergeben wurden, können wir uns mit den Rückgabewerten befassen. Die Methode Request Methode gibt eine Verifizierungsantwort zurück, die ein Statusfeld, eine http-Antwort, die wir ignorieren werden, und einen Fehlertyp enthält. Wir können den Status der Verifizierungsantwort zurückgeben und den Fehler behandeln.
4. verCheck-Funktion
Diese Funktion enthält die Logik für die Bestätigung des vom Benutzer eingegebenen Verifizierungscodes. Diese Funktion ist ähnlich wie die verStart Funktion (siehe oben):
func VerCheck(reqId, code string) string{
client := createClient()
response, _, err := client.Check(reqId, code)
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
return response.Status
}Die Funktion verCheck Funktion nimmt eine Anfrage-ID entgegen, die ein erforderliches Argument für die Check Funktion ist, und einen code Parameter, bei dem es sich um den an den Benutzer gesendeten Bestätigungscode handeln sollte. Die Funktion gibt einen Status zurück, der vom Typ String ist. Der zurückgegebene Status gibt an, ob der Benutzer den richtigen Bestätigungscode eingegeben hat. Der Antwortstatus ist 0, wenn und nur wenn der vom Benutzer eingegebene Bestätigungscode korrekt ist.
Im nächsten Abschnitt werden wir sehen, wo die vier oben erstellten Funktionen aufgerufen werden sollten, damit unsere Anwendung funktioniert.
Die Anwendung zusammenstellen
Nun, da alle Funktionen, die unsere Anwendung benötigt, fertig sind, können wir sie zu einer funktionierenden Anwendung zusammenfügen. Wir haben unsere Index-Seite bereits gerendert, aber wir müssen auch die Bestätigungsseite rendern, die angezeigt wird, sobald die Schaltfläche "Submit" angeklickt wird. Das ist in Go ganz anders als in anderen Sprachen. Wir müssen eine Handler-Funktion erstellen und sie an die /form Route anhängen, die die Daten des Registrierungsformulars empfängt. Als nächstes erstellen wir eine Funktion namens formHandler um die Handler-Schnittstelle von Go zu implementieren. Anschließend wird der Request Body geparst, so dass wir auf die Formulardaten in der index.html. Dazu verwenden wir die http-Request ParseForm Methode für diesen Zweck.
Die Formulardaten können aus dem geparsten Formular mit Hilfe der formValue Methode. Diese Methode nimmt den Namen auf, der im HTML-Eingabeattribut unserer index.html Datei. Wir können dann die Funktion verRequest Funktion mit der Telefonnummer aufrufen.
Um die Bestätigungsseite anzuzeigen, wenn ein Benutzer seine Telefonnummer eingibt, müssen wir eine separate Funktion erstellen. Wir können zwar einfach die http.Servefile Funktion verwenden, um unsere form.html Datei aufrufen, aber das ist in unserem Fall nicht die ideale Lösung. Wir müssen nicht nur die Bestätigungsdatei rendern, sondern auch die Telefonnummer des Benutzers und die Anfrage-ID übergeben, damit sie zum Aufrufen der verCheck Utility-Funktion aufzurufen.
Die Render-Funktion ist ziemlich einfach - sie empfängt eine http-Anforderung, einen Antwort-Writer und eine Schnittstelle. Die Render-Funktion verwendet Go's http/template Paket von Go, um die Dateien zu parsen und mit den bereitgestellten Daten (Telefonnummer des Benutzers und Anfrage-ID) auszuführen.
func render(w http.ResponseWriter, filename string, data interface{}) {
//parse the provided file
tmpl, err := template.ParseFiles(filename)
if err != nil {
log.Println(err)
}
//execute the file
if err := tmpl.Execute(w, data); err != nil {
log.Println(err)
}
}Wenn die Render-Funktion fertig ist, können wir sie in der formHandler Funktion aufrufen, indem wir die Route zur Formulardatei und unsere Nachricht übergeben. Um eine Nachricht zu übergeben, erstellen wir einfach eine Struktur, die die Felder definiert, die wir an die form.html-Datei übergeben möchten:
type Message struct {
Phone string
Id string
}Wir müssen versteckte Felder in der Datei form.html Datei versteckte Felder erstellen, um die Variablen in der Nachrichtenstruktur zu erhalten, indem wir Folgendes zum Formular hinzufügen:
<input name="phone" type="hidden" value="{{ .Phone }}">
<input name="requestId" type="hidden" value="{{ .Id }}">Zu diesem Zeitpunkt sieht die gesamte formHandler Funktion wie folgt aus
func formHandler (w http.ResponseWriter, r *http.Request){
//Parse the form
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
//Get the value of the Input from the form
Phone := r.FormValue("phone")
Id := verify.VerStart(Phone)
msg := &Message{
Id: Id,
Phone: Phone,
}
//Render the form.
render(w, "./static/form.html", msg)
}
Eine letzte Sache, die wir tun müssen, bevor wir die Dinge testen, ist die Registrierung der formHandler Funktion in der /form Route zu registrieren. Dazu fügen wir nur eine Zeile Code hinzu:
mux.HandleFunc("/form", formHandler)Und schon sind wir bereit! Sie können das Programm starten, Ihre Telefonnummer auf der Registrierungsseite eingeben und auf Senden klicken. Wenn Sie auf die Schaltfläche "Absenden" klicken, werden Sie zur Bestätigungsseite weitergeleitet und erhalten eine SMS auf Ihr Mobiltelefon.
SMS code
Unsere Anwendung sieht gut aus. Als nächstes müssen wir die Eingabe des Bestätigungscodes durch den Benutzer behandeln. Im aktuellen Zustand unserer Anwendung wird der Benutzer, wenn er seinen Bestätigungscode eingibt und auf "Senden" klickt, zur Bestätigungsroute weitergeleitet, die noch nicht eingerichtet ist.
Wir werden den Confirm Handler so einrichten, dass er den Bestätigungscode vom Client erhält und die verCheck util-Funktion aufruft. Der Confirm Handler ist dem Form Handler ziemlich ähnlich, mit nur wenigen Unterschieden.
Um loszulegen, erstellen wir die confirmHandler Funktion, die die Handler-Schnittstelle implementiert und die eingehende Anfrage analysiert, genau wie bei der formHandler function. Als nächstes extrahieren wir die Werte, die für die Ausführung der verCheck Funktion aus dem Formular. Es gibt drei Werte, die wir extrahieren müssen:
Die Rufnummer
Die Anfrage-ID
Der Bestätigungscode
Die ersten beiden Werte wurden von der formHandler mit Hilfe der Render-Funktion übergeben und in der form.html file. Um die Werte zu erhalten, fügen wir einfach die folgenden Zeilen hinzu, die wir bereits bei der Erstellung der formHandler Funktion besprochen hatten:
Id := r.FormValue("requestId")
phone := r.FormValue("phone")
confirmation := r.FormValue("confirmation")Da wir nun alle Werte erhalten haben, die für den zweiten Schritt in unserem Arbeitsablauf (Überprüfung des Codes) erforderlich sind, können wir die Funktion verCheck functionaufrufen, indem wir die Anforderungs-ID und den Bestätigungscode, den wir gerade extrahiert haben, eingeben. Erinnern Sie sich daran, wie wir die verCheck Funktion so eingerichtet haben, dass sie einen Erfolgsstatus zurückgibt? Wir können diesen Status überprüfen, um zu sehen, ob der vom Benutzer eingegebene Bestätigungscode korrekt ist, und eine Erfolgs- oder Fehlermeldung auf einer neuen Webseite ausgeben, indem wir die fmt.FPrint Funktion. Zu diesem Zeitpunkt sollte die confirmHandler wie folgt aussehen:
func confirmHandler(w http.ResponseWriter, r *http.Request) {
//Parse the form
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
//Extract the Form values
Id := r.FormValue("requestId")
phone := r.FormValue("phone")
//Receive the confirmation code
confirmation := r.FormValue("confirmation")
//Verify the Confirmation code
response := verify.VerCheck(Id, confirmation)
//Check if the confirmation code is incorrect
if response != "0" {
fmt.Fprint(w,"Verification failed! Input the correct code sent to ", phone)
return
}
fmt.Fprint(w,"🎉 Success! 🎉")
}An diesem Punkt sind wir mit unserer Anwendung fast fertig. Das letzte, was wir tun müssen, ist die Registrierung der confirmHandler Route zu registrieren, indem wir die folgende Codezeile in die Hauptfunktion einfügen:
mux.HandleFunc("/confirm", confirmHandler)Und schon sind wir startklar! Wenn Sie Ihren Server starten und die Adresse im Browser aufrufen, sollten Sie das Anmeldeformular sehen. Wenn Sie Ihre Telefonnummer eingeben und auf "Senden" klicken, bekommen Sie den Code auf Ihr Handy geschickt. Dann werden Sie automatisch auf die Bestätigungsseite weitergeleitet, wo Sie den Code eingeben können. Wenn Sie diesen Code eingeben, sollte der Erfolg auf der Seite ausgedruckt werden.
Schlussfolgerung
Bisher haben wir eine Anwendung in Go erstellt, die die Vonage Verify API nutzt, und haben etwas über Webentwicklung mit Go gelernt. Ich hoffe, das hat Ihnen gefallen. Der vollständige Code für diese Anwendung kann auf GitHub gefunden werden.