Building a JWT library in golang
This article assumes you’re
- A programmer
- Have a solid idea of what JWT is
- Understand basic golang
INTRO TO JWT
JWT consists of three parts
-
Header — defines the algorithm used for hashing the signature and the type of JWT. we’ll be using the HS256.
Others include HS384, HS512, RS2556 -
Payload — contains information about the issuer, expiration date, the user of the token e.t.c eg expiration date(ESP)
-
Signature — contains a concatenated value of both encoded header and payload Using the algorithm in the header to generate a hash. The returned hash is the signature
All these are concatenated with a dot, to form a token. The output looks like this.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3OD.kwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95Or.M7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
LET’S GET CODING
From the explanation above, JWT tokens consist of three parts. The first and second part is a base64Encode header containing the ALGO and TYP of the algorithm used and the second part consist of the payload.
Bellow 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”
)
// Base64Encode takes in a string and returns a base 64 encoded string
func Base64Encode(src string) string {
return strings.
TrimRight(base64.URLEncoding.
EncodeToString([]byte(src)), "=")
}
// Base64Encode takes in a base 64 encoded string and returns the //actual string or an error of it fails to decode the string
func Base64Decode(src string) (string, error) {
if := len(src) % 4; l > 0 {
src += strings.Repeat("=", 4-l)
}
decoded, err := base64.URLEncoding.DecodeString(src)
if err != nil {
errMsg := fmt.Errorf("Decoding Error %s", err)
return "", errMsg
}
return string(decoded), nil
}
The above two functions are self-explanatory. The first encodes a string and the other decode a base64 encoded string
Now that we can encode and decode our string, let’s create a method that hashes and validates a hash integrity
// Hash generates a Hmac256 hash of a string using a secret
func Hash(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))
}
// isValidHash validates a hash againt a value
func isValidHash(value string, hash string, secret string) bool {
return hash == Hash(value, secret)
}
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, then the character is a what is in the hash.
The isValidHash function only hashes the value with the secret and comared it with the hash
Above we created two methods, One for generating an HS256 hash and the other for validating a string against a hash.
NOW IT GET’S INTERESTING:
let’s create a token. For this, we’ll create two functions
- Encode -: this creates the JWT
- Decode -: this verifies, decodes and returns the payload.
ENCODE
// Encode generates a jwt.
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 + “.” + Hash(signatureValue, secret)
}
The header is not a param because we only support one ALGO. an Ideal case would be specifying the type of algorithm to use.
Encode might not be the best name but the function is pretty straightforward 1. Base64Encode the header and the payload and concatenate it with a dot. Call it signatureValue
2. Hash the signatureValue and call it a secret
3. Concatenate the signatureValue and the secret with a dot and return
signatureValue := Base64Encode(header) + “.” + Base64Encode(string(encodedPayload))
return signatureValue + “.” + Hmac256(signatureValue, secret)
DECODE.
Now that we can encode, it’s only proper to verify and 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())
}
// checks if the token has expired.
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
- Splits the token
- Confirms if the token has three parts
- Decodes the payload
- Checks if the token has not yet expired
- Checks if the token is valid
- Returns the payload.
If 1–6 passes you’ll get a valid payload. At anyoint any of those checks fails, you’ll get an error. Which shows your token is invalid
The other in which this token validation should be done is debatable, but regardless, we did check for multiple scenarios to make sure the token is valid.
OUR LIBRARY IN ACTION
create an example.go file
package main
import (
“fmt”
“jwt”
“time”
)
func main() {
// our secret secret
secret := “randpmly generated secrete”
type Meta struct {
Name string
Email string
}
payload := jwt.Payload{
Sub: “123”,
Exp: time.Now().Unix() + 100000,
Public: Meta{
Name: “Murphy”,
Email: “Murphy@jwt.com”,
},
}
token := jwt.Encode(payload, secret)
fmt.Println(token)
// prints out eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE1NDEyNzAyNzAsInB1YmxpYyI6eyJOYW1lIjoiTXVycGh5IiwiRW1haWwiOiJNdXJwaHlAand0LmNvbSJ9fQ==.dkzber79rM7gubpPCaAkjz0gFjxndbMCk6zQWrswkzE=
fmt.Println(jwt.Decode(token, secret))
// prints out our payload
123 1541270653 map[Name:Murphy Email:Murphy@jwt.com]} <nil>
}
I used a JWT debugger tool for chrome to validate our token. Just to further proof our library works. You can check it out yourself.
you can find the complete code here
CONCLUSION
JWT is an awesome concept, It’s secure if implemented properly and It has changed how most modern application is built. Golang is a wonderful language but you can always try building this library in your preferred language.
The concept showed here can be applied in any popular language of your choice.
Although this library works, in production I would rather go for a well tested and supported library. The idea is to show you how the internals of JWT works.
Payload struct for whoever needs it
cool, thanks
Where is the struct Payload ?