close icon
Golang

Authentication in Golang with JWTs

Practice Go and React by building and authenticating a RESTful API with JSON Web Tokens (JWTs).

Last Updated On: May 26, 2020

TL;DR: Learn how to build and secure a Go API with JSON Web Tokens (JWTs) and consume it with a modern React UI. Users will authenticate on the React side with Auth0 and then make a request to the Go API by sending their access token along with the request. Check out the GitHub repo for the full code now.

Why Golang

Golang, or simply Go, is an open source programming language developed by Google for building modern software. Go is a language designed to get stuff done efficiently and fast. The key benefits of Golang include:

  • Strongly typed and garbage collected
  • Blazing fast compile times
  • Concurrency built-in
  • Extensive standard library

Go makes every attempt to reduce both the amount of typing needed and the complexity of its syntax. Variables can be declared and initialized easily with := syntax, semicolons are unnecessary, and there is no complex type hierarchy.

". @golang is highly opinionated. The result is clean and easy to follow code."

Tweet

Tweet This

In this tutorial, you will be building a RESTful API in Go, so knowledge of the Go language is a prerequisite. It is out of the scope of this tutorial to cover the fundamentals of the Go programming language. If you are new to Go, check out the masterfully crafted Tour of Go, which covers everything from the basics to advanced topics such as concurrency, and then, you’ll be ready to proceed with this tutorial. If you are already somewhat familiar with Go, on the other hand, then let’s build an API!

Go is an excellent choice for building fast and scalable RESTful APIs. The net/http standard library provides key methods for interacting via the HTTP protocol. And augmented with the Gorilla Toolkit, you'll have an API up and running in no time. The app you are building today is called “We R VR.” The app allows virtual reality enthusiasts to provide feedback to developers on the games and experiences they are building.

We-R-Vr Go React final application

Idiomatic Go prefers small libraries over large frameworks and the use of the standard library whenever possible. This article will adhere to these idioms as much as possible to ensure that the code samples are applicable across the Go ecosystem. With that said, a couple of handy libraries such as gorilla/mux for routing and dgrijalva/jwt-go for JSON Web Tokens will be used to speed up development.

Interested in getting up-to-speed with JWTs as soon as possible?

Download the free ebook
JWT Handbook

Golang frameworks

Before jumping into the code, I do want to point out that while idiomatic Go tends to shy away from large frameworks, it does not mean that no frameworks are written in Go. Beego, Gin Gionic, Echo, and Revel are just some of the more traditional web/api frameworks available. Since the net/http standard package already provides so much functionality, these frameworks tend to be built on top of it or at least use parts of the net/http package. Therefore, learning any or all of them will still be beneficial, as the skills will be applicable throughout your Go career.

Building an API in Go

Let's get started! First, you need to install Go.

Once Go is installed, there are two options to finish setup: Go Modules or GOPATH.

Setting up your GOPATH workspace can be a bit cumbersome, so Go introduced a new dependency management system, Go Modules, in version 1.11. This tutorial won't go too in depth about either setup method, but here are some excellent resources if you'd like to know more:

For this tutorial, we're going to use Go modules. Go ahead and set that up now. Make a new directory where the project will be stored. Next, create the main.go file, which will be your application starting point. Finally, initialize the Go modules for the project. This can be named anything for tutorial purposes, but normally you'd want this to be a repository URL (without the http://) because this allows the module to be available at that URL in case the code needs to be shared.

mkdir go-vr-auth
cd go-vr-auth
touch main.go
go mod init github.com/auth0-blog/go-vr-auth

For simplicity, you will write your entire application in your main.go file. Open up the main.go file that was just created and paste in the following:

package main

// Import our dependencies. We'll use the standard HTTP library as well as the gorilla router for this app
import (
    "encoding/json"
  "github.com/gorilla/mux"
  "net/http"
)

func main() {
  // Here we are instantiating the gorilla/mux router
  r := mux.NewRouter()

  // On the default page we will simply serve our static index page.
  r.Handle("/", http.FileServer(http.Dir("./views/")))
  // We will setup our server so we can serve static assest like images, css from the /static/{file} route
  r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))

  // Our application will run on port 8080. Here we declare the port and pass in our router.
  http.ListenAndServe(":8080", r)
}

Create two folders in the same directory that the main.go file is in and name them views and static. In the views folder, create a new file called index.html. Set up a simple index.html page with the following:

