Securing APIs via JWT in GoLang
Json Web Token (JWT)
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Starting with Go Application
You can start directly with an empty Go project. We basically need a client and a server.
Creating a Simple Client on Go
We’ll start with an OAuth Client in GoLang. We’ll start with a main file, where we’ll need to import the jwt-go library.
import "github.com/golang-jwt/jwt/v4"
Creating a JWT generator
You can create a JWT generator function using the jwt-go library
func GenerateJWTToken() (string, error) {
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["authorized"] = true
claims["user"] = "@binator_1308"
claims["exp"] = time.Now().Add(time.Minute * 30).Unix()
tokenString, err := token.SignedString(mySignedKey)
if err != nil {
fmt.Errorf("generating JWT Token failed")
return "", err
}
return tokenString, nil
}
You can run the method to check if the client can generate the JWT token
Next we can create a method that exposes this feature via http and can create a valid JWT token on a new request
func HomePage(w http.ResponseWriter, r *http.Request) {
validToken, err := GenerateJWTToken()
if err != nil {
fmt.Fprintf(w, err.Error())
}
fmt.Fprintf(w, validToken)
}
We’ll also need to create a handler for this. We’ll use the port 9001 for client
func handleRequests() {
http.HandleFunc("/", HomePage)
log.Fatal(http.ListenAndServe(":9001", nil))
}
You should be able to see a JWT token generated on running the application and visiting localhost:9001
We now have a basic client up and running that can generate a JWT token.
Creating a Simple Server on Go
We’ll now start with a simple server. We can initialise a simple main file.
We can create a dummy homepage that contains some secret information
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Dummy secret information")
}
We’ll also need a handler that serves it on port 9000
func handleRequests() {
http.HandleFunc("/", homePage)
log.Fatal(http.ListenAndServe(":9000", nil))
}
We should be able to see the dummy secret information on localhost:9000
Now we want to limit this information to validated users who are authorized to access it.
We’ll start by creating a function to authorize requests. Let’s have a basic check for Token header being present for now.
func isAuthorized(endpoint func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header["Token"] != nil {
} else {
fmt.Fprintf(w, "Not Authorized")
}
})
}
Next we’ll replace the HandleFunc method with Handle within the handler, and wrap it with our authentication method.
Now we can move on to the authorization method. We can parse the Token header using the signing key and HMAC algorithm (which we used in our client). If it’s valid then we can serve the original endpoint.
func isAuthorized(endpoint func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header["Token"] != nil {
token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("there was an error")
}
return mySigningKey, nil
})
if err != nil {
fmt.Fprintf(w, err.Error())
}
if token.Valid {
endpoint(w, r)
}
} else {
fmt.Fprintf(w, "Not Authorized")
}
})
}
Now we can run the server again. It should still throw the Not Authorized exception on the browser
But on running it on postman with the valid header from client we should be able to fetch the secret information.
We can check the correct header on json.io debugger .The response should be something like this:
Authenticate requests made to server in Client
We need to add the TOKEN header with the valid JWT to the client to get the request authorized.
client := &http.Client{}
req, _ := http.NewRequest("GET", "http://localhost:9000", nil)
req.Header.Set("TOKEN", validToken)
res, err := client.Do(req)
if (err != nil) {
fmt.Fprintf(w, err.Error())
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Fprintf(w, err.Error())
}
With this our token should be valid and all our requests to server should be serviced.
Conclusion
We now have a basic server and client on GoLang, and have authorized a ReST API via JWT token. The API is now secured.
The code for this article can be found here