Go Adapter
The rampart-go adapter provides middleware for protecting Go HTTP services with Rampart-issued JWT tokens. It supports the standard library net/http, as well as popular routers including chi, gin, and fiber.
Installation
go get github.com/manimovassagh/rampart-go
Quick Start (net/http)
package main
import (
"encoding/json"
"log"
"net/http"
rampart "github.com/manimovassagh/rampart-go"
)
func main() {
auth, err := rampart.New(rampart.Config{
IssuerURL: "https://auth.example.com",
Audience: "my-api",
})
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
// Public route
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
// Protected route
mux.Handle("GET /api/profile", auth.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := rampart.ClaimsFromContext(r.Context())
json.NewEncoder(w).Encode(map[string]any{
"userId": claims.Subject,
"email": claims.Email,
"roles": claims.Roles,
})
})))
log.Println("Server running on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
Configuration
auth, err := rampart.New(rampart.Config{
// Required
IssuerURL: "https://auth.example.com",
Audience: "my-api",
// Optional
Realm: "default", // Organization/realm
ClockTolerance: 5 * time.Second, // Clock skew tolerance (default: 5s)
JWKSCacheTTL: 10 * time.Minute,// JWKS cache duration (default: 10m)
RequiredClaims: []string{"email"},// Claims that must exist in token
})
Configuration from Environment
auth, err := rampart.NewFromEnv()
Reads RAMPART_URL, RAMPART_CLIENT_ID, and RAMPART_REALM from the environment.
Claims
The Claims struct provides typed access to token claims:
type Claims struct {
Subject string `json:"sub"`
Email string `json:"email"`
Name string `json:"name"`
Roles []string `json:"roles"`
Scope string `json:"scope"`
OrgID string `json:"org_id"`
Issuer string `json:"iss"`
Audience []string `json:"aud"`
ExpiresAt int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
}
// Helper methods
claims.HasRole("admin") // bool
claims.HasAnyRole("admin", "editor") // bool
claims.HasScope("users:read") // bool
claims.HasAllScopes("users:read", "users:write") // bool
Extract claims from the request context:
claims := rampart.ClaimsFromContext(r.Context())
if claims == nil {
// No authenticated user
}
Middleware
RequireAuth
Verifies the bearer token. Returns 401 if missing or invalid.
mux.Handle("GET /api/data", auth.RequireAuth(handler))
RequireRoles
Requires all specified roles. Returns 403 if any role is missing.
mux.Handle("DELETE /api/admin/users/{id}",
auth.RequireAuth(
auth.RequireRoles("admin")(handler),
),
)
RequireScopes
Requires all specified scopes.
mux.Handle("POST /api/emails",
auth.RequireAuth(
auth.RequireScopes("email:send")(handler),
),
)
OptionalAuth
Attempts to verify the token if present but allows unauthenticated requests through. ClaimsFromContext returns nil for unauthenticated requests.
mux.Handle("GET /api/feed", auth.OptionalAuth(handler))
Router Integration: chi
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/go-chi/chi/v5"
chimw "github.com/go-chi/chi/v5/middleware"
rampart "github.com/manimovassagh/rampart-go"
rampartchi "github.com/manimovassagh/rampart-go/chi"
)
func main() {
auth, err := rampart.New(rampart.Config{
IssuerURL: "https://auth.example.com",
Audience: "my-api",
})
if err != nil {
log.Fatal(err)
}
r := chi.NewRouter()
r.Use(chimw.Logger)
r.Use(chimw.Recoverer)
// Public routes
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
// Protected routes
r.Group(func(r chi.Router) {
r.Use(rampartchi.Middleware(auth))
r.Get("/api/profile", func(w http.ResponseWriter, r *http.Request) {
claims := rampart.ClaimsFromContext(r.Context())
json.NewEncoder(w).Encode(map[string]any{
"userId": claims.Subject,
"email": claims.Email,
})
})
r.Get("/api/tasks", func(w http.ResponseWriter, r *http.Request) {
claims := rampart.ClaimsFromContext(r.Context())
json.NewEncoder(w).Encode(map[string]any{
"tasks": []string{"Review PR", "Deploy"},
"userId": claims.Subject,
})
})
})
// Admin routes — require admin role
r.Group(func(r chi.Router) {
r.Use(rampartchi.Middleware(auth))
r.Use(rampartchi.RequireRoles(auth, "admin"))
r.Get("/api/admin/stats", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]any{
"totalUsers": 1234,
})
})
})
log.Println("Server running on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
Router Integration: Gin
package main
import (
"net/http"
"github.com/gin-gonic/gin"
rampart "github.com/manimovassagh/rampart-go"
rampartgin "github.com/manimovassagh/rampart-go/gin"
)
func main() {
auth, err := rampart.New(rampart.Config{
IssuerURL: "https://auth.example.com",
Audience: "my-api",
})
if err != nil {
panic(err)
}
r := gin.Default()
// Public route
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
// Protected routes
api := r.Group("/api")
api.Use(rampartgin.Middleware(auth))
{
api.GET("/profile", func(c *gin.Context) {
claims := rampartgin.ClaimsFromContext(c)
c.JSON(http.StatusOK, gin.H{
"userId": claims.Subject,
"email": claims.Email,
"roles": claims.Roles,
})
})
api.GET("/tasks", func(c *gin.Context) {
claims := rampartgin.ClaimsFromContext(c)
c.JSON(http.StatusOK, gin.H{
"tasks": []string{"Review PR", "Deploy"},
"userId": claims.Subject,
})
})
}
// Admin routes
admin := r.Group("/api/admin")
admin.Use(rampartgin.Middleware(auth))
admin.Use(rampartgin.RequireRoles(auth, "admin"))
{
admin.GET("/stats", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"totalUsers": 1234})
})
}
r.Run(":8080")
}
Router Integration: Fiber
package main
import (
"log"
"github.com/gofiber/fiber/v2"
rampart "github.com/manimovassagh/rampart-go"
rampartfiber "github.com/manimovassagh/rampart-go/fiber"
)
func main() {
auth, err := rampart.New(rampart.Config{
IssuerURL: "https://auth.example.com",
Audience: "my-api",
})
if err != nil {
log.Fatal(err)
}
app := fiber.New()
// Public route
app.Get("/health", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"status": "ok"})
})
// Protected routes
api := app.Group("/api", rampartfiber.Middleware(auth))
api.Get("/profile", func(c *fiber.Ctx) error {
claims := rampartfiber.ClaimsFromContext(c)
return c.JSON(fiber.Map{
"userId": claims.Subject,
"email": claims.Email,
"roles": claims.Roles,
})
})
api.Get("/tasks", func(c *fiber.Ctx) error {
claims := rampartfiber.ClaimsFromContext(c)
return c.JSON(fiber.Map{
"tasks": []string{"Review PR", "Deploy"},
"userId": claims.Subject,
})
})
// Admin routes
admin := app.Group("/api/admin",
rampartfiber.Middleware(auth),
rampartfiber.RequireRoles(auth, "admin"),
)
admin.Get("/stats", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"totalUsers": 1234})
})
log.Fatal(app.Listen(":8080"))
}
JWKS Verification
The adapter handles JWKS fetching and caching automatically:
- On first request, fetches the JWKS from
{IssuerURL}/.well-known/jwks.json - Caches the key set for
JWKSCacheTTL(default: 10 minutes) - If a token references an unknown
kid, forces a JWKS refresh - Verifies RS256/RS384/RS512 and ES256/ES384/ES512 signatures
Manual Token Verification
If you need to verify tokens outside of HTTP middleware:
claims, err := auth.VerifyToken(ctx, tokenString)
if err != nil {
switch {
case errors.Is(err, rampart.ErrTokenExpired):
// Token has expired
case errors.Is(err, rampart.ErrInvalidSignature):
// Token signature is invalid
case errors.Is(err, rampart.ErrInvalidClaims):
// Required claims are missing
default:
// Other verification error
}
}
Custom Error Responses
Override the default 401/403 responses:
auth, err := rampart.New(rampart.Config{
IssuerURL: "https://auth.example.com",
Audience: "my-api",
OnUnauthorized: func(w http.ResponseWriter, r *http.Request, err error) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{
"error": "unauthorized",
"message": "Please provide a valid access token.",
})
},
OnForbidden: func(w http.ResponseWriter, r *http.Request, err error) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode(map[string]string{
"error": "forbidden",
"message": "You do not have permission to access this resource.",
})
},
})
Client Credentials Flow
For service-to-service communication:
client, err := rampart.NewClient(rampart.ClientConfig{
IssuerURL: "https://auth.example.com",
ClientID: "my-service",
ClientSecret: os.Getenv("RAMPART_CLIENT_SECRET"),
})
if err != nil {
log.Fatal(err)
}
// Get a token using client credentials
token, err := client.ClientCredentialsToken(ctx, []string{"users:read"})
if err != nil {
log.Fatal(err)
}
// Use the token
req, _ := http.NewRequest("GET", "https://api.internal/users", nil)
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
resp, err := http.DefaultClient.Do(req)
The client caches tokens and refreshes them automatically before expiry.