Over the past few weeks I’ve been doing a lot of investigation into JSON Web Tokens (JWT) for authentication in APIs. If you’ve been keeping up, you’ll remember I wrote about JWT authentication in a Node.js application as well as building a client facing NativeScript and Angular mobile application that made use of the Node.js backend. This is great, but what if you’re not very fond of JavaScript development?
We’re going to see how to create a backend API that creates and validates JSON Web Tokens using the Go programming language. This teach us how to create an API that offers an authentication mechanism outside of sessions and cookies, which typically are not available when working with an API.
If you’re not familiar with JSON Web Tokens, the concept is simple. The client sends login credentials which are validated against a database. If they are valid, a token is generated based on a sample set of data and a secret key that only the server knows about. This token is returned to the client and the client uses this in any future request. When a request contains a JWT, it is validated using the same secret key that was used to sign it.
To keep this project simple and easy to understand, we’re going to start from scratch. All code will exist in a single file that will serve several API endpoints.
At this point you should have already defined a $GOPATH variable, a requirement in Go development. Create a $GOPATH/src/github.com/nraboy/jwtproject/main.go file, but we’re not quite ready to develop in it yet. There are several project dependencies that must be downloaded first.
To make life easier, we’re going to use a few great libraries in our project. Execute the following commands from your Command Prompt or Terminal:
go get github.com/gorilla/mux
go get github.com/gorilla/context
go get github.com/mitchellh/mapstructure
go get github.com/dgrijalva/jwt-go
So what do each of these libraries do?
The mux
library will make the development of our Golang API a lot easier. Because at some point we plan to create a middleware, we’re going to need a way to pass around data, which is where the context
library comes into play. The jwt-go
library will allow us to create and validate JWT data and since decoded token data is in map format, we can use mapstructure
to convert the data into a custom data structure.
Now we can open that project file that we had previously created. Open the project’s $GOPATH/src/github.com/nraboy/jwtproject/main.go file and include the following code:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/context"
"github.com/gorilla/mux"
"github.com/mitchellh/mapstructure"
)
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}
type JwtToken struct {
Token string `json:"token"`
}
type Exception struct {
Message string `json:"message"`
}
func CreateTokenEndpoint(w http.ResponseWriter, req *http.Request) {}
func ProtectedEndpoint(w http.ResponseWriter, req *http.Request) {}
func main() {
router := mux.NewRouter()
fmt.Println("Starting the application...")
router.HandleFunc("/authenticate", CreateTokenEndpoint).Methods("POST")
router.HandleFunc("/protected", ProtectedEndpoint).Methods("GET")
log.Fatal(http.ListenAndServe(":12345", router))
}
You’ll notice in the above that we’ve imported everything we installed, plus a few other Golang project dependencies that are already available with Go.
The User
structure will store credential data passed from the client as well as the decoded JWT data. The JwtToken
data structure will hold our token data and the Exception
structure will hold any error messages we want to return. These are all less important to the rest of the project.
In the main
function we define our router and two endpoints. The application itself will be served on port 12345.
So let’s dig deeper into our two endpoint functions. One endpoint will accept credential data and create a token and the other will validate the token and display protected data.
This example will not use a database so we will be creating a token from whatever the user has provided us. Let’s evaluate the CreateTokenEndpoint
function:
func CreateTokenEndpoint(w http.ResponseWriter, req *http.Request) {
var user User
_ = json.NewDecoder(req.Body).Decode(&user)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": user.Username,
})
tokenString, error := token.SignedString([]byte("secret"))
if error != nil {
fmt.Println(error)
}
json.NewEncoder(w).Encode(JwtToken{Token: tokenString})
}
The above function was called via a POST request. Both a username
and password
should be present in the POST request even though we’re not validating for it.
The POST body is deserialized and added to our custom structure, at which point we use it to create new JWT data. While we would realistically want to use the username
and password
for prior account validation, the password should not be stored in the JWT itself for security reasons. The JWT is signed using a secret password, in this case secret
which probably isn’t strong enough in production. When we have the token it is returned back to the client that requested it.
Remember, in production you’d also have your database validation to verify that the credentials are accurate.
Now let’s have a look at the endpoint that will validate the JWT data. Check out the ProtectedEndpoint
API endpoint:
func ProtectedEndpoint(w http.ResponseWriter, req *http.Request) {
params := req.URL.Query()
token, _ := jwt.Parse(params["token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("There was an error")
}
return []byte("secret"), nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
var user User
mapstructure.Decode(claims, &user)
json.NewEncoder(w).Encode(user)
} else {
json.NewEncoder(w).Encode(Exception{Message: "Invalid authorization token"})
}
}
The above endpoint expects that query parameters exist in the request. The query parameter we’re looking for is the token
parameter. Typically you would use a header instead of a query parameter, but we’re going to look at this soon.
With the passed token, we can try to decode it using the same secret password that was used to sign it. If it is valid we can convert the token contents into our User
structure and return it or throw an error.
This is all good, but it would be painful to have to add all the validation logic to every protected endpoint. Instead, we should create a middleware that does the validation for us.
We’re going to create a new endpoint and a middleware that can be used for this new endpoint and any other. To keep the flow, let’s worry about token validation first. Create the following:
func ValidateMiddleware(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authorizationHeader := req.Header.Get("authorization")
if authorizationHeader != "" {
bearerToken := strings.Split(authorizationHeader, " ")
if len(bearerToken) == 2 {
token, error := jwt.Parse(bearerToken[1], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("There was an error")
}
return []byte("secret"), nil
})
if error != nil {
json.NewEncoder(w).Encode(Exception{Message: error.Error()})
return
}
if token.Valid {
context.Set(req, "decoded", token.Claims)
next(w, req)
} else {
json.NewEncoder(w).Encode(Exception{Message: "Invalid authorization token"})
}
}
} else {
json.NewEncoder(w).Encode(Exception{Message: "An authorization header is required"})
}
})
}
The above function acts as a wrapper to our real endpoint function which is why the parameter will be another HandlerFunc
function. This middleware function will also expect that the token exists as an authorization header rather than a query parameter.
If the data checks out, we can set a decoded
parameter that contains the decoded token data. This parameter will be passed from the middleware to the protected endpoint function. The protected function is called like a callback via whatever was passed.
So what does this new endpoint look like? How about something like this:
func TestEndpoint(w http.ResponseWriter, req *http.Request) {
decoded := context.Get(req, "decoded")
var user User
mapstructure.Decode(decoded.(jwt.MapClaims), &user)
json.NewEncoder(w).Encode(user)
}
In the above function we can access the decoded data that was set and choose to use it to gather any information to be used in our protected response. Since we don’t have any database activity, we will just return the decoded data.
The main
function would now look like this:
func main() {
router := mux.NewRouter()
fmt.Println("Starting the application...")
router.HandleFunc("/authenticate", CreateTokenEndpoint).Methods("POST")
router.HandleFunc("/protected", ProtectedEndpoint).Methods("GET")
router.HandleFunc("/test", ValidateMiddleware(TestEndpoint)).Methods("GET")
log.Fatal(http.ListenAndServe(":12345", router))
}
Not bad right? Just to re-iterate, the middleware function can be used on any endpoint we wish to protect, cutting down a lot of our code.
If you’ve understood the project up until this point, we should be able to test it. From the Terminal or Command Prompt, execute the following command:
go run *.go
If you don’t get any errors, the API can be accessed at http://localhost:12345 through a front-end client or testing tool like Postman.
Don’t want to go through all the steps of this project? You can download the full project source code here. With the source code downloaded and extracted, give it a run after you’ve installed the dependencies.
You just saw how to use JSON Web Tokens (JWT) in a Golang RESTful API. If you’d like to use a front-end application to test this API, check out the NativeScript with Angular example that I wrote about previously. If Golang isn’t really your thing, you can also check out a Node.js version of this same tutorial that I had written about.
If you’d like to download the full source code to this project, it can be found here. Just remember not to share it without my permission.