Middleware
Middleware in Okapi allows you to intercept and process HTTP requests before they reach your route handlers. This is useful for authentication, logging, request validation, and more.
Built-in Middleware
Basic Authentication
auth := okapi.BasicAuth{
Username: "admin",
Password: "password",
Realm: "Restricted",
ContextKey: "user", // where to store the username (default: "username")
}
// Global middleware
o.Use(auth.Middleware)
// Route-specific middleware
o.Get("/admin", adminHandler).Use(auth.Middleware)
CORS Middleware
cors := okapi.Cors{
AllowedOrigins: []string{"http://localhost:8080", "https://example.com"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
}
o := okapi.New(okapi.WithCors(cors))
JWT Middleware
Okapi includes powerful JWT middleware to secure your routes with JSON Web Tokens.
Features
- HS256 symmetric signing via
SigningSecret - RS256 and other asymmetric algorithms via
RSAKey - Remote JWKS discovery via
JwksUrl(e.g., OIDC or Auth0) - Local JWKS via
JwksFile - Claims validation with
ClaimsExpressionorValidateClaims - OpenAPI integration with
.WithBearerAuth() - Selective claim forwarding using
ForwardClaims
Basic HS256 Authentication
jwtAuth := okapi.JWTAuth{
SigningSecret: []byte("supersecret"), // Shared secret for HS256
TokenLookup: "header:Authorization", // Token source
ContextKey: "user", // Key for storing claims in context
}
Claim Validation Defaults
Audience and Issuer are optional: when either is left empty the
corresponding claim is not checked, and a token is accepted whether or not it
carries one. Set them to require a specific value.
An exp (expiry) claim is required by default. A signed token without one
never expires and the middleware has no revocation path, so any such token that
leaks stays valid until the signing key is rotated. If your issuer deliberately
mints non-expiring tokens, opt in explicitly:
jwtAuth := okapi.JWTAuth{
SigningSecret: []byte("supersecret"),
AllowMissingExpiry: true, // accept tokens with no "exp" claim
}
Remote JWKS (OIDC, Auth0)
jwtAuth := okapi.JWTAuth{
JwksUrl: "https://example.com/.well-known/jwks.json",
TokenLookup: "header:Authorization",
ContextKey: "user",
}
Claims Expression
Use ClaimsExpression to validate claims using simple expressions:
Supported Functions
Equals(field, value)Prefix(field, prefix)Contains(field, val1, val2, ...)— the claim equals one of the values, or, for an array claim, has an element equal to one of themOneOf(field, val1, val2, ...)Substring(field, val1, val2, ...)— the claim contains one of the values as a substring
Substring is a deliberately weak test and is not suitable for authorization
decisions: wherever any part of a role, scope or tenant string is
user-influenced, an attacker can make it contain the value you are checking
for. Prefer Equals, OneOf or Contains.
Logical Operators
!— NOT&&— AND||— OR
jwtAuth := okapi.JWTAuth{
SigningSecret: []byte("supersecret"),
ClaimsExpression: "Equals(`email_verified`, `true`) && Equals(`user.role`, `admin`)",
TokenLookup: "header:Authorization",
ContextKey: "user",
}
Forwarding Claims to Context
jwtAuth.ForwardClaims = map[string]string{
"email": "user.email",
"role": "user.role",
"name": "user.name",
}
Access claims in your handler:
func whoAmIHandler(c *okapi.Context) error {
email := c.GetString("email")
if email == "" {
return c.AbortUnauthorized("Unauthorized", fmt.Errorf("user not authenticated"))
}
return c.JSON(http.StatusOK, okapi.M{
"email": email,
"role": c.GetString("role"),
"name": c.GetString("name"),
})
}
Custom Claim Validation
jwtAuth.ValidateClaims = func(c *Context, claims jwt.Claims) error {
mapClaims, ok := claims.(jwt.MapClaims)
if !ok {
return errors.New("invalid claims type")
}
if emailVerified, _ := mapClaims["email_verified"].(bool); !emailVerified {
return errors.New("email not verified")
}
if role, _ := mapClaims["role"].(string); role != "admin" {
return errors.New("unauthorized role")
}
return nil
}
Custom Error Handling
auth := okapi.JWTAuth{
Audience: "okapi.example.com",
SigningSecret: SigningSecret,
OnUnauthorized: func(c *okapi.Context) error {
return c.ErrorUnauthorized("Unauthorized")
},
}
Protecting Routes
// Apply middleware globally
o.Use(jwtAuth.Middleware)
// Protect specific group
admin := o.Group("/admin", jwtAuth.Middleware).
WithBearerAuth() // Adds Bearer auth to OpenAPI docs
admin.Get("/users", adminGetUsersHandler)
// Route-specific middleware
o.Get("/protected", protectedHandler).Use(jwtAuth.Middleware)
Custom Middleware
Create your own middleware functions. Call c.Next() to pass control to the next middleware or handler:
func customMiddleware(c *okapi.Context) error {
start := time.Now()
err := c.Next()
log.Printf("Request took %v", time.Since(start))
return err
}
o.Use(customMiddleware)
Standard Library Middleware
You can also use standard http.Handler middleware:
o.UseMiddleware(func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Info("Hello from standard HTTP middleware")
handler.ServeHTTP(w, r)
})
})
Middleware Chaining
Apply multiple middleware to a route or group:
o.Get("/admin",
adminHandler,
).Use(
authMiddleware,
loggingMiddleware,
rateLimitMiddleware,
)