<!DOCTYPE html>
<head>
  <title>We R VR</title>
</head>
<body>
  <h1>Welcome to We R VR</h1>
</body>

Before you can run your application, you need to pull in the dependencies specified in main.go. In your terminal, run:

go get

Run the server with:

go run main.go

View it in browser at http://localhost:8080 and it should display "Welcome to We R VR"!

Note: go run will compile our code, create the binary file, and run it. For more information about Go commands, check out this excellent article, An Overview of Go Tooling.

Defining the API

With the foundation now in place, you can now define your API routes. This demo will stick to GET and POST requests. In addition to defining the routes, you’ll also implement a handler function called NotImplemented, which will be the default handler for routes with no custom functionality yet. Add this additional code to the main.go file now.

// imports here
func main(){
  // ...
    r := mux.NewRouter()
  r.Handle("/", http.FileServer(http.Dir("./views/")))
  
  ////////
  // NEW CODE
  ////////
  // Our API is going to consist of three routes
  // /status - which we will call to make sure that our API is up and running
  // /products - which will retrieve a list of products that the user can leave feedback on
  // /products/{slug}/feedback - which will capture user feedback on products
  r.Handle("/status", NotImplemented).Methods("GET")
  r.Handle("/products", NotImplemented).Methods("GET")
  r.Handle("/products/{slug}/feedback", NotImplemented).Methods("POST")

  // ...
}

// Here we are implementing the NotImplemented handler. Whenever an API endpoint is hit
// we will simply return the message "Not Implemented"
var NotImplemented = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  w.Write([]byte("Not Implemented"))
})

The Go API is shaping up nicely. Now restart the server (go run main.go) and try to access each of the three routes. For now, each route returns a 200 OK with the message Not Implemented. Go ahead and add the implementation for those now.

Adding functionality

You have your routes in place, but currently, they do nothing. Let’s change that. In this section, you will add the expected functionality to each of the routes.

// imports here

/* We will first create a new type called Product
   This type will contain information about VR experiences */
type Product struct {
    Id int
    Name string
    Slug string
    Description string
}

/* We will create our catalog of VR experiences and store them in a slice. */
var products = []Product{
  Product{Id: 1, Name: "World of Authcraft", Slug: "world-of-authcraft", Description : "Battle bugs and protect yourself from invaders while you explore a scary world with no security"},
  Product{Id: 2, Name: "Ocean Explorer", Slug: "ocean-explorer", Description : "Explore the depths of the sea in this one of a kind underwater experience"},
  Product{Id: 3, Name: "Dinosaur Park", Slug : "dinosaur-park", Description : "Go back 65 million years in the past and ride a T-Rex"},
  Product{Id: 4, Name: "Cars VR", Slug : "cars-vr", Description: "Get behind the wheel of the fastest cars in the world."},
  Product{Id: 5, Name: "Robin Hood", Slug: "robin-hood", Description : "Pick up the bow and arrow and master the art of archery"},
  Product{Id: 6, Name: "Real World VR", Slug: "real-world-vr", Description : "Explore the seven wonders of the world in VR"}}

func main() {
  // ...
}

var NotImplemented = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Not Implemented"))
})

/* The status handler will be invoked when the user calls the /status route
   It will simply return a string with the message "API is up and running" */
var StatusHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  w.Write([]byte("API is up and running"))
})

/* The products handler will be called when the user makes a GET request to the /products endpoint.
   This handler will return a list of products available for users to review */
var ProductsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  // Here we are converting the slice of products to JSON
  payload, _ := json.Marshal(products)

  w.Header().Set("Content-Type", "application/json")
  w.Write([]byte(payload))
})

/* The feedback handler will add either positive or negative feedback to the product
   We would normally save this data to the database - but for this demo, we'll fake it
   so that as long as the request is successful and we can match a product to our catalog of products we'll return an OK status. */
var AddFeedbackHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  var product Product
  vars := mux.Vars(r)
  slug := vars["slug"]

  for _, p := range products {
      if p.Slug == slug {
          product = p
      }
  }

  w.Header().Set("Content-Type", "application/json")
  if product.Slug != "" {
    payload, _ := json.Marshal(product)
    w.Write([]byte(payload))
  } else {
    http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
  }
})

With your functions in place, go back to the routes and update them with the appropriate handler functions:

