Inkmi is Dream Jobs for CTOs and written as a decoupled monolith in Go, HTMX, Alpinejs, NATS.io and Postgres. I document my adventures and challenges in writing the application here on this blog, tune in again.
You have been writing an SaaS application. The offer is a very clean and designed hosting for weblogs (look it up!). Each blog post can be viewed with
Https://hotcoolblog.com/blog/123
And edited with
Https://hotcoolblog.com/edit/123
(be aware of OWASP Insecure Direct Object Reference)
What if I type in https://hotcoolblog.com/edit/4711
?
We need to secure access to resources. Only owners of data (the blog) can edit the data, but everyone can read it.
In your code, you want to check if the owner of a resource is the current user, and if so, give them the right to do something. Take the example for the blog post above. This can be done with a few lines of code:
// somewhere in editBlogPost.go
if blogPost.Owner == request.User {
...
} else {
return errors.New("Permission denied")
}
What if you want an admin to edit all posts? What if there are groups? Writing this on your own is not extensible and does not scale. So we are looking for a framework to do this.
We can use Casbin.
First, we create an Enforcer
with a model and a policy. (The example is not for production, just a simple example, but you get it). Translating the above lines into Casbin
gets us:
func NewEnforcer() *casbin.Enforcer {
model := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = p.sub == "owner" && r.sub == r.obj.Owner && r.obj.Type == p.obj && regexMatch(r.act, p.act)
`
policy := `
p, owner, blogpost, (write)|(read)
`
sa := scas.NewAdapter(policy)
e := casbin.NewEnforcer(casbin.NewModel(model), sa)
return e
}
We check if the type of permission is owner, if the user who wants to do something is the owner and if the action matches (read or write).
(the example uses the String adapter for the policy for simplicity)
In EditBlogPost
we can check if the user is allowed to write the blog post:
obj := Resource {
Type: "blogpost",
Owner: user,
Post: postText
}
act := "write"
allowed := app.Enforcer.Enforce(user, obj, act)
if !allowed {
...
} else {
...
}
If the user is allowed to write, we get true
else we get false
.
Often developers confuse authentication and authorization. Sometimes developers use UUID
s for secrecy but hope for security (don’t!). Sometimes developers check authorization on the route /edit/123
. What when someone else calls EditBlogPost
without checking authorization?
The only way to secure the resource is at the point of writing to it (or reading it). This can be done with ownership and Casbin as shown.
About Inkmi
Inkmi is a website with Dream Jobs for CTOs. We're on a mission to transform the industry to create more dream jobs for CTOs. If you're a seasoned CTO looking for a new job, or a senior developer ready for your first CTO calling, head over to https://www.inkmi.com and tell us what your dream job looks like!