webgates-core
User-focused Rust building blocks for authentication and authorization.
webgates-core is the smallest crate in the webgates ecosystem. It gives you the domain model and authorization primitives without pulling in HTTP, cookies, JWT handling, sessions, database adapters, or framework integrations.
If you want to understand how webgates works at its foundation, or you want to build your own adapter around another framework, this is the crate to start with.
Who this crate is for
Use webgates-core when you want to:
- model users as accounts with roles, groups, and direct permissions
- make authorization decisions in pure Rust domain code
- build your own authentication or authorization services on top of the core types
- integrate with a framework that does not yet have a
webgatesadapter - keep dependencies minimal and avoid transport-specific concerns
If you are building a typical web application and want batteries included, you will usually want webgates instead.
What you learn in this crate
Most developers only need to internalize a small core loop:
Accountrepresents the current user and their assigned capabilitiesAccessPolicyexpresses what a protected action requiresAuthorizationServiceevaluates whether the account satisfies that policyPermissionsadd fine-grained capability checks when roles or groups are too broadCredentialsandCredentialsVerifierdefine the authentication boundary without committing you to one transport or backend
What this crate does not do
This crate intentionally does not include:
- HTTP middleware
- framework integrations
- JWT encoding or decoding
- cookies
- session issuance or renewal
- password hashing implementations
- repository implementations
Those concerns live in sibling crates such as webgates, webgates-axum, webgates-codecs, webgates-repositories, webgates-secrets, and webgates-sessions.
Install
Add this to your Cargo.toml:
[]
= "1.0.0"
Minimum supported Rust version: 1.91.
The mental model
If you are onboarding to the crate, this is the simplest way to think about it:
- You represent the current user as an
Account. - You express access requirements as an
AccessPolicy. - You ask
AuthorizationServicewhether the account satisfies that policy. - You optionally use
CredentialsandCredentialsVerifierto plug in your own login flow.
That is the core loop.
Quick start
This example shows the main flow you will use in application code.
use Account;
use AccessPolicy;
use AuthorizationService;
use Group;
use Role;
let account = new
.with_roles
.with_groups;
let policy = require_role
.or_require_group;
let authz = new;
assert!;
Core concepts
1. Accounts represent the current user
Account<R, G> is the central domain type. It stores:
- a generated
account_id - your application-level
user_id - assigned roles
- assigned groups
- direct permissions
Example:
use Account;
use Group;
use Permissions;
use Role;
let permissions: Permissions = .into_iter.collect;
let account = new
.with_roles
.with_groups
.with_permissions;
assert_eq!;
assert!;
assert!;
A useful onboarding detail: Account::new(...) automatically assigns the default role for your role type. With the built-in Role enum, that default is Role::User.
2. Roles model hierarchical privilege
The built-in Role type is ordered from least privileged to most privileged:
Role::UserRole::ReporterRole::ModeratorRole::Admin
That ordering matters when you use require_role_or_supervisor(...).
Example:
use AccessPolicy;
use Group;
use Role;
let exact = require_role;
let hierarchical = require_role_or_supervisor;
assert!;
assert!;
If your application needs its own role hierarchy, define your own enum in least-to-most privileged order and implement AccessHierarchy for it.
3. Groups model exact membership
Groups are useful for non-hierarchical membership such as departments, tenants, teams, or project assignments.
use Group;
let engineering = new;
let billing = new;
assert_eq!;
assert_eq!;
Use groups when access depends on belonging to something, not on privilege level.
4. Permissions model fine-grained capabilities
Permissions are string-based capabilities such as projects:read or admin:users:delete.
webgates-core converts them into deterministic PermissionId values internally, so you can do fast checks without maintaining a central numeric registry.
use Permissions;
use PermissionId;
let mut permissions = new;
permissions
.grant
.grant;
assert!;
assert!;
assert!;
Use permissions when a role is too broad and you need feature-level control.
5. Policies declare what access requires
AccessPolicy<R, G> is how you describe who should be allowed through.
Important onboarding note: policy requirements use OR semantics. If any configured role, group, or permission requirement matches, authorization succeeds.
use AccessPolicy;
use Group;
use Role;
let policy = require_role
.or_require_role_or_supervisor
.or_require_group
.or_require_permission;
assert!;
This is a good fit for rules like:
- admins may enter
- moderators and above may enter
- members of a specific emergency group may enter
- anybody with a dedicated override permission may enter
6. The authorization service evaluates the policy
AuthorizationService turns the policy into an authorization decision for a specific account.
use Account;
use AccessPolicy;
use AuthorizationService;
use Group;
use Role;
let account = new
.with_roles;
let policy = require_role
.or_require_role;
let service = new;
assert!;
A practical onboarding example
This example shows a realistic setup for an internal admin page.
use Account;
use AccessPolicy;
use AuthorizationService;
use Group;
use Role;
let mut account = new
.with_roles
.with_groups;
account.grant_permission;
account.grant_permission;
let policy = require_role
.or_require_group
.or_require_permission;
let authz = new;
assert!;
Here access succeeds even though the user is not an admin, because the policy accepts any matching requirement and the account belongs to the support group.
Authentication boundary: credentials and verification
webgates-core does not implement password hashing or login services for you. Instead, it gives you a clean boundary:
Credentials<Id>holds user-supplied identifier + plaintext secretCredentialsVerifieris the trait you implement to check those credentials against your own backend
use Credentials;
let credentials = new;
assert_eq!;
This separation is useful because it keeps your domain model independent from:
- which database you use
- how you hash passwords
- which framework handles the request
- whether login happens over HTTP, gRPC, CLI, or background jobs
Validate permissions during tests
If your application defines many permission strings, validate them in tests so collisions or duplicate definitions are caught early.
use validate_permissions;
validate_permissions!;
This is especially helpful when your permission surface grows over time or is shared across multiple modules.
Recommended onboarding path
If you are new to the crate, I recommend learning it in this order:
accounts::Accountroles::Roleandgroups::Grouppermissions::Permissionsauthz::access_policy::AccessPolicyauthz::authorization_service::AuthorizationServicecredentials::Credentialsandcredentials_verifier::CredentialsVerifier
That sequence mirrors how most applications adopt the crate.
Which crate should you use?
Choose based on how much infrastructure you want out of the box:
- use
webgates-corewhen you only want domain types and authorization primitives - use
webgateswhen you want the main user-facing composition crate - use
webgates-axumwhen you want Axum integration - use
webgates-sessionswhen you want framework-agnostic session lifecycle primitives - use
webgates-codecswhen you need JWT and codec support - use
webgates-repositorieswhen you want repository contracts and storage integrations - use
webgates-secretswhen you need hashing and secret handling helpers
Design goals
This crate is intentionally designed to be:
- framework-agnostic
- transport-agnostic
- easy to test
- small in dependency footprint
- usable in server and WASM contexts
- explicit about authorization behavior
Security notes
A few important things to keep in mind:
Credentialscontains plaintext secrets and should be short-lived- do not log secrets, tokens, or raw credential data
- use secure transport when credentials cross a process or network boundary
- treat permission names as application API and keep them stable once adopted
- validate your permission set in CI if your app depends heavily on string permissions
Related crates
webgates- the main composition crate for most applicationswebgates-axum- Axum integrationwebgates-codecs- JWT and codec supportwebgates-repositories- repository traits and backendswebgates-secrets- secret and hashing helperswebgates-sessions- session lifecycle and renewal primitiveswebgates-tonic- tonic integration
License
MIT