authjwt

package module
v1.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

README

einherjar/auth-jwt

version license go

A warrior's seal is recognized anywhere — but only if it cannot be forged.

JWT authentication middleware and token lifecycle management for the Einherjar framework. Supports HMAC-SHA256 (HS256), RSA-SHA256 (RS256), and ECDSA (ES256/ES384/ES512).

API

Symbol Kind Description
Verifier interface Validates JWT strings
Signer interface Extends Verifier; also signs tokens
NewHMACSigner(secret) func HS256 signer
NewRSASigner(key) func RS256 signer
NewRSASignerFromPEM(pem) func RS256 signer from PKCS#8/PKCS#1 PEM
NewRSAPublicKeyVerifier(key) func RS256 verifier (verify-only)
NewRSAPublicKeyVerifierFromPEM(pem) func RS256 verifier from PKIX/PKCS#1 PEM
NewECSigner(key) func ES256/384/512 signer (curve auto-detected)
NewECSignerFromPEM(pem) func EC signer from PKCS#8 PEM
NewECPublicKeyVerifier(key) func EC verifier (verify-only)
NewECPublicKeyVerifierFromPEM(pem) func EC verifier from PKIX PEM
TokenConfig struct AccessTTL, RefreshTTL, Issuer
TokenPair struct AccessToken, RefreshToken, ExpiresIn
IssueTokenPair(signer, uid, claims, cfg) func Sign access + refresh pair
Blacklist interface JTI revocation store (duck-typed by cache-valkey)
ErrTokenRevoked var Sentinel for replay-attack detection
RefreshTokenPair(ctx, signer, token, bl, cfg, claims) func Rotate tokens with blacklist check
AuthMiddleware(logger, verifier, publicPaths) func HTTP middleware — verifies Bearer token, calls authmw.SetTokenData

Dependency graph

contracts/logging  ──► auth-jwt
contracts/security ──► auth-jwt  (via auth/authmw)
core/xerrors       ──► auth-jwt
web/httputil       ──► auth-jwt
auth/authmw        ──► auth-jwt
jwt/v5             ──► auth-jwt  (only external dependency)

Wiring example — HMAC, full stack

import (
    "code.nochebuena.dev/einherjar/auth-jwt"
    "code.nochebuena.dev/einherjar/auth/authmw"
    "code.nochebuena.dev/einherjar/auth/rbac"
)

signer := authjwt.NewHMACSigner([]byte(os.Getenv("JWT_SECRET")))
cfg := authjwt.TokenConfig{
    AccessTTL:  15 * time.Minute,
    RefreshTTL: 7 * 24 * time.Hour,
    Issuer:     "myapp",
}

// JWT verification runs first (global).
srv.Use(authjwt.AuthMiddleware(logger, signer, []string{"/health", "/auth/*"}))

// Enrichment and authz follow.
srv.Use(authmw.EnrichmentMiddleware(logger, userEnricher))

const ReadOrders = security.Permission(0)
srv.With(authmw.AuthzMiddleware(logger, permissions, "orders", ReadOrders)).
    Get("/orders", ordersHandler)

// Login handler issues tokens:
pair, err := authjwt.IssueTokenPair(signer, uid, customClaims, cfg)

// Refresh handler rotates tokens:
newPair, err := authjwt.RefreshTokenPair(ctx, signer, body.RefreshToken, blacklist, cfg, freshClaims)
if errors.Is(err, authjwt.ErrTokenRevoked) {
    // replay attack — return 401 and require re-login
}

Verifier-only microservice (RSA)

// Service that verifies tokens but never issues them.
verifier, err := authjwt.NewRSAPublicKeyVerifierFromPEM([]byte(os.Getenv("RSA_PUBLIC_KEY_PEM")))
srv.Use(authjwt.AuthMiddleware(logger, verifier, publicPaths))

Environment variables

None. All configuration is passed in code.

Install

go get code.nochebuena.dev/einherjar/auth-jwt@v1.1.2

Documentation

Overview

Package authjwt provides JWT authentication middleware and token lifecycle management for the Einherjar framework. It supports HMAC-SHA256 (HS256), RSA-SHA256 (RS256), and ECDSA (ES256/ES384/ES512).

Typical wiring

signer := authjwt.NewHMACSigner([]byte(os.Getenv("JWT_SECRET")))
cfg := authjwt.TokenConfig{
    AccessTTL:  15 * time.Minute,
    RefreshTTL: 7 * 24 * time.Hour,
    Issuer:     "myapp",
}

// Verify Bearer tokens and inject uid+claims into context.
srv.Use(authjwt.AuthMiddleware(logger, signer, []string{"/health", "/auth/*"}))

// Enrichment and authz from auth/authmw follow downstream.
srv.Use(authmw.EnrichmentMiddleware(logger, userEnricher))

// Issue tokens on login:
pair, err := authjwt.IssueTokenPair(signer, uid, customClaims, cfg)

// Rotate tokens on refresh:
newPair, err := authjwt.RefreshTokenPair(ctx, signer, body.RefreshToken, blacklist, cfg, freshClaims)
if errors.Is(err, authjwt.ErrTokenRevoked) {
    // replay attack — force re-login
}

Index

Constants

This section is empty.

Variables

View Source
var ErrTokenRevoked = errors.New("token revoked")

ErrTokenRevoked is returned by RefreshTokenPair when the JTI is on the blacklist. Use errors.Is(err, authjwt.ErrTokenRevoked) to distinguish replay attacks from infrastructure errors.

