HTTP Middleware
Using authorization middleware when building HTTP servers focuses the responsibility of making authorization decisions to a single component instead of fragmenting the logic across all routes.
The middleware can be configured to retrieve authorization information, such as user identity, from incoming requests.
There are three flavors of the HTTP middleware:
middleware/httpz
provides middleware for HTTP servers using the standard net/http package.middleware/gorillaz
provides middleware for HTTP servers using gorilla/mux routers.middleware/ginz
provides middleware for HTTP servers using the Gin web framework.
Creating Middleware
Creating middleware requires two arguments: an authorizer client, and a Policy
that identifies the authorization policy to be applied, the decision rule to evaluate, and optionally a path to a policy module. If a path isn't provided, the middleware infers the policy path from the incoming request's URL. This behavior too can be further customized to fit other naming schemes.
import (
"github.com/aserto-dev/go-aserto"
"github.com/aserto-dev/go-aserto/az"
"github.com/aserto-dev/go-aserto/middleware"
"github.com/aserto-dev/go-aserto/middleware/httpz"
)
...
// Create an authorizer client.
azClient, err := az.New(
aserto.WithAddr("localhost:8282"),
aserto.WithInsecure(true),
)
// Create HTTP middleware.
mw := httpz.New(
azClient,
middleware.Policy{
Decision: "allowed", // policy rule to evaluate.
},
)
Configuring Middleware
Middleware can be configured to extract authorization information from incoming requests. This information includes:
- Identity: the identity of the caller.
- Policy Path: the policy module to evaluate (e.g.
"peoplefinder.GET.api.users.__id"
). - Resource: contextual information about the resource being accessed.
Identity
Identity information is set on the middleware's .Identity
.
For example, to configure the middleware to identify callers using a JWT in the "Authorization"
HTTP header:
mw.Identity.JWT().FromHeader("Authorization")
Or, to read a subject name from a "username"
context value on the incoming request (presumably, set by some authentication middleware):
mw.Identity.Subject().FromContextValue("username")
Policy Path
If a policy path isn't specified when the middleware is created, it will be inferred from the request URL starting with the HTTP method, followed by the URL segments separated by dots (.
). Path parameters are prefixed with two underscores (e.g. GET /users/{id}
becomes GET.users.__id
).
To add a prefix to the generated path (e.g. peoplefinder.GET.users.__id
) use:
mw.WithPolicyFromURL("peoplefinder")
To provide your own logic for determining the policy path use:
mw.WithPolicyPathMapper(
func(r *http.Request) string {
// custom logic inspects the request and returns the policy path.
},
)
Resource
Resource information can be added to authorization requests using
.WithResourceMapper()
:
mw.WithResourceMapper(
func(r *http.Request, resource map[string]interface{}) {
// Custom function to retrieve the owner of the resource being accessed.
resource["ownerId"] = GetOwner(r)
},
)
Connecting Middleware
With a configured middleware in hand, all that's left is connecting it to your HTTP router. The way you do that may differ depending on the library/framework you use, but they all follow similar patterns.
net/http
Using just the built-in net/http
package:
func Hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`"hello"`))
}
// Create a router.
mux := http.NewServeMux()
// Attach middleware to route handler.
mux.Handle("/hello", mw.HandlerFunc(Hello))
gorilla/mux
The popular gorilla/mux
package lets you set apply middleware to all
handlers in a router:
func Hello(w http.ResponseWriter, r *http.Request) {
name = mux.Vars(r)["name"]
w.Write([]byte(fmt.Sprintf(`"hello %s"`, name)))
}
r := mux.NewRouter() // Create new gorilla/mux Router.
r.Use(mw) // Attach authorization middleware to all routes.
r.HandleFunc("/hello/{name}", Hello) // Define route.
Gin
The middleware/ginz
package is similar to the middleware/gorillaz
module but uses gin.Context
instead of
http.Request
.
func Hello(c *gin.Context) {
name = c.Params.ByName("name")
c.JSON(http.StatusOK, fmt.Sprintf("hello %s", name))
}
A Gin resource mapper would look like this:
mw.WithResourceMapper(
func(c *gin.Context, resource map[string]interface{}) {
// Custom function to retrieve the owner of the resource being accessed.
resource["ownerId"] = GetOwner(c)
},
)