// ...

func main(){
  // ...
  r.Handle("/status", StatusHandler).Methods("GET")
  r.Handle("/products", ProductsHandler).Methods("GET")
  r.Handle("/products/{slug}/feedback", AddFeedbackHandler).Methods("POST")
  // ...
}
// ...

Restart the server and you should now see the products listed at http://localhost:8080/products. This means the handler is working correctly. But what exactly is a handler?

Handlers / middleware

In Go, middleware is referred to as handlers. It is abstracted code that runs before the intended code is executed. For example, you may have a logging middleware that logs information about each request. You wouldn't want to implement the logging code for each route individually, so you would write a middleware function that gets inserted before the main function of the route is called that would handle the logging.

"In Go, middleware is referred to as handlers."

Tweet

Tweet This

You will use custom handlers further down in the tutorial to secure your API.

Middleware libraries

This tutorial has been sticking to net/http as much as possible for its implementation, but there are many options for handling middleware in Auth0. You've seen the pure implementation in Golang by wrapping the middleware function around the intended function. Negroni and Alice are two excellent alternatives to handling middleware in Golang.

Now that you have your API set up, you can shift focus to the frontend, which is what will consume the API.

Building the UI with React

An API is only as good as the frontend that consumes it. In this tutorial, you will build your UI with React. Because this tutorial is primarily focused on Go, it won't go into too much detail about the ins-and-outs of the React application. However, the complete instructions showing how to build the React portion will be listed, so you can still follow along if you don't have React experience. If you're interested in a beginner's guide, check out the official React tutorial. The Golang API will work with any frontend, so feel free to implement the UI with any frontend technology you are comfortable with.

"An API is only as good as the frontend that consumes it."

Tweet

Tweet This

Getting started

If you don't already have a frontend in mind and want to follow this tutorial, go ahead and set up a default React App using create-react-app. Navigate to the static folder that you created earlier and run:

npx create-react-app .
cd static
npm start

First, install the dependencies needed on the React side.

npm install react-router-dom @auth0/auth0-spa-js bootstrap react-icons

Now set up the components.

Setting up components

Your application will allow the user to view and leave feedback on VR products. Users must be logged in before they can leave feedback.

You will need to build 3 components:

  • App — to launch the application
  • Home — will be displayed for non-logged in users
  • LoggedIn — will display the products available for review for authenticated users

First, create a new folder inside src called components.

mkdir src/components

Now make the two component files (App already exists):

cd src/components
touch Home.js LoggedIn.js

Setting up Auth0

Because some of the components will be depending on the authentication state, go ahead and set up the Auth0 React wrapper. Create a new file in src called react-auth0-spa.js:

cd ..
touch react-auth0-spa.js

Open it up and paste in the following:

// src/react-auth0-spa.js
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "@auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () =>
  window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
  children,
  onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
  ...initOptions
}) => {
  const [isAuthenticated, setIsAuthenticated] = useState();
  const [user, setUser] = useState();
  const [auth0Client, setAuth0] = useState();
  const [loading, setLoading] = useState(true);
  const [popupOpen, setPopupOpen] = useState(false);
  useEffect(() => {
    const initAuth0 = async () => {
      const auth0FromHook = await createAuth0Client(initOptions);
      setAuth0(auth0FromHook);
      if (window.location.search.includes("code=") &&
          window.location.search.includes("state=")) {
        const { appState } = await auth0FromHook.handleRedirectCallback();
        onRedirectCallback(appState);
      }
      const isAuthenticated = await auth0FromHook.isAuthenticated();
      setIsAuthenticated(isAuthenticated);
      if (isAuthenticated) {
        const user = await auth0FromHook.getUser();
        setUser(user);
      }
      setLoading(false);
    };
    initAuth0();
    // eslint-disable-next-line
  }, []);
  const loginWithPopup = async (params = {}) => {
    setPopupOpen(true);
    try {
      await auth0Client.loginWithPopup(params);
    } catch (error) {
      console.error(error);
    } finally {
      setPopupOpen(false);
    }
    const user = await auth0Client.getUser();
    setUser(user);
    setIsAuthenticated(true);
  };
  const handleRedirectCallback = async () => {
    setLoading(true);
    await auth0Client.handleRedirectCallback();
    const user = await auth0Client.getUser();
    setLoading(false);
    setIsAuthenticated(true);
    setUser(user);
  };
  return (
    <Auth0Context.Provider
      value={{
        isAuthenticated,
        user,
        loading,
        popupOpen,
        loginWithPopup,
        handleRedirectCallback,
        getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
        loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
        getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
        getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
        logout: (...p) => auth0Client.logout(...p)
      }}
    >
      {children}
    </Auth0Context.Provider>
  );
};

