So erstellen Sie eine Web-App mit Go, Gin und React

Dieser Artikel wurde ursprünglich in Mein Blog veröffentlicht

TL; DR: In diesem Tutorial werde ich Ihnen zeigen, wie einfach es ist, eine Webanwendung mit Go und dem Gin-Framework zu erstellen und eine Authentifizierung hinzuzufügen. Schauen Sie sich das Github-Repo für den Code an, den wir schreiben werden.

Gin ist ein Hochleistungs-Mikro-Framework. Es bietet ein sehr minimalistisches Framework, das nur die wichtigsten Features, Bibliotheken und Funktionen enthält, die zum Erstellen von Webanwendungen und Microservices erforderlich sind. Es macht es einfach, eine Pipeline zur Anforderungsbearbeitung aus modularen, wiederverwendbaren Teilen zu erstellen. Dazu können Sie Middleware schreiben, die an einen oder mehrere Anforderungshandler oder Gruppen von Anforderungshandlern angeschlossen werden kann.

Gin Eigenschaften

Gin ist ein schnelles, einfaches, aber voll funktionsfähiges und sehr effizientes Webframework für Go. Schauen Sie sich einige der folgenden Funktionen an, die es zu einem würdigen Rahmen machen, den Sie für Ihr nächstes Golang-Projekt in Betracht ziehen sollten.

  • Geschwindigkeit: Gin ist auf Geschwindigkeit ausgelegt. Das Framework bietet ein auf Radix-Bäumen basierendes Routing und einen geringen Speicherbedarf. Keine Reflexion. Vorhersehbare API-Leistung.
  • Absturzfrei : Gin kann zur Laufzeit Abstürze oder Panik auslösen und sich von diesen erholen. Auf diese Weise ist Ihre Anwendung immer verfügbar.
  • Routing: Gin bietet eine Routing-Oberfläche, mit der Sie ausdrücken können, wie Ihre Webanwendungs- oder API-Routen aussehen sollen.
  • JSON-Validierung: Gin kann JSON-Anforderungen einfach analysieren und validieren und prüfen, ob die erforderlichen Werte vorhanden sind.
  • Fehlerverwaltung: Gin bietet eine bequeme Möglichkeit, alle Fehler zu erfassen, die während einer HTTP-Anforderung aufgetreten sind. Schließlich kann eine Middleware sie in eine Protokolldatei oder in eine Datenbank schreiben und über das Netzwerk senden.
  • Integriertes Rendering: Gin bietet eine benutzerfreundliche API für das Rendern von JSON, XML und HTML.

Voraussetzungen

Um diesem Tutorial folgen zu können, müssen Sie Go auf Ihrem Computer installiert haben, einen Webbrowser zum Anzeigen der App und eine Befehlszeile zum Ausführen von Build-Befehlen.

Go, oder wie es normalerweise Golang genannt wird , ist eine Programmiersprache, die von Google zum Erstellen moderner Software entwickelt wurde. Go ist eine Sprache, mit der Dinge effizient und schnell erledigt werden können. Die wichtigsten Vorteile von Go sind:

  • Stark getippt und Müll gesammelt
  • Blitzschnelle Kompilierungszeiten
  • Parallelität eingebaut
  • Umfangreiche Standardbibliothek

Besuchen Sie den Download-Bereich der Go-Website, um Go auf Ihrem Computer zum Laufen zu bringen.

Erstellen einer App mit Gin

Wir werden mit Gin eine einfache App zum Auflisten von Witzen erstellen . Unsere App wird einige dumme Papa-Witze auflisten. Wir werden die Authentifizierung hinzufügen, damit alle angemeldeten Benutzer das Privileg haben, Witze zu mögen und anzusehen.

Auf diese Weise können wir veranschaulichen, wie Gin zum Entwickeln von Webanwendungen und / oder APIs verwendet werden kann.

Wir werden die folgenden von Gin angebotenen Funktionen nutzen:

  • Middleware
  • Routing
  • Gruppierung von Routen

Achtung, fertig, los

Wir werden unsere gesamte Go-Anwendung in eine main.goDatei schreiben . Da es sich um eine kleine Anwendung handelt, ist es einfach, die Anwendung nur go runvom Terminal aus zu erstellen .

Wir erstellen ein neues Verzeichnis golang-ginin unserem Go-Arbeitsbereich und dann eine main.goDatei darin:

$ mkdir -p $GOPATH/src/github.com/user/golang-gin $ cd $GOPATH/src/github.com/user/golang-gin $ touch main.go

Der Inhalt der main.goDatei:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Start and run the server router.Run(":3000") }

Wir müssen weitere Verzeichnisse für unsere statischen Dateien erstellen. main.goErstellen wir im selben Verzeichnis wie die Datei einen viewsOrdner. Im viewsOrdner, erstellen Sie einen jsOrdner und eine index.htmlDatei drin.

Die index.htmlDatei wird vorerst sehr einfach sein:

   Jokeish App   

Welcome to the Jokeish App

Bevor wir testen, was wir bisher haben, installieren wir die hinzugefügten Abhängigkeiten:

$ go get -u github.com/gin-gonic/gin $ go get -u github.com/gin-gonic/contrib/static

Um zu sehen, was funktioniert, müssen wir unseren Server durch Ausführen starten go run main.go.

Navigieren Sie nach dem Ausführen der Anwendung //localhost:3000in Ihrem Browser zu. Wenn alles gut gegangen ist, sollte der Header-Text der Ebene 1 angezeigt werden. Willkommen bei der Jokeish App .

API definieren

Fügen wir unserer main.goDatei weiteren Code für unsere API-Definitionen hinzu. Wir werden unsere mainFunktion mit zwei Routen /jokes/und /jokes/like/:jokeIDder Routengruppe aktualisieren /api/.

func main() { // ... leave the code above untouched... // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Der Inhalt der main.goDatei sollte folgendermaßen aussehen:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) // Start and run the server router.Run(":3000") } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Lassen Sie uns unsere App erneut ausführen go run main.gound auf unsere Routen zugreifen. //localhost:3000/api/jokesgibt eine 200 OKHeader-Antwort mit der Nachricht zurück jokes handler not implemented yet. Eine POST-Anforderung zum //localhost:3000/api/jokes/like/1Zurückgeben eines 200 OKHeaders und der Nachricht Likejoke handler not implemented yet.

Witze Daten

Da wir bereits unsere Routendefinition festgelegt haben, die nur eines bewirkt (eine JSON-Antwort zurückgeben), werden wir unsere Codebasis ein wenig aufpeppen, indem wir etwas mehr Code hinzufügen.

// ... leave the code above untouched... // Let's create our Jokes struct. This will contain information about a Joke // Joke contains information about a single Joke type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } // We'll create a list of jokes var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } func main() { // ... leave this block untouched... } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { // confirm Joke ID sent is valid // remember to import the `strconv` package if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke, and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes += 1 } } // return a pointer to the updated jokes list c.JSON(http.StatusOK, &jokes) } else { // Joke ID is invalid c.AbortWithStatus(http.StatusNotFound) } } // NB: Replace the JokeHandler and LikeJoke functions in the previous version to the ones above

Lassen Sie uns unsere API testen, da unser Code gut aussieht. Wir können mit cURLoder testen postmanund dann eine GETAnfrage senden //localhost:3000/jokes, um die vollständige Liste der Witze zu erhalten, und eine POSTAnfrage, //localhost:3000/jokes/like/{jokeid}um die Art eines Witzes zu erhöhen.

$ curl //localhost:3000/api/jokes $ curl -X POST //localhost:3000/api/jokes/like/4

Erstellen der Benutzeroberfläche (Reagieren)

Wir haben unsere API eingerichtet, also erstellen wir ein Frontend, um die Daten aus unserer API zu präsentieren. Dafür verwenden wir React. Wir werden nicht zu tief auf React eingehen, da dies nicht in den Rahmen dieses Tutorials fällt. Wenn Sie mehr über React erfahren möchten, lesen Sie das offizielle Tutorial. Sie können die Benutzeroberfläche mit jedem Frontend-Framework implementieren, mit dem Sie vertraut sind.

Installieren

Wir werden die index.htmlDatei bearbeiten , um die externen Bibliotheken hinzuzufügen, die zum Ausführen von React erforderlich sind. Dann müssen wir eine app.jsxDatei im views/jsVerzeichnis erstellen , die unseren Reaktionscode enthält.

Unsere index.htmlDatei sollte folgendermaßen aussehen:

     Jokeish App 

Aufbau unserer Komponenten

In React werden Ansichten in Komponenten unterteilt. Wir müssen einige Komponenten bauen:

  • Eine AppKomponente als Haupteintrag, mit der die Anwendung gestartet wird
  • Eine HomeKomponente, die nicht angemeldeten Benutzern gegenübersteht
  • Eine LoggedInKomponente mit Inhalten, die nur für authentifizierte Benutzer sichtbar sind
  • und eine JokeKomponente zum Anzeigen einer Liste von Witzen.

Wir werden alle diese Komponenten in die app.jsxDatei schreiben .

Die App-Komponente

Diese Komponente bootstraps unsere gesamte React-App. Es entscheidet, welche Komponente angezeigt werden soll, basierend darauf, ob ein Benutzer authentifiziert ist oder nicht. Wir werden nur mit der Basis beginnen und sie später mit mehr Funktionen aktualisieren.

class App extends React.Component { render() { if (this.loggedIn) { return (); } else { return (); } } }

Die Home-Komponente

Diese Komponente wird nicht angemeldeten Benutzern zusammen mit einer Schaltfläche angezeigt, die einen gehosteten Sperrbildschirm öffnet, auf dem sie sich anmelden oder anmelden können. Wir werden diese Funktionalität später hinzufügen.

class Home extends React.Component { render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ) } }

