Today, let's have a look at how you can use JWT Authentication in Go. We are going to use it in combination with Labstack Echo (my preferred tool for creating web servers with Go).

Let's define a simple webserver supporting JWT Authentication like this:

package main

import (
    "net/http"
    "time"

    "github.com/dgrijalva/jwt-go"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "github.com/pieterclaerhout/go-log"
)

const secret = "secret"

type jwtCustomClaims struct {
    Name  string `json:"name"`
    UUID  string `json:"uuid"`
    Admin bool   `json:"admin"`
    jwt.StandardClaims
}

func login(c echo.Context) error {

    username := c.FormValue("username")
    password := c.FormValue("password")

    if username != "pieter" || password != "claerhout" {
        return echo.ErrUnauthorized
    }

    claims := &jwtCustomClaims{
        Name:  "Pieter Claerhout",
        UUID:  "9E98C454-C7AC-4330-B2EF-983765E00547",
        Admin: true,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    t, err := token.SignedString([]byte(secret))
    if err != nil {
        return err
    }

    return c.JSON(http.StatusOK, map[string]string{
        "token": t,
    })
}

func accessible(c echo.Context) error {
    return c.String(http.StatusOK, "Accessible")
}

func restricted(c echo.Context) error {
    user := c.Get("user").(*jwt.Token)
    claims := user.Claims.(*jwtCustomClaims)
    log.InfoDump(claims, "claims")
    name := claims.Name
    return c.String(http.StatusOK, "Welcome "+name+"!")
}

func main() {

    e := echo.New()
    e.HideBanner = true
    e.HidePort = true

    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    e.POST("/login", login)
    e.GET("/", accessible)

    r := e.Group("/restricted")
    config := middleware.JWTConfig{
        Claims:     &jwtCustomClaims{},
        SigningKey: []byte("secret"),
    }
    r.Use(middleware.JWTWithConfig(config))
    r.GET("", restricted)

    e.Logger.Fatal(e.Start(":8080"))
}

Looking at the code, there are several endpoints defined:

  • /login: can be used to get a JWT token based on your login credentials
  • /: an endpoint which doesn't require authentication
  • /restricted: an endpoint which does require a valid JWT authentication token

The server will register itself on port 8080 and we're ready to go.

You can start the server like:

$ go run server.go

You can then use the /login endpoint to get a token:

$ curl -s -X POST -d 'username=pieter' -d 'password=claerhout' http://localhost:8080/login
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUGlldGVyIENsYWVyaG91dCIsInV1aWQiOiI5RTk4QzQ1NC1DN0FDLTQzMzAtQjJFRi05ODM3NjVFMDA1NDciLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjEwMzgzNTU0fQ.JqJ4x2yPXOmxJB1n4GqpfKnIMyorX2zF7kbWbfchX4c"}

You can then use this token to access the restricted URL:

$ curl http://localhost:8080/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUGlldGVyIENsYWVyaG91dCIsInV1aWQiOiI5RTk4QzQ1NC1DN0FDLTQzMzAtQjJFRi05ODM3NjVFMDA1NDciLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjEwMzgzNTU0fQ.JqJ4x2yPXOmxJB1n4GqpfKnIMyorX2zF7kbWbfchX4c"
Welcome Pieter Claerhout!

In addition, the server log will show you the contents of the authentication token:

claims &main.jwtCustomClaims{
  Name: "Pieter Claerhout",
  UUID: "9E98C454-C7AC-4330-B2EF-983765E00547",
  Admin: true,
  StandardClaims: jwt.StandardClaims{
    Audience: "",
    ExpiresAt: 1610383647,
    Id: "",
    IssuedAt: 0,
    Issuer: "",
    NotBefore: 0,
    Subject: "",
  },
}

The complete working implementation can be found on GitHub.

Related Posts

  • Looking up a CNAME in Go
  • Gotcha with defer in Go
  • Parsing a key pair from a PEM file in Go
  • Using the Docker client from Go part 1
  • Embedding file with Go 1.16