Authorization
Authorization in Nimbus determines what an authenticated user is allowed to do. While authentication verifies identity, authorization checks permissions. Nimbus provides the auth.Policy interface for fine-grained access control.
The Policy interface
A policy checks whether a user can perform an action on a resource:
type Policy interface {
Allow(ctx context.Context, user User, action string, resource any) bool
}
Defining a policy
Create a struct that implements auth.Policy. Check the action and compare the user to the resource:
type PostPolicy struct{}
func (p *PostPolicy) Allow(ctx context.Context, user auth.User, action string, resource any) bool {
post := resource.(*Post)
switch action {
case "view":
return true
case "update", "delete":
return post.UserID == user.GetID()
default:
return false
}
}
Using PolicyFunc
For simple checks, use auth.PolicyFunc to create a policy from a plain function:
adminOnly := auth.PolicyFunc(func(ctx context.Context, user auth.User, action string, resource any) bool {
appUser := user.(*AppUser)
return appUser.Role == "admin"
})
Registering policies
Keep a map of policies indexed by resource type so handlers can look them up by name:
var policies = map[string]auth.Policy{
"post": &PostPolicy{},
"comment": &CommentPolicy{},
"admin": adminOnly,
}
Checking permissions in handlers
Retrieve the user from context, fetch the resource, then call Allow on the appropriate policy:
func UpdatePost(c *http.Context) error {
user := auth.UserFromContext(c.Request.Context())
var post Post
db.First(&post, c.Param("id"))
policy := policies["post"]
if !policy.Allow(c.Request.Context(), user, "update", &post) {
return c.JSON(403, map[string]string{"error": "forbidden"})
}
// proceed with update ...
return c.JSON(200, map[string]string{"status": "updated"})
}
Role-based access control
You can build role-based authorization on top of policies. Add a Role field to your user model and check it in the policy:
type RolePolicy struct {
AllowedRoles []string
}
func (p *RolePolicy) Allow(ctx context.Context, user auth.User, action string, resource any) bool {
appUser := user.(*AppUser)
for _, role := range p.AllowedRoles {
if appUser.Role == role {
return true
}
}
return false
}
Gate pattern
For quick inline checks without a full policy struct, use PolicyFunc as a gate:
canPublish := auth.PolicyFunc(func(ctx context.Context, user auth.User, action string, resource any) bool {
appUser := user.(*AppUser)
return appUser.Role == "admin" || appUser.Role == "editor"
})
if !canPublish.Allow(ctx, user, "publish", nil) {
return c.JSON(403, map[string]string{"error": "forbidden"})
}