Angemeldete Komponente

Diese Komponente wird angezeigt, wenn ein Benutzer authentifiziert ist. Es speichert stateeine Reihe von Witzen, die beim Mounten der Komponente ausgefüllt werden.

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] } } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i){ return (); })} ) } }

Die Witzkomponente

Die JokeKomponente enthält Informationen zu jedem Element aus der anzuzeigenden Witzantwort.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "" } this.like = this.like.bind(this); } like() { // ... we'll add this block later } render() { return ( #{this.props.joke.id} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Wir haben unsere Komponenten geschrieben. Lassen Sie uns nun React mitteilen, wo die App gerendert werden soll. Wir werden den folgenden Codeblock am Ende unserer app.jsxDatei hinzufügen .

ReactDOM.render(, document.getElementById('app'));

Lassen Sie uns unseren Go-Server neu starten go run main.gound zur URL unserer App gehen //localhost:3000/. Sie werden sehen, dass die HomeKomponente gerendert wird.

Sichern Sie unsere Witz-App mit Auth0

Auth0 stellt bei jeder Anmeldung für Ihre Benutzer JSON-Web-Token aus. Dies bedeutet, dass Sie über eine solide Identitätsinfrastruktur verfügen können, einschließlich Single Sign-On, Benutzerverwaltung, Unterstützung für Anbieter sozialer Identität (Facebook, Github, Twitter usw.), Anbieter von Unternehmensidentität (Active Directory, LDAP, SAML usw.). und Ihre eigene Benutzerdatenbank mit nur wenigen Codezeilen.

Mit Auth0 können wir die Authentifizierung in unserer GIN-App einfach einrichten. Sie benötigen ein Konto, um diesem Teil zu folgen. Wenn Sie noch kein Auth0-Konto haben, melden Sie sich jetzt an.

Haftungsausschluss: Dies ist kein gesponserter Inhalt.

API-Client erstellen

Unsere Token werden mit Auth0 generiert, daher müssen wir eine API und einen Client über unser Auth0-Dashboard erstellen. Wenn Sie dies noch nicht getan haben, melden Sie sich erneut für ein Auth0-Konto an.

Navigieren Sie zum Erstellen einer neuen API zum Abschnitt APIs in Ihrem Dashboard und klicken Sie auf die Schaltfläche API erstellen .

Wählen Sie einen API- Namen und eine Kennung . Die Kennung ist die Zielgruppe für die Middleware. Der Signieralgorithmus sollte RS256 .

Um einen neuen Client zu erstellen, navigieren Sie zum Abschnitt "Clients" in Ihrem Dashboard und klicken Sie auf die Schaltfläche " Client erstellen" . Wählen Sie den Typ Regular Web Applications.

Beachten Sie nach dem Erstellen des Clients das client_idund client_secret, da wir es später benötigen.

Wir müssen die für unsere API erforderlichen Anmeldeinformationen zu einer Umgebungsvariablen hinzufügen. Erstellen Sie im Stammverzeichnis eine neue Datei .envund fügen Sie Folgendes mit den Details aus dem Auth0-Dashboard hinzu:

export export export AUTH0_DOMAIN="yourdomain.auth0.com" export

Sichern unserer API-Endpunkte

Derzeit ist unsere API für die Welt offen. Wir müssen unsere Endpunkte sichern, damit nur autorisierte Benutzer darauf zugreifen können.

Wir werden eine JWT-Middleware verwenden , um von jeder Anfrage, die unsere Endpunkte erreicht , nach einem gültigen JSON-Web-Token zu suchen.

Lassen Sie uns unsere Middleware erstellen:

// ... var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) // register our actual jwtMiddleware jwtMiddleWare = jwtMiddleware // ... the rest of the code below this function doesn't change yet } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } }

Im obigen Code haben wir eine neue jwtMiddleWareVariable, die in der mainFunktion initialisiert wird . Es wird in der authMiddlewaremittleren Funktion verwendet.

Wenn Sie bemerken, ziehen wir unsere serverseitigen Anmeldeinformationen aus einer Umgebungsvariablen (einem der Grundsätze einer 12-Faktor-App ). Unsere Middleware überprüft und empfängt ein Token von einer Anfrage und ruft die jwtMiddleWare.CheckJWTMethode zur Validierung des gesendeten Tokens auf.

Schreiben wir auch die Funktion zum Zurückgeben der JSON-Webschlüssel:

// ... the code above is untouched... // Jwks stores a slice of JSON Web Keys type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } func main() { // ... the code in this method is untouched... } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key.") } return cert, nil }

Verwenden der JWT-Middleware

Die Verwendung der Middleware ist sehr einfach. Wir übergeben es einfach als Parameter an unsere Routendefinition.

... api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) ...

