README
¶
simplejwt
A small Go library for generating JWKs and signing/encrypting/verifying/decrypting typed payloads as JWS/JWE, built on top of go-jose.
Each Go type you want to serialize gets its own JwtTokenConfig (which key to sign/encrypt
with, and which algorithms to use), registered on a ConfigResolver. A Manager combines that
ConfigResolver with a KeyStore of JWKs to serialize and parse tokens.
Installation
go get github.com/index0h/simplejwt@latest
# or
go get -tool github.com/index0h/simplejwt/cmd/simplejwt
Concepts
KeyGenerator— generates a*jose.JSONWebKeyfor a given key ID and JWA algorithm (RSA/RSA-PSS, ECDSA, EdDSA, HMAC and symmetric key-wrapping algorithms for JWE).KeyStore— an in-memory collection of JWKs, keyed byKeyID. Supports looking up a key by exact ID (GetById, used to verify/decrypt) or the newest key with a given prefix (GetNewest, used to sign/encrypt with key rotation in mind — see Key rotation).ConfigResolver— maps a Go type to itsJwtTokenConfig(whichKeyIDprefix and algorithm to sign/encrypt with). Register one entry per token type withRegister, look it up withResolve, or iterate all of them withAll.Manager— wraps aKeyStoreand aConfigResolverand exposesSerializeJws/UnserializeJws(JWS, signed) andSerializeJwe/UnserializeJwe(JWE, encrypted) for anyt anypayload that is JSON-serializable.
Usage
package main
import (
"fmt"
"github.com/go-jose/go-jose/v4"
"github.com/index0h/simplejwt/simplejwt"
)
type AccessToken struct {
Subject string `json:"sub"`
}
func main() {
// 1. Generate (or load) JWKs.
generator := simplejwt.NewKeyGenerator()
signKey, err := generator.Generate("access_20260803", string(jose.EdDSA))
if err != nil {
panic(err)
}
// 2. Put them in a KeyStore.
keyStore := simplejwt.NewKeyStore([]*jose.JSONWebKey{signKey})
// 3. Register a JwtTokenConfig per token type.
configResolver := simplejwt.NewConfigResolver()
configResolver.Register(&AccessToken{}, simplejwt.JwtTokenConfig{
SignKeyId: "access_", // GetNewest looks up the newest key with this prefix
SignAlgorithm: jose.EdDSA,
})
// 4. Serialize / parse tokens through the Manager.
manager := simplejwt.NewManager(keyStore, configResolver)
token := &AccessToken{Subject: "user-1"}
data, err := manager.SerializeJws(token)
if err != nil {
panic(err)
}
fmt.Println(string(data))
parsed := &AccessToken{}
if err := manager.UnserializeJws(data, parsed); err != nil {
panic(err)
}
fmt.Println(parsed.Subject)
}
SerializeJwe/UnserializeJwe work the same way, driven by EncryptKeyId,
EncryptKeyAlgorithm and EncryptContentAlgorithm on JwtTokenConfig.
Key rotation
KeyStore.GetNewest(prefix) returns the lexicographically greatest KeyID starting with
prefix. The intended pattern is to suffix each generated key with a sortable date, e.g.
access_20260803, and configure SignKeyId/EncryptKeyId in JwtTokenConfig with just the
prefix (access_). Signing/encrypting always picks up the newest key for that prefix, while
verifying/decrypting (GetById) works against any previously issued KeyID, so tokens signed
with older keys remain valid until you drop them from the KeyStore.
CLI
cmd/simplejwt generates a new JWK and prints it as JSON:
go run ./cmd/simplejwt jwk-generate --key-id=access_20260803 --algorithm=EdDSA
Supported --algorithm values are any jose.SignatureAlgorithm/jose.KeyAlgorithm string
accepted by KeyGenerator.Generate (see simplejwt/key_generator.go),
e.g. EdDSA, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512,
HS256, HS384, HS512 for signing keys, and RSA1_5, RSA-OAEP, RSA-OAEP-256, A128KW,
A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, ECDH-ES, ECDH-ES+A128KW,
ECDH-ES+A192KW, ECDH-ES+A256KW for encryption keys.
Development
go build ./...
go vet ./...
go test ./...