Home > Back-end >  Encode JWT properly
Encode JWT properly

Time:04-29

I'm trying to write simple JWT implementation with these functionalities:

  • Generating token using HMAC
  • Validating token (if signature is correct or exp is not timed out)
  • Decode token and getting claims

from scratch for better understanding how does it work in depth.

So far I found this article how to build an authentication microservice in golang from scratch. One chapter is dedicated to implementation JWT from scratch. I used it go generate token, however when I paste token in https://jwt.io I've got invalid signature and following warnings:

Token I paste look like below: eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJKV1QiIH0=.eyJhdWQiOiJmcm9udGVuZC5rbm93c2VhcmNoLm1sIiwiZXhwIjoiMTY1MTIyMjcyMyIsImlzcyI6Imtub3dzZWFyY2gubWwifQ==.SqCW8Hxakzck9Puzl0BEOkREPDyl38g2Fd4KFaDazV4=

My JWT code implementation:

package main

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

func GenerateToken(header string, payload map[string]string, secret string) (string, error) {
    h := hmac.New(sha256.New, []byte(secret))
    header64 := base64.StdEncoding.EncodeToString([]byte(header))

    payloadstr, err := json.Marshal(payload)
    if err != nil {
        return "", err
    }
    payload64 := base64.StdEncoding.EncodeToString(payloadstr)

    message := header64   "."   payload64

    unsignedStr := header   string(payloadstr)

    h.Write([]byte(unsignedStr))
    signature := base64.StdEncoding.EncodeToString(h.Sum(nil))

    tokenStr := message   "."   signature
    return tokenStr, nil
}

func ValidateToken(token string, secret string) (bool, error) {
    splitToken := strings.Split(token, ".")

    if len(splitToken) != 3 {
        return false, nil
    }

    header, err := base64.StdEncoding.DecodeString(splitToken[0])
    if err != nil {
        return false, err
    }
    payload, err := base64.StdEncoding.DecodeString(splitToken[1])
    if err != nil {
        return false, err
    }

    unsignedStr := string(header)   string(payload)
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(unsignedStr))

    signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
    fmt.Println(signature)

    if signature != splitToken[2] {
        return false, nil
    }

    return true, nil
}

func main() {
    claimsMap := map[string]string{
        "aud": "frontend.knowsearch.ml",
        "iss": "knowsearch.ml",
        "exp": fmt.Sprint(time.Now().Add(time.Second * 2).Unix()),
    }
    secret := "Secure_Random_String"
    header := `{ "alg": "HS256", "typ": "JWT" }`

    tokenString, err := GenerateToken(header, claimsMap, secret)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("token: ", tokenString)

    isValid, _ := ValidateToken(tokenString, secret)
    fmt.Println("is token valid: ", isValid)

    duration := time.Second * 4
    time.Sleep(duration)

    isValid, _ = ValidateToken(tokenString, secret)
    fmt.Println("is token valid: ", isValid)

}

What's wrong with implementation above and how to fix it and get rid of warnings?

I decided to use Golang for implementation, however examples in any other languages very appreciated.

CodePudding user response:

JWT specification requires that all padding = characters are removed:

Base64 encoding using the URL- and filename-safe character set defined in Section 5 of RFC 4648 [RFC4648], with all trailing '=' characters omitted (as permitted by Section 3.2) and without the inclusion of any line breaks, whitespace, or other additional characters.

You can use base64.RawURLEncoding , which creates Base64Url encoding without padding, instead of base64.StdEncoding.

You can see the differences between the StdEncoding, RawStdEncodingand RawURLEncoding in this short Go Playground example.

Also, I strongly recommend to use a JWT library if it's not for learning exercise.

  • Related