Unsere main.goDatei sollte folgendermaßen aussehen:

package main import ( "encoding/json" "errors" "fmt" "log" "net/http" "os" "strconv" jwtmiddleware "github.com/auth0/go-jwt-middleware" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) type Response struct { Message string `json:"message"` } type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } /** we'll create a list of jokes */ var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) jwtMiddleWare = jwtMiddleware // Set the router as the default one shipped with Gin router := gin.Default() // Serve the frontend router.Use(static.Serve("/", static.LocalFile("./views", true))) api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) } // Start the app router.Run(":3000") } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key") } return cert, nil } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } } // JokeHandler returns a list of jokes available (in memory) func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } func LikeJoke(c *gin.Context) { // Check joke ID is valid if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes = jokes[i].Likes + 1 } } c.JSON(http.StatusOK, &jokes) } else { // the jokes ID is invalid c.AbortWithStatus(http.StatusNotFound) } }

Lassen Sie uns die jwtmiddlewareBibliotheken installieren :

$ go get -u github.com/auth0/go-jwt-middleware $ go get -u github.com/dgrijalva/jwt-go

Lassen Sie uns unsere Umgebungsdatei als Quelle verwenden und unseren App-Server neu starten:

$ source .env $ go run main.go

Wenn wir nun versuchen, auf einen der Endpunkte zuzugreifen, wird ein 401 UnauthorizedFehler angezeigt. Das liegt daran, dass wir zusammen mit der Anfrage ein Token senden müssen.

Mit Auth0 anmelden und reagieren

Lassen Sie uns ein Anmeldesystem implementieren, damit Benutzer sich anmelden oder Konten erstellen und Zugriff auf unsere Witze erhalten können. Wir werden unserer app.jsxDatei die folgenden Auth0-Anmeldeinformationen hinzufügen:

  • AUTH0_CLIENT_ID
  • AUTH0_DOMAIN
  • AUTH0_CALLBACK_URL - Die URL Ihrer App
  • AUTH0_API_AUDIENCE
Sie können die finden AUTH0_CLIENT_ID, AUTH0_DOMAINund AUTH0_API_AUDIENCEDaten von Ihrer Auth0 Management - Dashboard.

Wir müssen ein callbackfestlegen, zu dem Auth0 umleitet. Navigieren Sie in Ihrem Dashboard zum Abschnitt Clients. Setzen wir in den Einstellungen den Rückruf auf //localhost:3000:

Lassen Sie uns mit den vorhandenen Anmeldeinformationen unsere React-Komponenten aktualisieren.

APP-Komponente