This wrapper creates functions that easily integrate with the rest of the React components to allow the user to log in and log out as well as display user information.

Now create another file that will help handle redirects after a user signs in.

Create a new file called history.js in the src directory.

touch history.js

Now paste in the following:

// src/history.js

import { createBrowserHistory } from "history";
export default createBrowserHistory();

Next, you need to integrate the Auth0 SDK into the React application.

Open up src/index.js and replace it with:

// src/index.js
import 'bootstrap/dist/css/bootstrap.css';
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { Auth0Provider } from "./react-auth0-spa";
import config from "./auth_config.json";
import history from "./history";

// A function that routes the user to the right place
// after login
const onRedirectCallback = appState => {
  history.push(
    appState && appState.targetUrl
      ? appState.targetUrl
      : window.location.pathname
  );
};

// Wrap App in the Auth0Provider component
// The domain and client_id values will be found in your Auth0 dashboard
ReactDOM.render(
  <Auth0Provider
    domain={config.domain}
    client_id={config.clientId}
    redirect_uri={window.location.origin}
    onRedirectCallback={onRedirectCallback}
  >
    <App />
  </Auth0Provider>,
  document.getElementById("root")
);

serviceWorker.unregister();

This wraps the <App> component in the <Auth0Provider> component. You now need to fill in the domain and client_id values.

If you haven't already, sign up for a free Auth0 account now.

Try out the most powerful authentication platform for free.Get started →
  • Once you're in the dashboard, click on Create Application in the top right corner.
  • Name your application "React Go App" or anything you'd like.
  • Select "Single Page Web Applications", and click "Create".
  • Click on "Settings".
  • Scroll down and fill in Allowed Callback URLs, Allowed Logout URLs, and Allowed Web Origins with http://localhost:3000 or whatever you're using for your React application.
  • Click "Save changes" and scroll back up to the top.

You need to grab the Client ID and Domain values and pull them into your React app.

Instead of dropping these straight into src/index.js, create a new file, auth_config.json in the src directory:

touch auth_config.json

These aren't necessarily sensitive values, but it's still best practice to not commit them to a GitHub repository. With this new file, you can add it to your .gitignore to keep it out of your repo.

Open up the newly created auth_config.json and paste in the following:

{
  "domain": "YOUR_DOMAIN",
  "clientId": "YOUR_CLIENT_ID"
}

Replace these two values with the two from your own Auth0 dashboard.

Now go ahead and fill in the components to see it all come together.

App component

The App component is going to kick off your React application. Open up src/components/App.js and replace it with:

import React from 'react';
import './App.css';
import Home from './components/Home.js';
import LoggedIn from './components/LoggedIn.js';
import { useAuth0 } from "./react-auth0-spa";

const App = () => {
  const { isAuthenticated } = useAuth0();

  const { loading } = useAuth0();

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div className="App">
      {!isAuthenticated && (
        <Home />
      )}

      {isAuthenticated && <LoggedIn />}
    </div>
  );
};

export default App;

Home component

The Home component will be displayed when a user is not yet logged in. Open up src/components/Home.js and paste in:

import React, { Fragment } from "react";
import { useAuth0 } from "../react-auth0-spa";

const Home = () => {
  const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
  return (
    <Fragment>
      <div className="container">
        <div className="jumbotron text-center mt-5">
          <h1>We R VR</h1>
          <p>Provide valuable feedback to VR experience developers.</p>
          {!isAuthenticated && (
            <button className="btn btn-primary btn-lg btn-login btn-block" onClick={() => loginWithRedirect({})}>Sign in</button>
          )}
        </div>
      </div>
    </Fragment>
  );
};

export default Home;

LoggedIn component

The LoggedIn component will be displayed when a user has a valid access token, which means they are logged in. This could be split into multiple components, but this tutorial will just keep it all together for simplicity. Open src/components/LoggedIn.js and paste in the following:

import React, { useState } from "react";
import { useAuth0 } from "../react-auth0-spa";
import { FiThumbsUp, FiThumbsDown } from 'react-icons/fi';

const LoggedIn = () => {
  const [voted, setVoted] = useState(['']);
  const [products, setProducts] = useState([
    {
      id: 1,
      Name: "World of Authcraft",
      Slug: "world-of-authcraft",
      Description:
        "Battle bugs and protect yourself from invaders while you explore a scary world with no security",
    },
    {
      id: 2,
      Name: "Ocean Explorer",
      Slug: "ocean-explorer",
      Description:
        "Explore the depths of the sea in this one of a kind underwater experience",
    },
  ]);

  const { getTokenSilently, loading, user, logout, isAuthenticated } = useAuth0();

  const vote = (type) => {
    alert(type);
  }

  if (loading || !user) {
    return <div>Loading...</div>;
  }

  return (
    <div className="container">
      <div className="jumbotron text-center mt-5">
        {isAuthenticated && <span className="btn btn-primary float-right" onClick={() => logout()}>Log out</span>}
        <h1>We R VR</h1>
        <p>
          Hi, {user.name}! Below you'll find the latest games that need feedback. Please provide honest feedback so developers can make the best games.
        </p>
        <div className="row">
          {products.map(function (product) {
            return (
              <div className="col-sm-4">
                <div className="card">
                  <div className="card-header">
                    {product.Name}
                    <span className="float-left">{voted}</span>
                  </div>
                  <div className="card-body">{product.Description}</div>
                  <div className="card-footer">
                    <a onClick={() => vote("Upvoted")} className="btn btn-default float-left">
                        <FiThumbsUp />
                    </a>
                    <a onClick={() => vote("Downvoted")} className="btn btn-default float-right">
                      <FiThumbsDown />
                    </a>
                  </div>
                </div>
              </div>  
            );
          })}
        </div>
      </div>
    </div>
  );
};

export default LoggedIn;

There is currently some mock product data used for display purposes. Once you connect this to your backend, you'll pull this data from the API instead.

Note: Never rely on the frontend to protect sensitive data. Hard-coded data like this will still be accessible if someone reads through your source code. Any data that you want protected should always be sent from the backend, pending proper authorization.

Now it's time to test this out. Start the React application in the terminal with:

npm start

We R VR React Login Page

You can now sign in and view the page with the two sample games. Click on the thumbs up or down icons, and it will fire an alert.

We R VR React Login Page

Because you only want authorized users to be able to view and interact with the VR game data, you need to secure it from the backend. Go ahead and jump back to the Golang side and set that up.

Authorization with Golang

Adding authorization will allow you to protect your API. Since your app deals with projects that are in active development, you don’t want any data to be publicly available.

You've already accomplished the first step, which requires the user to sign in to your React application. The next step is to pull the data from the Go application, but only if the user has a valid access token.

First, clean up the Go API code from the previous section. Replace everything in main.go with the following:

package main

// Import our dependencies. We'll use the standard HTTP library as well as the gorilla router for this app
import (
  "encoding/json"
  "errors"
  "github.com/auth0/go-jwt-middleware"
  "github.com/dgrijalva/jwt-go"
  "github.com/gorilla/handlers"
  "github.com/gorilla/mux"
  "github.com/rs/cors"
  "net/http"
)

/* We will first create a new type called Product
   This type will contain information about VR experiences */
type Product struct {
    Id          int
    Name        string
    Slug        string
    Description string
}

var products = []Product{
    Product{Id: 1, Name: "World of Authcraft", Slug: "world-of-authcraft", Description: "Battle bugs and protect yourself from invaders while you explore a scary world with no security"},
    Product{Id: 2, Name: "Ocean Explorer", Slug: "ocean-explorer", Description: "Explore the depths of the sea in this one of a kind underwater experience"},
    Product{Id: 3, Name: "Dinosaur Park", Slug: "dinosaur-park", Description: "Go back 65 million years in the past and ride a T-Rex"},
    Product{Id: 4, Name: "Cars VR", Slug: "cars-vr", Description: "Get behind the wheel of the fastest cars in the world."},
    Product{Id: 5, Name: "Robin Hood", Slug: "robin-hood", Description: "Pick up the bow and arrow and master the art of archery"},
    Product{Id: 6, Name: "Real World VR", Slug: "real-world-vr", Description: "Explore the seven wonders of the world in VR"},
}

