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.


JWT


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
}

ECDSA


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
}

main.go


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)
}

Test


$ go run -race main.go

TOKEN STRING:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpbmFuenp6Iiwic3ViIjoiY2xpZW50LWlkLTEiLCJleHAiOjE3MDY2MzUxNjUsIm5iZiI6MTcwNjYzMTU2NSwiaWF0IjoxNzA2NjMxNTY1LCJqdGkiOiJ0b2tlbi1pZC0xIn0.PNsehlztU3LCG9boo9PA8iMsGr264xtMe7wuBbcZgilFIvQsUJulJ6d0lzf1b9fUxayy8gmeqIz8XLjv6b63Cg

TOKEN OBJECT:
{ID:token-id-1 ClientID:client-id-1}

Content


# HEADER

{
"alg": "ES256",
"typ": "JWT"
}

# PAYLOAD

{
"iss": "inanzzz",
"sub": "client-id-1",
"exp": 1706635165,
"nbf": 1706631565,
"iat": 1706631565,
"jti": "token-id-1"
}