In this example we are going to create a JWT token using RSA RS256 private key and validate it with public key. RSA is a asymmetric signing method which uses different keys for both creation and validation. Use this if both creator (server app) and user (client app) of tokens are allowed to validate it. The token creator would know both private and public keys whereas the user would only know the public key.


Keys


This is how the keys generally are used for the given action.



Run command below first to create both private and public RSA keys.


.PHONY: cert
cert:
openssl genrsa -out cert/id_rsa 4096
openssl rsa -in cert/id_rsa -pubout -out cert/id_rsa.pub

main.go


package main

import (
"fmt"
"io/ioutil"
"log"
"time"

"github.com/you/client/internal/pkg/token"
)

func main() {
prvKey, err := ioutil.ReadFile("cert/id_rsa")
if err != nil {
log.Fatalln(err)
}
pubKey, err := ioutil.ReadFile("cert/id_rsa.pub")
if err != nil {
log.Fatalln(err)
}

jwtToken := token.NewJWT(prvKey, pubKey)

// 1. Create a new JWT token.
tok, err := jwtToken.Create(time.Hour, "Can be anything")
if err != nil {
log.Fatalln(err)
}
fmt.Println("TOKEN:", tok)

// 2. Validate an existing JWT token.
content, err := jwtToken.Validate(tok)
if err != nil {
log.Fatalln(err)
}
fmt.Println("CONTENT:", content)
}

token.go


package token

import (
"fmt"
"time"

"github.com/dgrijalva/jwt-go"
)

type JWT struct {
privateKey []byte
publicKey []byte
}

func NewJWT(privateKey []byte, publicKey []byte) JWT {
return JWT{
privateKey: privateKey,
publicKey: publicKey,
}
}

func (j JWT) Create(ttl time.Duration, content interface{}) (string, error) {
key, err := jwt.ParseRSAPrivateKeyFromPEM(j.privateKey)
if err != nil {
return "", fmt.Errorf("create: parse key: %w", err)
}

now := time.Now().UTC()

claims := make(jwt.MapClaims)
claims["dat"] = content // Our custom data.
claims["exp"] = now.Add(ttl).Unix() // The expiration time after which the token must be disregarded.
claims["iat"] = now.Unix() // The time at which the token was issued.
claims["nbf"] = now.Unix() // The time before which the token must be disregarded.

token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
if err != nil {
return "", fmt.Errorf("create: sign token: %w", err)
}

return token, nil
}

func (j JWT) Validate(token string) (interface{}, error) {
key, err := jwt.ParseRSAPublicKeyFromPEM(j.publicKey)
if err != nil {
return "", fmt.Errorf("validate: parse key: %w", err)
}

tok, err := jwt.Parse(token, func(jwtToken *jwt.Token) (interface{}, error) {
if _, ok := jwtToken.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected method: %s", jwtToken.Header["alg"])
}

return key, nil
})
if err != nil {
return nil, fmt.Errorf("validate: %w", err)
}

claims, ok := tok.Claims.(jwt.MapClaims)
if !ok || !tok.Valid {
return nil, fmt.Errorf("validate: invalid")
}

return claims["dat"], nil
}

Test


Use jwt.io to decode the token and JSON Web Token (JWT) for more information about JWT in general.


$ go run -race main.go

TOKEN: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXQiOiJDYW4gYmUgYW55dGhpbmciLCJleHAiOjE2MDQyNTI5NzUsImlhdCI6MTYwNDI0OTM3NSwibmJmIjoxNjA0MjQ5Mzc1fQ.Ig-odzhYvhBLVvbs7y2zELFC1qgLCp4EC-XrDmcxhxHxIwS1u3i3gQ8FnzI7CKa495CFgZtwjk_SJhUGyCQQmVjpMpGYrZiJJ_4QiIlC8b0bFrq65SZOqumMzSE9pUH0V-pWiIzkqWni6PX9KLS7YJvr8o7l1dJ772d5nWw8wZDVJn76PHJo7SEtInA3-l-oxDvQ2rqtefo5enkDM_2Yg77h3542KwGFZVig8B-bzl6kO8w391gXJa8GdfIsbLsXFnI1-LdWZzjXSnD3wsUV8PsBJ0AkCYccwV7i4Sk4d56XkgTcb5IHixcwm64RXkEWmxw_RLZlMz4Fa6mSSB3nTbJnYFGxV8t35KKQjrsTdaRuVfGaxw_i54JAktpJxoRioR846f1o_OsvyrHY1cDi8kPEVinuW_UiRBLD26dOEvBbreM0bQaPn9K3_a6gBwtQX0xdaQsSha4dvvPZ-krzZr3TWzkewQRCRaZqJeL0pdiPV_l95R3HUCQTrznLzKu-QlwxBELrC92NK6ex13XuovBFRHfZDnTaXFc0JcSmXLRAAL7PREcLiAZCTz5b0oVP7K_vIjf2LhqYTPgUxUYm4HZySBxPEtu5QiVZA807nEBXpTWZ4DZAlyPYXokzV1jFfDBRtYRwxA6CqW7SWUwOPFal0IxvjlV4sP5SJwqGawM

CONTENT: Can be anything