func main() {

  jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
        // Will fill in next
  })
  
    r := mux.NewRouter()

    r.Handle("/", http.FileServer(http.Dir("./views/")))
  r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))

  r.Handle("/products", jwtMiddleware.Handler(ProductsHandler)).Methods("GET")
    r.Handle("/products/{slug}/feedback", jwtMiddleware.Handler(AddFeedbackHandler)).Methods("POST")

  // For dev only - Set up CORS so React client can consume our API
  corsWrapper := cors.New(cors.Options{
        AllowedMethods: []string{"GET", "POST"},
        AllowedHeaders: []string{"Content-Type", "Origin", "Accept", "*"},
  })
  
    http.ListenAndServe(":8080", corsWrapper.Handler(r))
}

var ProductsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    payload, _ := json.Marshal(products)

    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(payload))
})

var AddFeedbackHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    var product Product
    vars := mux.Vars(r)
    slug := vars["slug"]

    for _, p := range products {
        if p.Slug == slug {
            product = p
        }
    }

    w.Header().Set("Content-Type", "application/json")
    if product.Slug != "" {
        payload, _ := json.Marshal(product)
        w.Write([]byte(payload))
    } else {
        http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
    }
})

You still have your products struct and two endpoints to GET products and POST feedback. You also have two handlers. Since you're updating this application to use Auth0 for authorization and authentication, a new handler, jwtMiddleware, has been added. This will wrap around the endpoints you want to protect. You also need to enable CORS to allow cross-origin requests so that the React client can consume the API.

Note: If you find that some of the imports disappear on save, make sure that your code editor isn't set to remove unused imports on save. In VS Code, open up your "User Settings" and search for gofmt. If it's set to goreturn, change that to gofmt.

Create the middleware

Next, create the middleware that will be applied to your endpoints. This middleware will check if an access token exists and is valid. If it passes the checks, the request will proceed. If not, a 401 Authorization error is returned.

First, you need to define the structs to be used in the middleware. In main.go, add the following underneath the imports section:

package main

import (
  // ...
) 

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"`
}

The JSONWebKeys struct holds fields related to the JSON Web Key Set for this API. These keys contain the public keys, which will be used to verify JWTs.

Create the middleware now so that you can see how these are used. In main.go, find the empty jwtMiddleware and replace it with:

// imports
// ...

func main() {
  jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options {
    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
      // Verify 'aud' claim
      aud := "YOUR_API_IDENTIFIER"
      checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false)
      if !checkAud {
          return token, errors.New("Invalid audience.")
      }
      // Verify 'iss' claim
      iss := "https://YOUR_DOMAIN/"
      checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false)
      if !checkIss {
          return token, errors.New("Invalid issuer.")
      }

      cert, err := getPemCert(token)
      if err != nil {
          panic(err.Error())
      }

      result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
      return result, nil
    },
    SigningMethod: jwt.SigningMethodRS256,
  })

  // ...
}

// ...

You have the new auth0/go-jwt-middleware middleware function that will validate tokens coming from Auth0. There are some values here that you'll need to fill in, so take note of those, and you'll come back to them once setup is finished.

Next, you need to create the function to grab the JSON Web Key Set and return the certificate with the public key. Still in main.go, add this function at the very bottom after AddFeedbackHandler():

// main.go

// imports here
// ...

func main() {
  // ...
}

func getPemCert(token *jwt.Token) (string, error) {
    cert := ""
    resp, err := http.Get("https://YOUR_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
    }

    for k, _ := range jwks.Keys {
        if token.Header["kid"] == jwks.Keys[k].Kid {
            cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----"
        }
    }

    if cert == "" {
        err := errors.New("Unable to find appropriate key.")
        return cert, err
    }

    return cert, nil
}

Now that the middleware is set up, you need to apply it to the endpoints you want to protect.

Apply the middleware

Scroll to where you defined the routes in the main.go file and you'll find that the jwtMiddleware has already been applied on the /products and /products/{slug}/feedback endpoints:

r.Handle("/products", jwtMiddleware.Handler(ProductsHandler)).Methods("GET")
r.Handle("/products/{slug}/feedback", jwtMiddleware.Handler(AddFeedbackHandler)).Methods("POST")

Any private endpoints that you want to protect in the future should also use jwtMiddleware.

