Middleware performs some specific function on the HTTP request or response at a specific stage in the HTTP pipeline before or after the user defined controller. Middleware is a design pattern to eloquently add cross cutting concerns like logging, handling authentication without having many code contact points.
chi's
middlewares are just stdlib net/http middleware handlers. There is nothing special about them, which means the router and all the tooling is designed to be compatible and friendly with any middleware in the community. This offers much better extensibility and reuse of packages and is at the heart of chi's purpose.
Here is an example of a standard net/http middleware where we assign a context key "user"
the value of "123"
. This middleware sets a hypothetical user identifier on the request context and calls the next handler in the chain.
// HTTP middleware setting a value on the request context
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// create new context from `r` request context, and assign key `"user"`
// to value of `"123"`
ctx := context.WithValue(r.Context(), "user", "123")
// call the next handler in the chain, passing the response writer and
// the updated request object with the new context value.
//
// note: context.Context values are nested, so any previously set
// values will be accessible as well, and the new `"user"` key
// will be accessible from this point forward.
next.ServeHTTP(w, r.WithContext(ctx))
})
}
We can now take these values from the context in our Handlers like this:
func MyHandler(w http.ResponseWriter, r *http.Request) {
// here we read from the request context and fetch out `"user"` key set in
// the MyMiddleware example above.
user := r.Context().Value("user").(string)
// respond to the client
w.Write([]byte(fmt.Sprintf("hi %s", user)))
}
AllowContentEncoding enforces a whitelist of request Content-Encoding otherwise responds with a 415 Unsupported Media Type status
.
Content-Encoding Parameters: gzip
, deflate
, gzip, deflate
, deflate, gzip
This Middleware Doesn't Support br
encoding
Refer Content-Encoding
import (
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.AllowContentEncoding("deflate", "gzip"))
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
AllowContentType enforces a whitelist of request Content-Types otherwise responds with a 415 Unsupported Media Type status
.
Content-Type Parameters: application/json
, text/xml
, application/json, text/xml
Refer Content-Type
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.AllowContentType("application/json","text/xml"))
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
CleanPath middleware will clean out double slash mistakes from a user's request path. For example, if a user requests /users//1 or //users////1 will both be treated as: /users/1
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.CleanPath)
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
Compress is a middleware that compresses response body of a given content types to a data format based on Accept-Encoding request header. It uses a given compression level.
NOTE: make sure to set the Content-Type header on your response otherwise this middleware will not compress the response body. For ex, in your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody)) or set it manually.
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.Compress(5, "text/html", "text/css"))
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match. An empty charset will allow requests with no Content-Type header or no specified charset.
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
allowedCharsets := []string{"UTF-8", "Latin-1", ""}
r.Use(middleware.ContentCharset(allowedCharsets...))
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
To Implement CORS in chi
we can use go-chi/cors
This middleware is designed to be used as a top-level middleware on the chi router. Applying with within a r.Group()
or using With()
will not work without routes matching OPTIONS added.
func main() {
r := chi.NewRouter()
// Basic CORS
// for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
r.Use(cors.Handler(cors.Options{
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
AllowedOrigins: []string{"https://*", "http://*"},
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: false,
MaxAge: 300, // Maximum value not ignored by any of major browsers
}))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3000", r)
}
GetHead automatically route undefined HEAD requests to GET handlers.
Reference: HEAD
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.GetHead)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {})
}
Heartbeat endpoint middleware useful to setting up a path like /ping
that load balancers or uptime testing external services can make a request before hitting any routes. It's also convenient to place this above ACL middlewares as well.
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.Heartbeat("/"))
}
Get -> http://api_address/
Response -> ".", Status 200
Logger is a middleware that logs the start and end of each request, along with some useful data about what was requested, what the response status was, and how long it took to return. When standard output is a TTY, Logger will print in color, otherwise it will print in black and white. Logger prints a request ID if one is provided.
Alternatively, look at https://github.com/goware/httplog for a more in-depth http logger with structured logging support.
IMPORTANT NOTE: Logger should go before any other middleware that may change
the response, such as middleware.Recoverer
.
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.Logger) // <--<< Logger should come before Recoverer
r.Use(middleware.Recoverer)
r.Get("/", handler)
}
NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent a router (or subrouter) from being cached by an upstream proxy and/or client.
As per http://wiki.nginx.org/HttpProxyModule - NoCache sets:
Expires: Thu, 01 Jan 1970 00:00:00 UTC
Cache-Control: no-cache, private, max-age=0
X-Accel-Expires: 0
Pragma: no-cache (for HTTP/1.0 proxies/clients)
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.NoCache)
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
We can make an Authorization Server, which generates tokens for three scopes
- username & password
- clientID & Secret
- RefreshTokenGrant
Example:
package main
import(
"errors"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/oauth"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "PUT", "POST", "DELETE", "HEAD", "OPTION"},
AllowedHeaders: []string{"User-Agent", "Content-Type", "Accept", "Accept-Encoding", "Accept-Language", "Cache-Control", "Connection", "DNT", "Host", "Origin", "Pragma", "Referer"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300, // Maximum value not ignored by any of major browsers
}))
registerAPI(r)
_ = http.ListenAndServe(":8080", r)
}
func registerAPI(r *chi.Mux) {
s := oauth.NewBearerServer(
"mySecretKey-10101",
time.Second*120,
&TestUserVerifier{},
nil)
r.Post("/token", s.UserCredentials)
r.Post("/auth", s.ClientCredentials)
}
POST http://localhost:3000/token
User-Agent: Fiddler
Host: localhost:3000
Content-Length: 50
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=user01&password=12345
POST http://localhost:3000/auth
User-Agent: Fiddler
Host: localhost:3000
Content-Length: 66
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=abcdef&client_secret=12345
POST http://localhost:3000/token
User-Agent: Fiddler
Host: localhost:3000
Content-Length: 50
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token={the refresh_token obtained in the previous response}
Refer Example For the full Example...
Here we can implement oauth2 authentication and verification
Example:
package main
import (
"bytes"
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/oauth"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "PUT", "POST", "DELETE", "HEAD", "OPTION"},
AllowedHeaders: []string{"User-Agent", "Content-Type", "Accept", "Accept-Encoding", "Accept-Language", "Cache-Control", "Connection", "DNT", "Host", "Origin", "Pragma", "Referer"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300, // Maximum value not ignored by any of major browsers
}))
registerAPI(r)
_ = http.ListenAndServe(":8081", r)
}
func registerAPI(r *chi.Mux) {
r.Route("/", func(r chi.Router) {
// use the Bearer Authentication middleware
r.Use(oauth.Authorize("mySecretKey-10101", nil))
r.Get("/customers", GetCustomers)
r.Get("/customers/{id}/orders", GetOrders)
})
}
Resource Server Example
Get Customers
GET http://localhost:3200/customers
User-Agent: Fiddler
Host: localhost:3200
Content-Length: 0
Content-Type: application/json
Authorization: Bearer {access_token}
Get Orders
GET http://localhost:3200/customers/12345/orders
User-Agent: Fiddler
Host: localhost:3200
Content-Length: 0
Content-Type: application/json
Authorization: Bearer {access_token}
{access_token} is produced by the Authorization Server response (see example /test/authserver).
Refer Example For the full Example...
Profiler is a convenient subrouter used for mounting net/http/pprof. ie. Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
// ..middlewares
r.Mount("/debug", middleware.Profiler())
// ..routes
}
Now you can request @ /debug for pprof profiles
RealIP is a middleware that sets a http.Request's RemoteAddr to the results of parsing either the X-Real-IP header or the X-Forwarded-For header (in that order).
This middleware should be inserted fairly early in the middleware stack to ensure that subsequent layers (e.g., request loggers) which examine the RemoteAddr will see the intended value.
You should only use this middleware if you can trust the headers passed to you (in particular, the two headers this middleware uses), for example because you have placed a reverse proxy like HAProxy or nginx in front of chi. If your reverse proxies are configured to pass along arbitrary header values from the client, or if you use this middleware without a reverse proxy, malicious clients will be able to cause harm (or, depending on how you're using RemoteAddr, vulnerable to an attack of some sort).
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
// ..middlewares
r.Use(middleware.RealIP)
// ..routes
}
Recoverer is a middleware that recovers from panics, logs the panic (and a backtrace), and returns a HTTP 500 (Internal Server Error) status if possible. Recoverer prints a request ID if one is provided.
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
// ..middlewares
r.Use(middleware.Recoverer)
// ..routes
r.Get("/", func(http.ResponseWriter, *http.Request) { panic("foo") })
}
RedirectSlashes is a middleware that will match request paths with a trailing slash and redirect to the same path, less the trailing slash.
NOTE: RedirectSlashes middleware is incompatible with http.FileServer, see Issue 343
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.RedirectSlashes)
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
RouteHeaders is a neat little header-based router that allows you to direct the flow of a request through a middleware stack based on a request header.
For example, lets say you'd like to setup multiple routers depending on the request Host header, you could then do something as so:
r := chi.NewRouter()
rSubdomain := chi.NewRouter()
r.Use(middleware.RouteHeaders().
Route("Host", "example.com", middleware.New(r)).
Route("Host", "*.example.com", middleware.New(rSubdomain)).
Handler)
r.Get("/", h)
rSubdomain.Get("/", h2)
Another example, imagine you want to setup multiple CORS handlers, where for your origin servers you allow authorized requests, but for third-party public requests, authorization is disabled.
r := chi.NewRouter()
r.Use(middleware.RouteHeaders().
Route("Origin", "https://app.skyweaver.net", cors.Handler(cors.Options{
AllowedOrigins: []string{"https://api.skyweaver.net"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
AllowCredentials: true, // <----------<<< allow credentials
})).
Route("Origin", "*", cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Content-Type"},
AllowCredentials: false, // <----------<<< do not allow credentials
})).
Handler)
StripSlashes is a middleware that will match request paths with a trailing slash, strip it from the path and continue routing through the mux, if a route matches, then it will serve the handler.
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.StripSlashes)
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
Throttle is a middleware that limits number of currently processed requests at a time across all users. Note: Throttle is not a rate-limiter per user, instead it just puts a ceiling on the number of currentl in-flight requests being processed from the point from where the Throttle middleware is mounted.
Throttle has a BacklogTimeout of 60 seconds by default
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.Throttle(15))
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
ThrottleBacklog is a middleware that limits number of currently processed requests at a time and provides a backlog for holding a finite number of pending requests.
Usage
import (
"time"
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(ThrottleBacklog(10, 50, time.Second*10))
r.Post("/", func(w http.ResponseWriter, r *http.Request) {})
}
Timeout is a middleware that cancels ctx after a given timeout and return a 504 Gateway Timeout error to the client.
It's required that you select the ctx.Done() channel to check for the signal if the context has reached its deadline and return, otherwise the timeout signal will be just ignored.
ie. a route/handler may look like:
r.Get("/long", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
processTime := time.Duration(rand.Intn(4)+1) * time.Second
select {
case <-ctx.Done():
return
case <-time.After(processTime):
// The above channel simulates some hard work.
}
w.Write([]byte("done"))
})
Usage
import (
"github.com/go-chi/chi/v5/middleware"
)
func main(){
r := chi.NewRouter()
r.Use(middleware.Timeout(time.Second*60))
// handlers ...
}
For Implementing JWT Authentication we can use go-chi/jwtauth
It is a middleware built upon lestrrat-go/jwx
The jwtauth
http middleware package provides a simple way to verify a JWT token
from a http request and send the result down the request context (context.Context
).
In a complete JWT-authentication flow, you'll first capture the token from a http
request, decode it, verify it and then validate that its correctly signed and hasn't
expired - the jwtauth.Verifier
middleware handler takes care of all of that. The
jwtauth.Verifier
will set the context values on keys jwtauth.TokenCtxKey
and
jwtauth.ErrorCtxKey
.
Next, it's up to an authentication handler to respond or continue processing after the
jwtauth.Verifier
. The jwtauth.Authenticator
middleware responds with a 401 Unauthorized
plain-text payload for all unverified tokens and passes the good ones through. You can
also copy the Authenticator and customize it to handle invalid tokens to better fit
your flow (ie. with a JSON error response body).
By default, the Verifier
will search for a JWT token in a http request, in the order:
- 'Authorization: BEARER T' request header
- 'jwt' Cookie value
The first JWT string that is found as an authorization header
or cookie header is then decoded by the lestrrat-go/jwx
library and a jwt.Token
object is set on the request context. In the case of a signature decoding error
the Verifier will also set the error on the request context.
The Verifier always calls the next http handler in sequence, which can either
be the generic jwtauth.Authenticator
middleware or your own custom handler
which checks the request context jwt token and error to prepare a custom
http response.
Note: jwtauth supports custom verification sequences for finding a token
from a request by using the Verify
middleware instantiator directly. The default
Verifier
is instantiated by calling Verify(ja, TokenFromHeader, TokenFromCookie)
.
Usage
See the full example.
package main
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5"
)
var tokenAuth *jwtauth.JWTAuth
func init() {
tokenAuth = jwtauth.New("HS256", []byte("secret"), nil) // replace with secret key
// For debugging/example purposes, we generate and print
// a sample jwt token with claims `user_id:123` here:
_, tokenString, _ := tokenAuth.Encode(map[string]interface{}{"user_id": 123})
fmt.Printf("DEBUG: a sample jwt is %s\n\n", tokenString)
}
func main() {
addr := ":3333"
fmt.Printf("Starting server on %v\n", addr)
http.ListenAndServe(addr, router())
}
func router() http.Handler {
r := chi.NewRouter()
// Protected routes
r.Group(func(r chi.Router) {
// Seek, verify and validate JWT tokens
r.Use(jwtauth.Verifier(tokenAuth))
// Handle valid / invalid tokens. In this example, we use
// the provided authenticator middleware, but you can write your
// own very easily, look at the Authenticator method in jwtauth.go
// and tweak it, its not scary.
r.Use(jwtauth.Authenticator)
r.Get("/admin", func(w http.ResponseWriter, r *http.Request) {
_, claims, _ := jwtauth.FromContext(r.Context())
w.Write([]byte(fmt.Sprintf("protected area. hi %v", claims["user_id"])))
})
})
// Public routes
r.Group(func(r chi.Router) {
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome anonymous"))
})
})
return r
}
To implement this we can use go-chi/httprate
package main
import (
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/httprate"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
// Enable httprate request limiter of 100 requests per minute.
//
// In the code example below, rate-limiting is bound to the request IP address
// via the LimitByIP middleware handler.
//
// To have a single rate-limiter for all requests, use httprate.LimitAll(..).
//
// Please see _example/main.go for other more, or read the library code.
r.Use(httprate.LimitByIP(100, 1*time.Minute))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("."))
})
http.ListenAndServe(":3333", r)
}