Expand description
laye is a framework-agnostic role- and permission-based access control (RBAC) library.
Build composable AccessPolicy rules, implement Principal on your auth type, and wire
the provided middleware into actix-web or axum (tower). Authentication — decoding tokens,
querying the database — is entirely outside laye’s scope.
§Feature flags
| Feature | What it adds |
|---|---|
actix-web | actix module: PolicyMiddlewareFactory, AuthPrincipal, MaybeAuthPrincipal |
tower | tower_middleware module: AccessControlLayer |
§How it works
- Implement
Principalon your auth info struct (decoded JWT payload, session data, etc.). - Build an
AccessPolicywithAccessPolicy::require_all(AND) orAccessPolicy::require_any(OR), chainingAccessRules and nested sub-policies. - Insert your principal into request extensions from your own upstream auth middleware — before
layeruns:- actix-web:
req.extensions_mut().insert(my_user) - axum / tower:
req.extensions_mut().insert(my_user)
- actix-web:
- Apply a
layemiddleware / layer. It readsPfrom extensions and callsAccessPolicy::check. If no principal is found the request is rejected with 401 Unauthorized; if the principal fails the policy it is rejected with 403 Forbidden.
§Quick start
Implement Principal:
use laye::Principal;
#[derive(Clone)]
struct MyUser {
roles: Vec<String>,
permissions: Vec<String>,
authenticated: bool,
}
impl Principal for MyUser {
fn roles(&self) -> &[String] { &self.roles }
fn permissions(&self) -> &[String] { &self.permissions }
fn is_authenticated(&self) -> bool { self.authenticated }
}Build and evaluate a policy:
let editor = MyUser { roles: vec!["editor".into()], permissions: vec![], authenticated: true };
let viewer = MyUser { roles: vec!["viewer".into()], permissions: vec![], authenticated: true };
// Require authenticated AND (role "admin" OR role "editor")
let policy = AccessPolicy::require_all()
.add_rule(AccessRule::Authenticated)
.add_policy(
AccessPolicy::require_any()
.add_rule(AccessRule::Role("admin".into()))
.add_rule(AccessRule::Role("editor".into())),
);
assert_eq!(policy.check(Some(&editor)), LayeCheckResult::Authorized);
assert_eq!(policy.check(Some(&viewer)), LayeCheckResult::Forbidden);
assert_eq!(policy.check(None), LayeCheckResult::Unauthorized);Permission-based rules work the same way:
let writer = MyUser {
roles: vec![],
permissions: vec!["posts:write".into()],
authenticated: true,
};
let policy = AccessPolicy::require_all()
.add_rule(AccessRule::Authenticated)
.add_rule(AccessRule::Permission("posts:write".into()));
assert_eq!(policy.check(Some(&writer)), LayeCheckResult::Authorized);Guest-only route (reject already-authenticated users):
let guest_policy = AccessPolicy::require_all().add_rule(AccessRule::Guest);
assert_eq!(guest_policy.check(None), LayeCheckResult::Authorized);
assert_eq!(guest_policy.check(Some(&editor)), LayeCheckResult::Forbidden);§Framework integrations
See laye::actix for actix-web middleware and extractors.
See laye::tower_middleware for the tower / axum layer.
Re-exports§
pub use policy::AccessPolicy;pub use policy::AccessRule;pub use principal::Principal;pub use result::LayeCheckResult;
Modules§
- actix
actix-web - actix-web middleware and extractors for
laye. - policy
- Policy types:
AccessPolicy,AccessRule, andCustomFn. TheAccessPolicybuilder andAccessRuleenum. - principal
- The
Principaltrait. ThePrincipaltrait that callers implement on their auth type. - result
- The
LayeCheckResulttype. TheLayeCheckResulttype returned by every policy evaluation. - tower_
middleware tower - tower / axum middleware for
laye.