Add the imports

Now bring in the Auth0 JWT Middleware package and the CORS package that you added to your imports earlier.

  • auth0/go-jwt-middleware — Auth0 package that fetches your Auth0 public key and checks for JWTs on HTTP requests
  • rs/cors — CORS is a net/http handler implementing CORS specification in Go

In the terminal in the root of the Go project, install these new packages with:

go get

Note: You can alternatively run go build main.go here, which will also grab the dependencies and compile.

Setting up the Auth0 API

Now you need to register your Go API with Auth0. If you haven't already, sign up for a free Auth0 account.

Navigate to the APIs section and create a new API by clicking the Create API button.

Create We-R-VR Auth0 API Application

Give your API a Name and an Identifier. These can be anything you'd like, but for the Identifier, it's best to use a URL format for naming convention purposes. This doesn't have to be a publicly available URL, and Auth0 will never call it. You can leave the Signing Algorithm as is (RS256). Once you have that filled out, click Create.

Back in main.go, there are some values that need updating. In your Auth0 dashboard, you can easily find these by clicking on Quick Start and looking that the sample C# code listed.

Grab the value for options.Authority and copy it. Now back in main.go in jwtMiddleware, there's a variable called aud. Replace YOUR_API_IDENTIFIER with the value you copied from your Auth0 dashboard. This is your API identifier.

Next, copy the value of options.Audience from your Auth0 dashboard and replace https://YOUR_DOMAIN/ with it.

Note: Make sure you include the trailing slash here.

Now scroll down to getPemCert() and find this line:

resp, err := http.Get("https://YOUR_DOMAIN/.well-known/jwks.json")

Replace YOUR_DOMAIN with the copied value.

The final step here is to rerun the application. This can be done in your terminal with:

go run .

Now the application is ready to test!

Testing it out

You can use a tool like Postman to test that access control is working properly. If you don't have Postman, check out the next section to test with cURL.

Testing with Postman

Your /products endpoint is currently protected and requires a valid token to access. Let's make sure this works as expected. In Postman, paste in http://localhost:8080/products, make sure it's set to a GET request, and click "Send". You should get back this response: Required authorization token not found.

Golang authorization required Postman

Now try sending an access token along with the request. Back in your Auth0 dashboard, go to the API that you created earlier. Click on the Test tab and scroll down to where it says Response. Copy the value of the access token.

Back in Postman, click on Headers and fill in the first row as follows:

  • KEY — Authorization
  • VALUE — Bearer pasteyourtokenhere

Click Send, and the products list should be returned!

Golang authorized Postman

Testing with cURL

This can also be tested straight from the command line with cURL. Just replace yourtokenhere with your test token and then paste it into your terminal.

curl --request GET \
  --url http://localhost:8080/products \
  --header 'authorization: Bearer yourtokenhere'

Connecting React and Go

So you now have users able to sign in on the React side and API authorization implemented on the backend. The final step is to connect the two. The goal here is that a user will sign into the frontend, and then you'll send their token to the backend with the request for product data. If their access token is valid, then you'll return the data.

To accomplish this, you only need to update the React side.

Calling the Go API with React

Open up the React project and look at the LoggedIn.js file. Right now, you have the products data hard-coded in here. The problem with this is that a user can still easily find this data if they wanted, even though you want them to be signed in to view it.

If you're curious how this is possible, make sure your React app is running, and then go to http://localhost:3000/static/js/main.chunk.js in your browser (incognito so you can make sure you're not signed in). Now search for "World of Authcraft", and sure enough, there is the data.

This is why it's always essential to serve private data from the backend where the code isn't accessible. To do this, you need to make an HTTP request from your frontend to your backend to get the products. This request must be accompanied by a valid access token. So once the user signs in, you'll pass their access token along with the request.

In src/index.js, updated ReactDOM.render() as follows:

ReactDOM.render(
  <Auth0Provider
    domain={config.domain}
    client_id={config.clientId}
    redirect_uri={window.location.origin}
    audience={config.audience}     // NEW - specify the audience value
    onRedirectCallback={onRedirectCallback}
  >
    <App />
  </Auth0Provider>,
  document.getElementById("root")
);

You're adding an audience value here, so you have to update your auth_config.json file with that value:

{
  "domain": "YOUR_DOMAIN",
  "clientId": "YOUR_CLIENT_ID",
  "audience": "YOUR_API_IDENTIFIER"
}

