JWT Implementation in Go
Tags: go, architecture, microservices
Published: 2021-03-11 03:36:56

## What is JWT?
JWTs (short for JSON Web Tokens) are simply a way of safely transmitting information between two parties in a JSON object, which can be signed, encrypted, and verified. It's commonly used for `Bearer` tokens in different authentication services (e.g. if you are the "bearer" of a valid token, it grants you access to something).

JWT tokens are broken down into three parts:
1. Header - hashing algorithm and token type
2. Payload - data (contains the Claims)
3. Signature - header + payload + key hashed together

The result of each part is separated by a `.`. The header and the payload are base64 encoded into the signature before hashed together with your 32 byte secret key. By verifying this signature, we can confirm if any content has been altered during a certain transmission between e.g. an user and a service provider.  

An example of a decoded token could be:
```
"header": {
  "alg": "HS512",
  "typ": "JWT"
}
"payload": {
  "sub": "1234567890",
  "message": "my message",
  "iat": 1516239022
}
HMACSHA512(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-32-byte-secret
)
```
Both the `Header` and `Payload` are JSON objects, and the `Signature` is simply a combination of them hashed and signed with your key. The object above would result in:
```
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibWVzc2FnZSI6Im15IG1lc3NhZ2UiLCJpYXQiOjE1MTYyMzkwMjJ9.2Lfy_7c4nI8Jrpuq4hGlY0COq7Vukbuqoy65MGmvuHP_a0v7VMC2KXRM1GYffvQZUshWd2im-IY94k1ptN9TZw
```
You can play around with the parameters and verify different results at [their website](jwt.io)

## Go Implementation
In order to implement a JWT system in Go, we simply need the `jwt-go` package and, optionally, the `uuid` package. 

We first need to define our claims struct:
```go
type UserClaims struct {
  jwt.StandardClaims
  SessionID int64 `json:"session_id"`
}
```
The claims contain personal information about the user, such as email, name, etc., and it's mainly used for authentication using these specific identifiers.  

We can then define our `GenerateNewKey` and `CreateToken` functions:
```go
type key struct {
	key     []byte
	created time.Time
}

var currentKID = ""
var keysMockDB = map[string]key{} // replace with real db

// generate key that'll be used when signing the token
func GenerateNewKey() error {
	newKey := make([]byte, 64)
	_, err := io.ReadFull(rand.Reader, newKey)
	if err != nil {
		return fmt.Errorf("Error in GenerateNewKey while generatinig new Key: %w", err)
	}

	uid := uuid.NewV4()

	keysMockDB[uid.String()] = key{
		key:     newKey,
		created: time.Now(),
	}
	currentKID = uid.String()

	return nil
}

func CreateToken(claims *UserClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodES512, claims)

	signedToken, err := token.SignedString(keysMockDB[currentKID].key)
	if err != nil {
		return "", fmt.Errorf("token.SignedString: %w", err)
	}

	return signedToken, nil
}
```
Also, we can define a `ParseToken` function, which would parse a signed token to fetch the claims:

```go
func ParseToken(signedToken string) (*UserClaims, error) {
	token, err := jwt.ParseWithClaims(
		signedToken,
		&UserClaims{},
		func(t *jwt.Token) (interface{}, error) {
			// t is the unverified token to check 
			// if same signing algorithm is being used
			if t.Method.Alg() != jwt.SigningMethodES512.Alg() {
				return nil, fmt.Errorf("Invalid signing algorithm")
			}

			// kid is an optional header claim which holds a key identifier
			kid, ok := t.Header["kid"].(string)
			if !ok {
				return nil, fmt.Errorf("Invalid key ID")
			}

			key, ok := keysMockDB[kid]
			if !ok {
				return nil, fmt.Errorf("Invalid key ID")
			}

			return key, nil
		},
	)
	if err != nil {
		return nil, fmt.Errorf("Error in ParseToken while parsing token: %w", err)
	}

	if token.Valid {
		return nil, fmt.Errorf("Error in ParseToken, token is not valid")
	}

	return token.Claims.(*UserClaims), nil
}
```

Full Code At: [Source](https://github.com/gusandrioli/jwt-article)