Rust Axum Casbin Authorisation
I have finally managed to write my first rust library axum-casbin-auth which creates a middleware layer for the Axum web framework to authorise HTTP requests based on the Casbin framework.
Couple of introductions
- Axum — web application framework that focuses on ergonomics and modularity. Please go through Axum to familiarise certain concepts but the idea is pretty fundamental and can be applied to many frameworks with few changes.
- Casbin — An authorization library that supports access control models like ACL, RBAC, ABAC.
- Tower — a library of modular and reusable components for building robust networking clients and servers.
In this article, let us walk through the workings of the library and quick implementation to scratch the surface of what is possible.
We will have two structs in order to integrate this as a middleware in the Axum framework. You can find more information on how the Axum middleware works here.
We use Arc from std and RwLock from tokio to wrap Enforcer as it is not thread-safe and designed for a synchronous programming model. The Enforcer is the module from casbin which takes in a configuration, and policy and allows us to authorise the requests which we will see below in a short while.
A middleware is a service that intercepts a request, performs some operation and passes to the next inner function or rejects the request if needed. You can imagine as layers of an onion where the outermost layer gets the request first and goes internally until the last layer and sends the response to the client. Please refer to the above Axum middleware link as there is a good explanation of how the middleware ordering works in Axum.
- Auth Claims
We need a subject (primary property that denotes a user/entity like email, username, etc…) for which we define policies in casbin and authorise against. At the moment we create a struct CasbinAuthClaims to have the subject alone but it can be extended to additional properties in the future.
- Layer Implementation
Now that we have created the concrete types for our layer and middleware, let us satisfy the contract in order to use the layer with Axum. The layer is a trait provided by the tower crate which has an associated type to denote the Service that this layer encompasses. In our case, the CasbinAuthLayer wraps on the CasbinAuthMiddleware service. CasbinAuthLayer can receive many input properties which can become a part of its internal state, which can then be passed to the inner service if needed.
- Service Implementation
This is the main block which implements the middleware logic. We implement the Service trait for our CasbinAuthMiddleware. There are a few associated types for which we need to provide type values:
- Response -> Response which is the HTTP response type from the Axum
- Error -> Infalliable — The error type for errors that can never happen. Since this enum has no variant, a value of this type can never actually exist. This can be useful for generic APIs that use Result and parameterize the error type, to indicate that the result is always Ok.
- Future -> This is the type that denotes what will the value that we will get in the future, as this is an asynchronous operation. HTTP requests do not have a definite end time and may take varying amounts of time to complete hence we use a future as output. You can refer to the future documentation to learn more about the concepts. Without going in-depth the main gist of the call function of the Service trait is responsible for the business logic of the authorisation middleware. Below are the logical steps we perform to identify if the request is authorized and can be allowed to pass to the next layer or not:
- Get the path of the request — like /, /hello, /api/admin, etc…
- Get the method of the request — GET | POST | PUT | DELETE etc…
- Try to get the CasbinAuthClaims from the request. This is the necessary information that is outside the scope of this library in order for us to authorise the request. We will see how it is injected in a short while.
- With all the information we then call the enforce_mut on the Casbin enforcer to validate based on the authorisation policies which we will see in a short while as well.
- If the rules match and are valid, we call the next layer like below and return the response
9. In other cases, we return the unauthorised response and terminate the request (Implicitly)
With this in place, the library is complete and let us see how it works in an Axum service.
For this example, we will see how to implement Casbin RESTful model. The example is provided here
- Casbin model.conf -> casbin.org/docs/en/syntax-for-models
The [request_definition] specifies the format of properties that are extracted from the request to check for authorisation.
- sub -> Subject which can be email, username, etc
- obj -> Object — In this context, it is the request path /data/123
- act -> Action — In this context, it is GET | POST, etc…
The [policy_definition] specifies the format of properties by which the multiple rules/policies are added. You can have more than 1 policy definition.
The [policy_effect] is the definition of the policy effect. It defines whether the access request should be approved if multiple policy rules match the request. For example, one rule permits and the other denies.
The [matchers] is the definition for policy matchers. The matchers are expressions. It defines how the policy rules are evaluated against the request.
- Casbin policies
The policy file can be static or dynamically stored in DBs as well. In this example, we will have a static policy.csv file which follows the policy_definition structure mentioned above.
We have defined two policies of type p
- Subject = firstname.lastname@example.org, Obj = *, Action = POST -> This means the subject is allowed to perform any POST operation
- Subject = email@example.com, Obj = /, Action = GET -> This means the subject is allowed to perform only GET operation on the / path.
If the rule matches don’t allow, then the request Is forbidden.
The above file contains the logic of signing and verifying the JWT token which contains the information of user email ( Subject ), and expiry information of the token. We implement the FromRequest trait of the Axum framework so that we can inject the decoded claims information into the request from the token sent from the client in the Authorisation header.
The snippet below in FromRequest implementation above is the part that is enabling our library middleware logic to work correctly.
You can run the example service and it will start a server in localhost port 8080
cargo run --bin restful
- Admin token making a POST call to /protected route — Allowed
- User token making a POST call to /protected route — Denied
- User token making a GET call to / route — Allowed
We have successfully implemented a RESTful authorisation using axum-casbin-auth middleware library. This just scratches the surface of what is possible and hope you had fun as much as I had while learning and developing this library. I am open to any suggestions or comments or a random hi 👋🏻. If you like this post and wanted to support me, you can do so by buying me a ☕️ here.