const AUTH0_CLIENT_ID = "aIAOt9fkMZKrNsSsFqbKj5KTI0ObTDPP"; const AUTH0_DOMAIN = "hakaselabs.auth0.com"; const AUTH0_CALLBACK_URL = location.href; const AUTH0_API_AUDIENCE = "golang-gin"; class App extends React.Component { parseHash() { this.auth0 = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID }); this.auth0.parseHash(window.location.hash, (err, authResult) => { if (err) { return console.log(err); } if ( authResult !== null && authResult.accessToken !== null && authResult.idToken !== null ) { localStorage.setItem("access_token", authResult.accessToken); localStorage.setItem("id_token", authResult.idToken); localStorage.setItem( "profile", JSON.stringify(authResult.idTokenPayload) ); window.location = window.location.href.substr( 0, window.location.href.indexOf("#") ); } }); } setup() { $.ajaxSetup({ beforeSend: (r) => { if (localStorage.getItem("access_token")) { r.setRequestHeader( "Authorization", "Bearer " + localStorage.getItem("access_token") ); } } }); } setState() { let idToken = localStorage.getItem("id_token"); if (idToken) { this.loggedIn = true; } else { this.loggedIn = false; } } componentWillMount() { this.setup(); this.parseHash(); this.setState(); } render() { if (this.loggedIn) { return ; } return ; } }

Ich aktualisierte , um die App - Komponente mit drei Komponentenmethoden ( setup, parseHash, und setState) und einer Lebenszyklus - Methode componentWillMount. Die parseHashMethode initialisiert den auth0webAuthClient und analysiert den Hash in ein besser lesbares Format, um ihn in localSt zu speichern. Um den Sperrbildschirm anzuzeigen, erfassen und speichern Sie das Benutzertoken und fügen Sie allen Anforderungen an unsere API den richtigen Autorisierungsheader hinzu

Hauptkomponente

Unsere Home-Komponente wird aktualisiert. Wir werden die Funktionalität für die authenticateMethode hinzufügen , wodurch der gehostete Sperrbildschirm angezeigt wird und unsere Benutzer sich anmelden oder anmelden können.

class Home extends React.Component { constructor(props) { super(props); this.authenticate = this.authenticate.bind(this); } authenticate() { this.WebAuth = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID, scope: "openid profile", audience: AUTH0_API_AUDIENCE, responseType: "token id_token", redirectUri: AUTH0_CALLBACK_URL }); this.WebAuth.authorize(); } render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ); } }

Angemeldete Komponente

Wir werden die LoggedInKomponente aktualisieren , um mit unserer API zu kommunizieren und alle Witze zu machen. Jeder Witz wird als propan die JokeKomponente übergeben, die ein Bootstrap-Panel rendert. Schreiben wir diese:

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] }; this.serverRequest = this.serverRequest.bind(this); this.logout = this.logout.bind(this); } logout() { localStorage.removeItem("id_token"); localStorage.removeItem("access_token"); localStorage.removeItem("profile"); location.reload(); } serverRequest() { $.get("//localhost:3000/api/jokes", res => { this.setState({ jokes: res }); }); } componentDidMount() { this.serverRequest(); } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i) { return ; })} ); } }

Scherzkomponente

Wir werden die JokeKomponente auch aktualisieren, um jedes von der übergeordneten Komponente ( LoggedIn) an sie übergebene Witzelement zu formatieren . Wir werden auch eine likeMethode hinzufügen , die die Art eines Witzes erhöht.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "", jokes: [] }; this.like = this.like.bind(this); this.serverRequest = this.serverRequest.bind(this); } like() { let joke = this.props.joke; this.serverRequest(joke); } serverRequest(joke) { $.post( "//localhost:3000/api/jokes/like/" + joke.id, { like: 1 }, res => { console.log("res... ", res); this.setState({ liked: "Liked!", jokes: res }); this.props.jokes = res; } ); } render() { return ( #{this.props.joke.id}{" "} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Alles zusammenfügen

Wenn die Benutzeroberfläche und die API vollständig sind, können wir unsere App testen. Wir starten zunächst unseren Server source .env && go run main.gound navigieren dann //localhost:3000von jedem Browser aus zu. Sie sollten die HomeKomponente mit einer Anmeldeschaltfläche sehen. Durch Klicken auf die Anmeldeschaltfläche wird auf eine gehostete Sperrseite umgeleitet (Konto erstellen oder anmelden), um die Anwendung weiter zu verwenden.

Zuhause:

Auth0 Hosted Lock Screen:

Angemeldete App-Ansicht:

Fazit

Glückwunsch! Sie haben gelernt, wie Sie mit Go und dem Gin-Framework eine Anwendung und eine API erstellen.

Habe ich etwas Wichtiges verpasst? Lass es mich in den Kommentaren wissen.

Sie können Hallo zu mir auf Twitter @codehakase sagen