View Source
var Module observability.Identifiable = &moduleID{}

Module identifies this package to observability systems. auth-jwt is a function library — it is not registered with the launcher as a lifecycle component. Register Module manually with any version registry if needed.

Functions

func AuthMiddleware

func AuthMiddleware(logger logging.Logger, verifier Verifier, publicPaths []string) func(http.Handler) http.Handler

AuthMiddleware verifies the Bearer access token and injects uid + claims into context via authmw.SetTokenData. Downstream authmw.EnrichmentMiddleware reads them transparently.

Accepts a Verifier — pass a Signer when the service issues tokens, or a NewRSAPublicKeyVerifier/NewECPublicKeyVerifier when it only verifies.

Requests to publicPaths are skipped without verification (path.Match wildcards supported). Returns 401 on missing, invalid, or expired tokens.

Types

type Blacklist

type Blacklist interface {
	IsRevoked(ctx context.Context, jti string) (bool, error)
	Revoke(ctx context.Context, jti string, ttl time.Duration) error
}

Blacklist records and checks revoked refresh token JTIs. Satisfied by einherjar/cache-valkey via duck typing. TTL on Revoke should match the token's remaining lifetime so entries expire naturally.

type Signer

type Signer interface {
	Verifier
	Sign(claims jwt.Claims) (string, error)
}

Signer signs and verifies JWTs. NewHMACSigner, NewRSASigner, and NewECSigner return implementations backed by HS256, RS256, and ES256/ES384/ES512 respectively.

func NewECSigner

func NewECSigner(privateKey *ecdsa.PrivateKey) Signer

NewECSigner returns a Signer backed by ECDSA. The signing algorithm is auto-detected from the key's curve: P-256→ES256, P-384→ES384, P-521→ES512.

func NewECSignerFromPEM

func NewECSignerFromPEM(pemKey []byte) (Signer, error)

NewECSignerFromPEM parses a PKCS#8 PEM-encoded ECDSA private key.

func NewHMACSigner

func NewHMACSigner(secret []byte) Signer

NewHMACSigner returns a Signer backed by HMAC-SHA256 (HS256). secret should be at least 32 bytes; shorter values are accepted but weakened.

func NewRSASigner

func NewRSASigner(privateKey *rsa.PrivateKey) Signer

NewRSASigner returns a Signer backed by RSA-SHA256 (RS256). The public key is derived from the private key — no separate argument needed.

func NewRSASignerFromPEM

func NewRSASignerFromPEM(pemKey []byte) (Signer, error)

NewRSASignerFromPEM parses a PKCS#8 or PKCS#1 PEM-encoded RSA private key.

type TokenConfig

type TokenConfig struct {
	AccessTTL  time.Duration
	RefreshTTL time.Duration
	Issuer     string
}

TokenConfig configures token lifetimes and the issuer claim.

type TokenPair

type TokenPair struct {
	AccessToken  string
	RefreshToken string
	ExpiresIn    int64 // seconds until the access token expires
}

TokenPair holds an access token and a refresh token.

func IssueTokenPair

func IssueTokenPair(signer Signer, uid string, customClaims map[string]any, cfg TokenConfig) (TokenPair, error)

IssueTokenPair signs a new access + refresh token pair for uid. customClaims are merged into the access token at the top level. Use this to embed per-resource permission masks so ClaimsPermissionProvider can read them without a DB call. The refresh token carries only sub, iss, iat, exp, jti, and fam (token family).

func RefreshTokenPair

func RefreshTokenPair(ctx context.Context, signer Signer, refreshToken string, bl Blacklist, cfg TokenConfig, customClaims map[string]any) (TokenPair, error)

RefreshTokenPair validates refreshToken, checks the blacklist, revokes the old JTI, and issues a new token pair for the same uid. customClaims are merged into the new access token — re-fetch fresh permissions here so role changes take effect without revoking outstanding access tokens. Returns ErrTokenRevoked if the JTI is already on the blacklist (replay attack or re-use after rotation).

type Verifier

type Verifier interface {
	Verify(tokenString string) (*jwt.Token, error)
}

Verifier validates JWT strings. Services that verify tokens but never issue them use a Verifier (e.g. NewRSAPublicKeyVerifier, NewECPublicKeyVerifier) instead of the full Signer.

func NewECPublicKeyVerifier

func NewECPublicKeyVerifier(publicKey *ecdsa.PublicKey) Verifier

NewECPublicKeyVerifier returns a Verifier backed by an ECDSA public key. Use this in services that verify tokens but never issue them.

func NewECPublicKeyVerifierFromPEM

func NewECPublicKeyVerifierFromPEM(pemKey []byte) (Verifier, error)

NewECPublicKeyVerifierFromPEM parses a PKIX PEM-encoded ECDSA public key.

func NewRSAPublicKeyVerifier

func NewRSAPublicKeyVerifier(publicKey *rsa.PublicKey) Verifier

NewRSAPublicKeyVerifier returns a Verifier backed by an RSA public key. Use this in services that verify tokens but never issue them.

func NewRSAPublicKeyVerifierFromPEM

func NewRSAPublicKeyVerifierFromPEM(pemKey []byte) (Verifier, error)

NewRSAPublicKeyVerifierFromPEM parses a PKIX or PKCS#1 PEM-encoded RSA public key.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL