# casbin-rs: authorization decisions driven by a model file and a policy file

> casbin-rs is a Rust port of the Casbin authorization library, where the access control logic lives in a model file and the grants live in a policy file that can change at runtime. It fits teams that want one permission model shared across services, and it imposes a specific concurrency contract on the enforcer.

**apache/casbin-rs** — An authorization library that supports access control models like ACL, RBAC, ABAC in Rust

- Repository: https://github.com/apache/casbin-rs
- Website: https://casbin.org
- Stars: 1,148 · Forks: 95
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/apache-casbin-rs

## Two files decide whether a request is allowed

The core idea of casbin-rs is a split between logic and data. An enforcer is constructed from two files: a model file that describes how requests are matched, and a policy file that lists the actual grants. The README's first example builds one from `examples/rbac_with_domains_model.conf` and `examples/rbac_with_domains_policy.csv`, which is the naming convention across the `examples/` directory in the repository: every scenario has a `.conf` model and a `.csv` policy. The `examples/` folder makes the design legible, since it contains pairs such as `basic_model.conf` with `basic_policy.csv`, `basic_without_users_model.conf` with `basic_without_users_policy.csv`, `ipmatch_model.conf` with `ipmatch_policy.csv`, and `priority_model.conf` with `priority_indeterminate_policy.csv`. The supported model list in the README runs from plain ACL through ACL with a superuser, ACL without users, ACL without resources, RBAC, RBAC with resource roles, and RBAC with domains or tenants. The point for a Rust service is that changing who can read what becomes a data change, not a recompile.

## The enforce call is the only thing your handler needs

The integration surface is deliberately narrow. A caller forms a request tuple, in the README's example the subject, the object, and the action, and passes it to `enforce`, which returns whether the request is authorized. The hook in the README looks like this: a subject constant, an object constant, an action constant, and an `if let Ok(authorized) = e.enforce((sub, obj, act))` branch with three arms, one for permitting, one for denying, and one for the error case. The `Result` return type is the detail worth noticing, because a policy that fails to evaluate is not the same as a denial, and the example code keeps those paths separate rather than collapsing them. A domain or tenant variant simply widens the request tuple to four elements, which is why the domain example in the README passes `("alice", "domain1", "data1", "read")`. Whether a request tuple has three or four parts is decided by the model file, so the same `enforce` call serves both shapes.

## The enforcer is not thread-safe, and the README says so

This is the single most important operational detail in the documentation, and it is easy to miss because the note sits after the code example. The README states that the Enforcer instance is not thread-safe, so in an environment where multiple threads might access it you have to protect it with an RwLock, expressed as `let e = Arc::new(RwLock::new(e));`. The reason is structural rather than accidental: the enforcer holds mutable state, including the role manager and any cached policy state. A Tokio service handling many requests concurrently therefore needs a lock around every check, which is a per-request contention point, and a blocking lock in an async task is a further cost. The crate offers no documented multi-threaded enforcer type, and the README does not describe a read-only or immutable view you can share without locking. If your service makes authorization checks on a hot path, the architecture of how you hold the enforcer matters as much as the policy language.

## Feature flags decide the runtime, the cache and the logging backend

The Cargo.toml shows that the crate is assembled from optional pieces rather than one fixed stack. The default feature set is `runtime-tokio` plus `incremental`. `runtime-async-std` swaps the runtime, and `runtime-tokio` maps to `tokio/fs` and `tokio/io-util`. Other switches are `cached`, which pulls in `mini-moka` for a cache, `glob`, which enables `globset` for pattern matching, `ip`, which enables `ip_network` and matches the `ipmatch` example files, `logging`, which brings in `slog` with the term and async backends, `explain` for explanations, and `watcher`. The policy engine itself is written in `rhai`, compiled with `sync`, `only_i32`, `no_function`, `no_float`, `no_optimize`, `no_module`, `serde` and `unchecked`, which tells you the matcher expression language is deliberately restricted rather than a full scripting host. A separate target section covers `wasm32` with `getrandom`, so the crate is meant to compile for the browser as well as for servers. The manifest declares edition 2021 and version 2.18.1.

## Getting started: dependency, enforcer, hook

Installation is a Cargo.toml entry, and the README gives a full example rather than a bare line. It also flags the runtime version you should check before you start. The dependency block below is copied from the README, and the README notes that crates.io carries the current version, so confirm the number there before pinning:

```toml
[dependencies]
casbin = { version = "2.8.0", default-features = false, features = ["runtime-async-std", "logging", "incremental"] }
tokio = { version = "1.10.0", features = ["fs", "io-util"] }
```

The enforcer is then built asynchronously from a model and a policy path, and logging is switched on before the first check:

```rust
use casbin::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let mut e = Enforcer::new("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv").await?;
    e.enable_log(true);

    e.enforce(("alice", "domain1", "data1", "read"))?;
    Ok(())
}
```

Note the shape of that snippet: `Enforcer::new` is awaited, so the model and policy files are read asynchronously through the runtime you enabled. In a service you would hold the result behind the Arc and RwLock wrapper described above.

## The tokio version floor is the sharpest upgrade edge

The README carries a warning that is easy to skim past. Support for `tokio v1.0` or later starts at casbin v2.0.6, and the last version that supports `tokio v0.2` is casbin v2.0.5, so the README tells you to choose according to your needs. In practice that puts a version constraint on any project whose tokio dependency has not been upgraded in a long time, and it means the feature name `runtime-tokio` alone does not tell you which tokio generation you are on. The tagged releases visible in the repository history show v2.19.0, v2.19.1 and v2.20.0 between December 2025 and February 2026, and the last push was on 2026-10-09, so the project is still moving. The README also points to crates.io for the right version, which is the line to read before you write a version constraint into your own manifest, since the number in the install example is older than the current tags.

## One policy format across several language runtimes

Casbin is a family, and the README lists the sibling implementations for Go, Java, Node.js, PHP, Python, .NET and C++, with casbin-rs as the Rust entry. Each is marked production-ready in the table, and the shared point is the model and policy format, which is what makes a mixed language estate possible: a service written in Go and a service written in Rust can read the same model file and the same policy CSV. That is a different approach from embedding permission checks in each service's own code, where an authorization change means a pull request per service and a deployment, and where a mismatch between two hand-written checks is a security bug. The trade-off runs the other way too. A model file is a small domain specific language, so a policy that is hard to express in it ends up as custom code, and the repository includes a `CUSTOM_FUNCTIONS.md` file at the top level, which is the documented place to look when the built-in matcher functions run out. The README table also mentions an online editor at casbin.org for drafting policies, which is aimed at making the model file less forbidding for people who would rather not open the model file by hand.

## Conclusion

Adopt casbin-rs if your team already thinks in Casbin model and policy files, or if you want to change permissions by editing CSV rather than by shipping a binary. Do not adopt it as a drop-in library inside a single threaded service that wants zero configuration, because the enforcer needs a model file, a policy file and an explicit lock if more than one thread touches it. Verify three things first: which runtime feature you compile in, since the default is runtime-tokio and an async-std build is opt in; that the tokio version in your workspace is 1.0 or later, because casbin v2.0.6 was the first release to support it; and that the Enforcer is wrapped in Arc and RwLock before you share it across tasks. The crate is Apache-2.0, the last push was on 2026-10-09, and tagged releases reach v2.20.0.

## FAQ

### Which tokio version does casbin-rs support?

The README says tokio v1.0 or later is supported from casbin v2.0.6, and that the last release supporting tokio v0.2 is casbin v2.0.5. The crate's default feature set includes runtime-tokio, with runtime-async-std available as an alternative.

### Is the casbin-rs Enforcer safe to share between threads?

No. The README states that the Enforcer instance is not thread-safe and that it must be protected with an RwLock, in the form `let e = Arc::new(RwLock::new(e));`, when multiple threads might access it.

### What is the difference between a model file and a policy file in casbin-rs?

The model file describes how a request is matched, and the policy file lists the grants. The enforcer is built from both, as in `Enforcer::new("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")`, and the policy data can change without recompiling the service.

### Which access control models does casbin-rs implement?

The README lists ACL, ACL with superuser, ACL without users, ACL without resources, RBAC, RBAC with resource roles, and RBAC with domains or tenants. The `examples/` directory ships matching model and policy pairs for each scenario.

### What licence is casbin-rs released under?

The Cargo.toml in the repository declares the licence as Apache-2.0, and the crate publishes to crates.io under the name `casbin`. It also lists documentation on docs.rs.

## Sources

- [apache/casbin-rs on GitHub](https://github.com/apache/casbin-rs)
- [License: Apache-2.0](https://github.com/apache/casbin-rs/blob/master/LICENSE)
- [Project website](https://casbin.org)
- [README](https://github.com/apache/casbin-rs/blob/master/README.md)
- [Releases](https://github.com/apache/casbin-rs/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/apache-casbin-rs
