DEV Community

Cover image for JWT Implementation in Go
Gustavo Andrioli
Gustavo Andrioli

Posted on

JWT Implementation in Go

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
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can play around with the parameters and verify different results at their website

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:

type UserClaims struct {
  jwt.StandardClaims
  SessionID int64 `json:"session_id"`
}
Enter fullscreen mode Exit fullscreen mode

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:

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
}
Enter fullscreen mode Exit fullscreen mode

Also, we can define a ParseToken function, which would parse a signed token to fetch the claims:

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
}
Enter fullscreen mode Exit fullscreen mode

Full Code At: Source

Top comments (0)