Build a JWT Library in GOLANG

Published Dec 22, 2017Last updated Feb 19, 2018
Build a JWT Library in GOLANG

I woke up this morning and felt like sharing something tangible for Golang.

WHY JWT?
While learning Golang, I decided to build a recipe web service using GO. When it came time to authenticate my endpoints, I decided to build my very own JWT library. I'll be walking you through my solution.
Feel free criticize anything you find wrong — I get to learn from your comments.

WHAT IS JWT?

jwt.io definition: "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."

This allows you to write web services that can be consumed over the web, mobile, desktop, and any other device that can communicate with a given server.
It consists of three parts:

  1. Header - base64encoded, defines the algorithm used for hashing the signature and the type of JWT. We'll be using the HS256.
    Others include HS384, HS512, and RS256.
  2. Payload - contains information about the issuer, expiration date, the user of the token e.t.c eg expiration date (ESP).
  3. Signature - contains a concatenated Hash of both encoded header and payload using the algorithm in the header.

All of these are concatenated with a dot to form a token. The output looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3OD.kwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95Or.M7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

LET'S GET IT CRACKING
From the explanation above, JWT tokens contain three parts. The first and second part is a base64Encode header containing the ALGO and TYP of the algorithm used and the third part consists of the payload.

Below is a list of the internal packages needed for this library to work:

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "errors"
    "strings"
    "time"
)

func Base64Encode(src string) string {
    data := []byte(src)
    str := base64.StdEncoding.EncodeToString(data)
    return str
}

The above fuction takes in a string and returns a base64Encoded string.

Next, we will create a base64Decode method for decoding an encoded payload.

func Base64Decode(src string) (string, error) {
    decoded, err := base64.StdEncoding.DecodeString(src)
    if err != nil {
        errMsg := fmt.Errorf("Decoding Error %s", err)
        return "", errMsg
    }
    return string(decoded), nil
}

This method returns either a decode string or an error if decoding fails. I used the "fmt" module to generate an error. We can use the error module as well.

Now that we can encode and decode our string, let's create a method that hashes and compares the integrity of a hash.

func Hmac256(src string, secret string) string {
    key := []byte(secret)
    h := hmac.New(sha256.New, key)
    h.Write([]byte(src))
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

Using the Golang hmac library, I created a method that accepts a string and a secret key for hashing a string.

Now that we have a way of hashing our JWT, let's move on to creating a method for verifying if the token sent by the client is valid.

NB: Hash cannot be reversed. All you can do is hash the same character and compare it with a hashed value. If it evaluates to true, the character is what is in the hash.

Let's move on. I called verify method compareHmac — not the best name to give a function, but that's the best I've got.

func CompareHmac(message string, messageHmac string, secret string) bool {
    return messageHmac == Hmac256(message, secret)
}

This method takes in the string to verify, the hashed string and the secret key. Based on my previous explanation, all I did was to compare the hashed message with the hash.

Now that we have a way to hash our payload, let's create an encode method to generate a hash.

func Encode(payload Payload, secret string) string {
    type Header struct {
        Alg string `json:"alg"`
        Typ string `json:"typ"`
    }
    header := Header{
        Alg: "HS256",
        Typ: "JWT",
    }
    str, _ := json.Marshal(header)
    header := Base64Encode(string(str))
    encodedPayload, _ := json.Marshal(payload)
    signatureValue := header + "." + 
    Base64Encode(string(encodedPayload))
    return signatureValue + "." + Hmac256(signatureValue, secret)
}

The above method is pretty straightforward — all I did was to encode the header of our token, which tells the decoder that it's an "HS256" ALGO by converting the Header struct into a JSON and encoding it.

Moving on, I also concatenated the encoded header and payload to form the signature value. Finally, I returned a concatenation of the signature value and the hash of that signature value using the secrete I passed in.

signatureValue := header + "." + Base64Encode(string(encodedPayload))
return signatureValue + "." + Hmac256(signatureValue, secret)

Testing out the encoding method requires us to pass a payload of type Payload, which I defined below. It contains common params found in JWT payloads.

type Payload struct {
    Sub    string      `json:"sub,omitempty"`
    Exp    int64       `json:"exp,omitempty"`
    Iss    string      `json:"iss,omitempty"`
    Aud    string      `json:"aud,omitempty"`
    Public interface{} `json:"public,omitempty"`
}

If you have issues understanding how JSON works in go, check out this great resource link.

Now that we can encode, it would only be proper to decode the payload.

func Decode(jwt string, secret string) (interface{}, error) {
    token := strings.Split(jwt, ".")

    // check if the jwt token contains
    // header, payload and token
    if len(token) != 3 {
        splitErr := errors.New("Invalid token: token should contain header, payload and secret")
        return nil, splitErr
    }
    // decode payload
    decodedPayload, PayloadErr := Base64Decode(token[1])
    if PayloadErr != nil {
        return nil, fmt.Errorf("Invalid payload: %s", PayloadErr.Error())
    }
    payload := Payload{}

    // parses payload from string to a struct
    ParseErr := json.Unmarshal([]byte(decodedPayload), &payload)
    if ParseErr != nil {
        return nil, fmt.Errorf("Invalid payload: %s", ParseErr.Error())
    }

    if payload.Exp != 0 && time.Now().Unix() > payload.Exp {
        return nil, errors.New("Expired token: token has expired")
    }

    signatureValue := token[0] + "." + token[1]

    // verifies if the header and signature is exactly whats in
    // the signature
    if CompareHmac(signatureValue, token[2], secret) == false {
        return nil, errors.New("Invalid token")
    }

    return payload, nil
}

The above function takes in a token and a secret and performs the process listed below

  1. Splits the token
  2. Confirms if the token has the parts
  3. Decodes the payload
  4. Checks if the token has not yet expired
  5. Finally checks if the token is valid
  6. Returns the payload as an interface

The other way in which this token validation could be done is debatable, but regardless, we did check for multiple scenarios to make sure the token is valid.

You can find the complete code in this repo.

CONCLUSION.
This library was great for my learning purpose, but in real production code, I might go for an external library. Golang is a beautiful language and I love the way it forces me to adhere to good programming practices... although what's good programming practice is debatable these days. To get a better sense of how JWT works, try building it in your preferred language.

Discover and read more posts from Enaho Isiwele
get started