The audience can be found back in the Auth0 dashboard. Click on the Go API you created earlier and then copy the value for Identifer under Settings. Paste this in as audience.

Next, open up LoggedIn.js and replace it with the following:

import React, { useState, useEffect } from "react";
import { useAuth0 } from "../react-auth0-spa";
import { FiThumbsUp, FiThumbsDown } from "react-icons/fi";

const LoggedIn = () => {
  const [products, setProducts] = useState([]);
  const [voted, setVoted] = useState({
    "world-of-authcraft": "",
    "ocean-explorer": "",
    "dinosaur-park": "",
    "cars-vr": "",
    "robin-hood": "",
    "real-world-vr": "",
  });

  const {
    getTokenSilently,
    loading,
    user,
    logout,
    isAuthenticated,
  } = useAuth0();

  useEffect(() => {
    const getProducts = async () => {
      try {
        const token = await getTokenSilently();
        // Send a GET request to the server and add the signed in user's
        // access token in the Authorization header
        const response = await fetch("http://localhost:8080/products", {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        });

        const responseData = await response.json();

        setProducts(responseData);
      } catch (error) {
        console.error(error);
      }
    };

    getProducts();
  }, []);

  const vote = async (slug, type, index) => {
    try {
      const token = await getTokenSilently();
      // Send a POST request to the Go server for the selected product
      // with the vote type
      const response = await fetch(
        `http://localhost:8080/products/${slug}/feedback`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${token}`,
          },
          body: JSON.stringify({ vote: type }),
        }
      );
      // Since this is just for demonstration and we're not actually
      // persisting this data, we'll just set the product vote status here
      // if the product exists
      if (response.ok) {
        setVoted({
          ...voted,
          [slug]: [type],
        });
      } else console.log(response.status);
    } catch (error) {
      console.error(error);
    }
  };

  if (loading || !user) {
    return <div>Loading...</div>;
  }

  return (
    <div className="container">
      <div className="jumbotron text-center mt-5">
        {isAuthenticated && (
          <span
            className="btn btn-primary float-right"
            onClick={() => logout()}
          >
            Log out
          </span>
        )}
        <h1>We R VR</h1>
        <p>
          Hi, {user.name}! Below you'll find the latest games that need
          feedback. Please provide honest feedback so developers can make the
          best games.
        </p>
        <div className="row">
          {products.map(function (product, index) {
            const prodSlug = product.Slug;
            return (
              <div className="col-sm-4" key={index}>
                <div className="card mb-4">
                  <div className="card-header">{product.Name}</div>
                  <div className="card-body">{product.Description}</div>
                  <div className="card-footer">
                    <a onClick={() => vote(product.Slug, "Upvoted", index)}
                      className="btn btn-default float-left">
                      <FiThumbsUp />
                    </a>
                    <small className="text-muted">{voted[prodSlug]}</small>
                    <a onClick={() => vote(product.Slug, "Downvoted", index)}
                      className="btn btn-default float-right">
                      <FiThumbsDown />
                    </a>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
};

export default LoggedIn;

Here's what changed:

  • getProducts() — Instead of hardcoding the data, you're now pulling it in from uour Go server (http://localhost:8080/products)!
  • vote() — You're also making a POST request to the Go server to reflect your vote for a given product. This demo is not persisting the data, but it's still hitting the server and saving the vote in memory as long as the server returns a 200 OK response.
  • Authorization header — When you set up your Go server, you created a middleware that expects an access token. You're passing the signed-in user's access token along with the request, as you can see here:
const token = await getTokenSilently();

const response = await fetch("http://localhost:8080/products", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

Test it out

To test everything out, make sure you have both your Go server and React client running.

Build and run Go server

Make sure you're in the directory of your Go project in your terminal and run:

go run .

Run your React application

Make sure you're in the directory of your React project in your terminal and run:

npm start

We-R-Vr Go React final application

Conclusion

That's it! If you made it all the way through, pat yourself on the back because you just learned how to:

  • Build an API with Go
  • Connect a Go server to a React client
  • Set up authentication in a React app
  • Secure a Go API
  • Create middleware in Go

    To conclude, Go is an excellent language for building scalable and highly performant APIs. Make sure to reach out in the comments if you have any questions, and thanks for reading!

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon