Protect an operation
Authentication answers who is calling. Authorization answers what that caller may do. A successful login does not entitle a user to every note in a database.
Put the access requirement on the operation
The notes application's inputs implement iet_core::operation::Operation with
Access::Public. A service calls operation::check before doing work. That call
checks the access requirement and then validates the input.
For an operation that requires a signed-in user, the descriptor becomes:
// Fragment: CreateNote is your input type and implements Validate + Sync.
The service still calls check::<CreateNote, _>(authorizer, context, &input).await?.
With an empty context, that now fails with Unauthenticated; it does not create a
login mechanism. Your adapter must authenticate credentials using a trusted
provider and build the context from the resulting principal.
Never deserialize a client-supplied Principal or subject directly into trusted
context. CallContext is metadata, not cryptographic proof of identity. See
authentication for producing that trusted value.
Require a permission when identity is not enough
Use Access::Permission("notes.create") for a named permission. check first
requires a nonempty principal, then invokes your iet_core::Authorizer, then
validates the input. The permission string is application policy, not a built-in
role name.
iet_authz::DenyPermissions is useful while no permission system is selected:
permission-protected operations fail closed. It does not deny public operations,
because those do not ask the authorizer for a decision.
If you need roles, iet-auth-rbac::Rbac<S> delegates to a RoleStore. If you need
permissions on specific resources, iet-auth-acl::Acl<S> delegates to an
AccessStore through ResourceAuthorizer. Select those only when your policy
needs them; an ownership check does not require inventing a role hierarchy.
Enforce ownership separately
Access::Authenticated says nothing about the ID being accessed.
Access::Permission("notes.read") can grant an action without identifying a
particular row. For owner-scoped data, derive the owner from trusted context and
include it in the repository's read or mutation predicate.
Do not fetch or update a row solely by its client-supplied ID, and do not trust
an owner query parameter as proof of ownership. The
persistence guide shows typed owner predicates. The
framework does not automatically inject tenant or owner filters.
Test the denial path
Keep direct service tests for:
- No principal on an authenticated operation.
- A principal without the required permission.
- A different user's resource, even when the caller has the general permission.
- Authorization backend failure, which must remain an error rather than an allow.
iet_authz::All requires both authorizers to succeed. Any tries the second only
when the first returns Forbidden; an unavailable backend is not interpreted as
a denial that can be bypassed by another check.
Source: crates/core/iet-core/src/operation.rs and
crates/core/iet-authz/src/lib.rs.