gorilla / mux

Package gorilla/mux is a powerful HTTP router and URL matcher for building Go web servers with 🦍
https://gorilla.github.io
BSD 3-Clause "New" or "Revised" License
20.92k stars 1.85k forks source link

[FEATURE] Route metadata #750

Closed deborggraever closed 5 months ago

deborggraever commented 10 months ago

Is there an existing feature request for this?

Is your feature request related to a problem? Please describe.

Currently it is difficult to do authorization on routes based on claim requirements, unless we do the check inside the http handler. Coming from a C# .NET world, there it is easy because it support attributes like an [Authorize] attribute.

As workaround i added a unique name to each route, and created a authorization mapping between the route name and the authorization policy.

Middleware

var routePolicies map[string]string
var authPolicies map[string]authorization.Policy

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        route := mux.CurrentRoute(r)
        policyName, ok := routePolicies[route.GetName()]
        if !ok {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }

        policy, ok := authPolicies[policyName]
        if !ok || !policy.MeetsRequirements(r.Context()) {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }

        next.ServeHTTP(w, r)
    })
}

Setup

// Register the route
r.HandleFunc("/v1/product", api.GetProductsHandlerV1).Methods(http.MethodGet).Name("product_api:GetProductsHandlerV1")
r.HandleFunc("/v1/product/{id}", api.GetProductByIdHandlerV1).Methods(http.MethodGet).Name("product_api:GetProductByIdHandlerV1")

// Set the route policies
routePolicies["product_api:GetProductsHandlerV1"] = "product.read"
routePolicies["product_api:GetProductByIdHandlerV1"] = "product.read"

// Create the policies
authPolicies["product.read"] = authorization.NewPolicy(
    "product.read",
    NewUserRequirement(),
    NewScopeRequirement("product.read"),
)

Describe the solution that you would like.

To make it easy to create an authorization middleware with limited code, it would be nice to have metadata on a route.

Example:

r.HandleFunc("/v1/product", api.GetProductsHandlerV1).Methods(http.MethodGet).Metadata("authPolicy", "product.read")
r.HandleFunc("/v1/product/{id}", api.GetProductByIdHandlerV1).Methods(http.MethodGet).Metadata("authPolicy", "product.read")

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        route := mux.CurrentRoute(r)
        policy, ok := authPolicies[route.GetMetadata()["authPolicy"]]
        if !ok || !policy.MeetsRequirements(r.Context()) {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }

        next.ServeHTTP(w, r)
    })
}

Route metadata could be used for many other solutions, where u want to set some properties on a route to use inside the handler or middlewares

Describe alternatives you have considered.

No response

Anything else?

No response