# einherjar/auth-jwt [![version](https://img.shields.io/badge/version-v1.0.0-5C4EE5?style=flat-square)](https://code.nochebuena.dev/einherjar/auth-jwt) [![license](https://img.shields.io/badge/license-AGPL--3.0-22863A?style=flat-square)](LICENSE) [![go](https://img.shields.io/badge/Go-1.26+-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev) > 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 ```go 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) ```go // 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 ```bash go get code.nochebuena.dev/einherjar/auth-jwt@v1.0.0 ```