The middleware packages apply RFC 9470 step-up enforcement to your HTTP handlers. Three framework integrations are provided.
type Config struct {
Provider providers.Provider // required: token introspection
PolicyEngine *policy.Engine // optional: nil = allow all authenticated
Realm string // WWW-Authenticate realm (default "IAM")
EnableDPoP bool // RFC 9449 DPoP proof validation (future)
}import iamstdlib "github.com/common-iam/iam/pkg/middleware/stdlib"
middleware := iamstdlib.Middleware(iamstdlib.Config{
Provider: provider,
PolicyEngine: engine,
Realm: "MyApp",
})
// Wrap your entire mux
http.ListenAndServe(":8080", middleware(yourMux))
// Or wrap specific routes
mux.Handle("/api/", middleware(http.HandlerFunc(apiHandler)))Reading claims in a handler:
func myHandler(w http.ResponseWriter, r *http.Request) {
claims, ok := iamstdlib.ClaimsFromContext(r.Context())
if !ok {
http.Error(w, "no claims", 500)
return
}
fmt.Fprintf(w, "Hello %s (acr=%s)", claims.Subject, claims.ACR)
}import iamgin "github.com/common-iam/iam/pkg/middleware/gin"
r := gin.New()
r.Use(iamgin.Middleware(iamgin.Config{
Provider: provider,
PolicyEngine: engine,
Realm: "MyApp",
}))
// Or apply to a group only
api := r.Group("/api")
api.Use(iamgin.Middleware(cfg))
api.GET("/profile", func(c *gin.Context) {
claims, _ := iamgin.ClaimsFromContext(c)
c.JSON(200, gin.H{
"sub": claims.Subject,
"acr": claims.ACR,
"email": claims.Email,
})
})import iamecho "github.com/common-iam/iam/pkg/middleware/echo"
e := echo.New()
e.Use(iamecho.Middleware(iamecho.Config{
Provider: provider,
PolicyEngine: engine,
Realm: "MyApp",
}))
// Or apply to a group
api := e.Group("/api")
api.Use(iamecho.Middleware(cfg))
api.GET("/profile", func(c echo.Context) error {
claims, _ := iamecho.ClaimsFromContext(c)
return c.JSON(200, map[string]string{
"sub": claims.Subject,
"acr": claims.ACR,
})
})Incoming Request
│
▼
1. Extract Bearer token from Authorization header
│ missing/malformed → 401 + WWW-Authenticate (invalid_token)
▼
2. Introspect token (cache → AS)
│ inactive/expired → 401 + WWW-Authenticate (invalid_token)
▼
3. Evaluate policy (if PolicyEngine is set)
│ no match → pass through
│ match, allowed → pass through
│ match, denied → 401 + WWW-Authenticate (insufficient_user_authentication)
│ acr_values=<required>
│ max_age=<required>
▼
4. Attach CommonClaims to context
▼
5. Call next handler
Headers:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="MyApp",
error="insufficient_user_authentication",
error_description="higher authentication level required",
acr_values="urn:mace:incommon:iap:silver",
max_age=300
Content-Type: application/json
Body:
{
"error": "insufficient_user_authentication",
"error_description": "higher authentication level required"
}When you receive a 401 with error=insufficient_user_authentication:
// Browser / SPA example
async function callAPI(endpoint) {
const response = await fetch(endpoint, {
headers: { Authorization: `Bearer ${accessToken}` }
});
if (response.status === 401) {
const wwwAuth = response.headers.get('WWW-Authenticate');
const challenge = parseChallenge(wwwAuth);
// challenge.acr_values = "urn:mace:incommon:iap:silver"
// challenge.max_age = 300
// Save original request for later retry
sessionStorage.setItem('pending_request', endpoint);
// Redirect to AS with step-up hint
window.location.href = buildAuthURL({
acr_values: challenge.acr_values,
max_age: challenge.max_age,
prompt: 'login',
});
return;
}
return response.json();
}If you need unauthenticated paths alongside authenticated ones, use route grouping rather than path exclusions in the middleware. This is safer and more explicit:
// Gin example
r := gin.New()
// Public routes — no middleware
r.GET("/health", healthHandler)
r.GET("/api/public/*path", publicHandler)
// Protected routes — with middleware
protected := r.Group("/api")
protected.Use(iamgin.Middleware(cfg))
protected.GET("/users", usersHandler)
protected.POST("/payments", paymentsHandler)The IAM middleware should run after request ID / tracing middleware (so trace IDs are available) but before business logic:
r := gin.New()
r.Use(gin.Logger()) // 1. logging
r.Use(requestid.New()) // 2. trace/request ID
r.Use(iamgin.Middleware(cfg)) // 3. auth enforcement ← here
r.Use(rateLimiter()) // 4. rate limiting (optional, after auth)
// business handlers