30/01/2024 - GO
In this example we are going to create ECDSA keys then use then sign and verify JWT token. You would normally save the private key somewhere safe and reuse it to reproduce both key objects to use them in your application. Full ECDSA example can be found here.
package jwt
type Token struct {
ID string
ClientID string
}
package jwt
import "time"
type CreateTokenArgs struct {
ID string
ClientID string
Now time.Time
TTL time.Duration
}
type ValidateToken struct {
Token string
}
package jwt
import (
"context"
"errors"
"github.com/golang-jwt/jwt/v5"
)
type JWT struct {
Issuer string
SigningMethod jwt.SigningMethod
PublicKey any
PrivateKey any
}
func (j *JWT) CreateToken(ctx context.Context, args CreateTokenArgs) (string, error) {
token := jwt.NewWithClaims(j.SigningMethod, jwt.RegisteredClaims{
ID: args.ID,
Subject: args.ClientID,
Issuer: j.Issuer,
ExpiresAt: jwt.NewNumericDate(args.Now.Add(args.TTL)),
NotBefore: jwt.NewNumericDate(args.Now),
IssuedAt: jwt.NewNumericDate(args.Now),
})
sig, err := token.SignedString(j.PrivateKey)
if err != nil {
return "", err
}
return sig, nil
}
func (j *JWT) ValidateToken(ctx context.Context, args ValidateToken) (Token, error) {
token, err := jwt.ParseWithClaims(args.Token, &jwt.RegisteredClaims{}, func(t *jwt.Token) (interface{}, error) {
return j.PublicKey, nil
})
if err != nil {
return Token{}, err
}
if !token.Valid {
return Token{}, errors.New("invalid token")
}
claims, ok := token.Claims.(*jwt.RegisteredClaims)
if !ok {
return Token{}, errors.New("invalid token claims")
}
return Token{
ID: claims.ID,
ClientID: claims.Subject,
}, nil
}
package ecdsa
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
)
type ECDSA struct {
Private *ecdsa.PrivateKey
Public *ecdsa.PublicKey
}
func New(curve elliptic.Curve) (ECDSA, error) {
prv, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return ECDSA{}, err
}
return ECDSA{
Private: prv,
Public: &prv.PublicKey,
}, nil
}
package main
import (
"context"
"crypto/elliptic"
"fmt"
"time"
ecdsapkg "rate-limit/pkg/ecdsa"
jwtpkg "rate-limit/pkg/jwt"
jwtv5 "github.com/golang-jwt/jwt/v5"
)
func main() {
ecdsa, err := ecdsapkg.New(elliptic.P256())
if err != nil {
panic(err)
}
jwt := jwtpkg.JWT{
Issuer: "inanzzz",
SigningMethod: jwtv5.SigningMethodES256,
PublicKey: ecdsa.Public,
PrivateKey: ecdsa.Private,
}
tokenString, err := jwt.CreateToken(context.Background(), jwtpkg.CreateTokenArgs{
ID: "token-id-1",
ClientID: "client-id-1",
Now: time.Now().UTC(),
TTL: time.Hour,
})
if err != nil {
panic(err)
}
fmt.Println("TOKEN STRING:")
fmt.Println(tokenString)
tokenObject, err := jwt.ValidateToken(context.Background(), jwtpkg.ValidateToken{Token: tokenString})
if err != nil {
panic(err)
}
fmt.Println("TOKEN OBJECT:")
fmt.Printf("%+v\n", tokenObject)
}
$ go run -race main.go
TOKEN STRING:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpbmFuenp6Iiwic3ViIjoiY2xpZW50LWlkLTEiLCJleHAiOjE3MDY2MzUxNjUsIm5iZiI6MTcwNjYzMTU2NSwiaWF0IjoxNzA2NjMxNTY1LCJqdGkiOiJ0b2tlbi1pZC0xIn0.PNsehlztU3LCG9boo9PA8iMsGr264xtMe7wuBbcZgilFIvQsUJulJ6d0lzf1b9fUxayy8gmeqIz8XLjv6b63Cg
TOKEN OBJECT:
{ID:token-id-1 ClientID:client-id-1}
# HEADER
{
"alg": "ES256",
"typ": "JWT"
}
# PAYLOAD
{
"iss": "inanzzz",
"sub": "client-id-1",
"exp": 1706635165,
"nbf": 1706631565,
"iat": 1706631565,
"jti": "token-id-1"
}