business-logic-ensure-authorized-requester
Ensure Authorized Requester for Business Logic Entry Points
Goal
Every business-logic entry point that requires authorization must include an ensure requester is authorized business constraint, placed after authentication and before any business operation runs.
This constraint follows the ensure ... formalism. Restate the rule as:
ensure requester is authorized
Translate that formulation into the syntax, naming, and control-flow conventions of the language in use. The function name is always the same generic phrasing across entry points; the action-specific authorization policy lives inside the function body.
This constraint must be a module-private function, defined inside the same module as the business-logic entry point it protects. It is not exported, not reused across modules, and not accessible to other entry points.
On success the constraint returns a unit-equivalent value (no business data). On failure it returns an error indicating that the requester is not authorized to perform the action.
What Counts as In Scope
Apply this skill to code that does one or more of these things:
- defines a business-logic entry point that must only run for requesters with permission to perform its action
- checks roles, permissions, capabilities, ownership, tenancy, scopes, or policy decisions before executing business logic
- protects a business operation from execution by an authenticated but unauthorized requester
- extracts authorization verification logic into a helper, method, policy, or validator inside business logic
Relationship to Other Skills
This skill is a specialization of the general business-logic-ensure-business-constraints skill. All rules from that skill apply here. This skill adds specificity about what the constraint checks, where it must appear, what it is named, and where it must live:
- The constraint always expresses
ensure requester is authorized, using that exact generic phrasing translated into the local naming style. - The constraint must be a module-private function in the same module as the entry point.
- The constraint must be placed after
ensure requester is authenticatedand before any other business constraint, business operation, or data mutation. - The constraint verifies authorization for the specific action of the entry point, not identity authentication, generic role membership, or unrelated business rules.
- The constraint returns a unit-equivalent value on success, in line with the general
ensure business constraintsskill.
This skill is the authorization counterpart of business-logic-ensure-authenticated-requester. Authentication answers "who is this requester?"; authorization answers "is this requester allowed to perform this entry point's action?". Keep the two checks separate.
Ensure Rule
-
Name the constraint with the fixed generic phrasing.
- Use the exact name
ensure requester is authorized, translated into the local naming style:ensureRequesterIsAuthorized,ensure_requester_is_authorized,EnsureRequesterIsAuthorized, or the closest local equivalent. - Do not vary the function name per entry point. The name is generic; the body is action-specific.
- Do not use action-specific names such as
ensureRequesterCanCreateReservationor generic non-ensure ...names such ascheckPermissions,hasAccess, orisAuthorized.
- Use the exact name
-
Define the constraint as module-private inside the entry point's module.
- The function lives in the same file or module as the business-logic entry point it protects.
- Use the language's idiomatic visibility for module-private functions: no
export, leading underscore,private, package-private, file-internal, or the project's local convention. - Do not export this function from the module. Do not place it in a shared module for reuse across entry points.
- Each entry point's module has its own
ensure requester is authorizedfunction, tailored to its own action. Two entry points never share the same function.
-
Place this constraint after authentication and before any business operation.
- It runs after
ensure requester is authenticatedso the requester id is available. - It runs before any other business constraint that depends on authorization, before any data mutation, and before any business operation.
- It runs after
-
Encode the action-specific policy inside the function body.
- The body checks whether the requester is allowed to perform this entry point's specific action.
- Use the requester id, the input fields of the command or query, and any project-level policy primitives to evaluate the rule.
- The body may delegate to a shared policy, rule engine, or domain service if the project provides one. The module-private function still wraps that delegation under the generic name.
-
The constraint returns a unit-equivalent value on success.
- Use the unit-equivalent value of the language or project:
void,unit,undefined,None, or the success side of a result wrapper. - Do not return booleans, roles, policies, permissions, or any other data from this constraint.
- Use the unit-equivalent value of the language or project:
-
The constraint fails with an error when the requester is not authorized.
- Use the project's error convention, such as an error value, thrown domain error, result error, or equivalent failure construct.
- Make the error correspond to the specific violation: the requester is not authorized to perform that action.
- Distinguish this error from the unauthenticated-requester error so callers and observability can tell the two apart.
-
Keep the constraint focused on authorization for this action only.
- Do not mix authentication, eligibility, availability, or unrelated business rules into this constraint.
- Use separate
ensure ...constraints for those concerns.
Input Convention
The authorization constraint needs at least the requester identity and any inputs required to evaluate the policy for the action (for example, a target resource id, tenant id, or owner id).
When the business-logic-entry-point-execution-context skill is active in the project, the constraint retrieves the requester identity from the execution context and accepts only the action-specific inputs as parameters. The command or query type does not need to carry the requester identity.
When the execution context skill is not active, the constraint receives the requester id as an explicit parameter, typically the requester id returned by ensure requester is authenticated. Pass the action-specific inputs explicitly as well. Do not reach outside the entry point's input or the prior authentication result to obtain authorization-relevant data.
Determining Whether an Entry Point Requires Authorization
Most business-logic entry points that require authentication also require authorization. Some genuinely do not — for example, an entry point that any authenticated requester is allowed to invoke without further restriction.
Apply this skill when at least one of the following holds for the entry point:
- The action operates on resources owned by, or scoped to, a specific requester, tenant, or organization.
- The action requires a role, permission, capability, scope, or policy decision beyond being authenticated.
- The action is restricted by business rules that depend on the requester's identity or relationship to the resource.
If none of these hold and the entry point is genuinely open to any authenticated requester, document that decision explicitly in the entry point and skip this constraint. Do not treat the absence of an authorization check as a default; it must be a deliberate, justified choice.
Detection Workflow
-
Find business-logic entry points first.
- Identify command handlers, query handlers, and other business-logic entry points.
- Determine which entry points must restrict execution to requesters with permission for the specific action.
-
Check for the presence of the authorization constraint.
- Look for an
ensure requester is authorizedfunction or its local equivalent in the same module as the entry point. - Verify that it is module-private, not exported, and not shared across modules.
- Verify that it is invoked after authentication and before any business operation.
- Look for an
-
Check the constraint shape.
- Verify that the function uses the fixed generic name, not an action-specific or unrelated name.
- Verify that success returns a unit-equivalent value.
- Verify that failure produces an error indicating the requester is not authorized.
-
Check the constraint focus.
- Verify that the body encodes the specific authorization policy for this entry point's action.
- Verify that it does not also check authentication, availability, eligibility, or unrelated rules.
-
Prefer semantic classification to syntax alone.
- Do not classify a check as the authorization constraint only because it appears after authentication.
- Classify it by whether it verifies that the requester is allowed to perform the action.
Writing or Changing the Authorization Constraint
-
Name the constraint with the fixed generic phrasing.
- Translate
ensure requester is authorizedinto the local naming convention. - Do not vary the name per entry point.
- Translate
-
Define it as module-private in the entry point's module.
- Place the function in the same file or module as the entry point it protects.
- Use the language's idiomatic module-private visibility.
- Do not export it. Do not move it to a shared utilities module.
-
Place the call after authentication and before any business operation.
- After
ensure requester is authenticated. - Before any other business constraint that depends on the requester being authorized, before data reads that should be policy-filtered, and before any data mutation.
- After
-
Return a unit-equivalent value on success.
- Do not return booleans, roles, policies, or any other data from this constraint.
- Let the absence of error mean the requester is authorized.
-
Return or raise a meaningful error on failure.
- Use an error type or value that explains that the requester is not authorized to perform the action.
- Keep this error distinct from the unauthenticated-requester error.
-
Encode the action-specific policy in the body.
- Express the policy in business terms: ownership, role, capability, scope, tenancy, relationship, or whichever rule the entry point requires.
- Delegate to shared policy primitives, rule engines, or domain services where the project provides them, but keep the module-private wrapper as the single entry-point-side call site.
-
Do not share the function across entry points.
- If two entry points need the same authorization rule, each module still defines its own
ensure requester is authorizedfunction and may delegate to a shared policy primitive. - The shared primitive is not the constraint; the module-private function is.
- If two entry points need the same authorization rule, each module still defines its own
Examples
TypeScript with execution context and neverthrow:
// create-reservation-command-handler.ts
function ensureRequesterIsAuthorized(
command: CreateReservationCommand,
): ResultAsync<void, RequesterIsNotAuthorized> {
const ctx = getExecutionContext();
const requesterId = ctx.requesterId;
// policy specific to creating a reservation, using requesterId and command fields
if (!isAllowed) {
return errAsync(new RequesterIsNotAuthorized());
}
return okAsync(undefined);
}
export function createReservationCommandHandler(
command: CreateReservationCommand,
): ResultAsync<CreateReservationCommandHandlerSuccess, CreateReservationCommandHandlerError> {
return ensureRequesterIsAuthenticated()
.andThen((requesterId) =>
ensureRequesterIsAuthorized(command).map(() => requesterId),
)
.andThen((requesterId) => {
// other business constraints and business logic using requesterId
});
}
TypeScript with neverthrow (explicit passing, for languages without execution context):
// cancel-order-command-handler.ts
function ensureRequesterIsAuthorized(
requesterId: RequesterId,
command: CancelOrderCommand,
): ResultAsync<void, RequesterIsNotAuthorized> {
// policy specific to cancelling an order, using requesterId and command.orderId
}
export function cancelOrderCommandHandler(
command: CancelOrderCommand,
): ResultAsync<CancelOrderCommandHandlerSuccess, CancelOrderCommandHandlerError> {
return ensureRequesterIsAuthenticated(command.requesterId)
.andThen((requesterId) =>
ensureRequesterIsAuthorized(requesterId, command).map(() => requesterId),
)
.andThen((requesterId) => {
// other business constraints and business logic using requesterId
});
}
TypeScript with exceptions:
// cancel-order-command-handler.ts
async function ensureRequesterIsAuthorized(
requesterId: RequesterId,
command: CancelOrderCommand,
): Promise<void> {
// policy specific to cancelling an order
// throw RequesterIsNotAuthorized if not allowed
}
export async function cancelOrderCommandHandler(
command: CancelOrderCommand,
): Promise<CancelOrderCommandHandlerSuccess> {
const requesterId = await ensureRequesterIsAuthenticated(command.requesterId);
await ensureRequesterIsAuthorized(requesterId, command);
// other business constraints and business logic using requesterId
}
Python (leading underscore marks module-private):
# cancel_order_command_handler.py
def _ensure_requester_is_authorized(
requester_id: RequesterId,
command: CancelOrderCommand,
) -> None:
# policy specific to cancelling an order
# raise RequesterIsNotAuthorized if not allowed
...
def cancel_order_command_handler(
command: CancelOrderCommand,
) -> CancelOrderCommandHandlerSuccess:
requester_id = ensure_requester_is_authenticated(command.requester_id)
_ensure_requester_is_authorized(requester_id, command)
# other business constraints and business logic using requester_id
Kotlin (file-internal visibility marks module-private):
// CancelOrderCommandHandler.kt
private fun ensureRequesterIsAuthorized(
requesterId: RequesterId,
command: CancelOrderCommand,
) {
// policy specific to cancelling an order
// throw RequesterIsNotAuthorized if not allowed
}
fun cancelOrderCommandHandler(
command: CancelOrderCommand,
): CancelOrderCommandHandlerSuccess {
val requesterId = ensureRequesterIsAuthenticated(command.requesterId)
ensureRequesterIsAuthorized(requesterId, command)
// other business constraints and business logic using requesterId
}
Review Questions
When reading or reviewing code, ask:
- Does this entry point require an authorization check beyond authentication?
- Is there an
ensure requester is authorizedfunction or its local equivalent in the same module as the entry point? - Is the function module-private and not exported or shared across modules?
- Does the function use the fixed generic name rather than an action-specific or unrelated name?
- Is it invoked after authentication and before any business operation or data mutation?
- Does the body encode the specific authorization policy for this entry point's action?
- Does it return a unit-equivalent value on success?
- Does it produce an error distinct from the unauthenticated-requester error when the requester is not allowed to perform the action?
- Does it avoid mixing in authentication, eligibility, availability, or unrelated business rules?
- If the entry point intentionally has no authorization check, is that decision explicit and justified?
If the answer is yes, apply this skill.
Report the Outcome
When finishing the task:
- state which entry points were identified or changed
- state where the
ensure requester is authorizedconstraint was added or verified - state how the name was translated into the local language convention and how module-private visibility was applied
- state that the function lives in the same module as the entry point and is not shared across modules
- state that the constraint returns a unit-equivalent value on success and which failure error shape was used
- state that the constraint is placed after authentication and before any business operation at the entry point
- state any entry point that was intentionally left without an authorization check and the reason
More from code-sherpas/agent-skills
neverthrow-return-types
Require `neverthrow`-based return types in TypeScript and JavaScript code whenever the surrounding technology allows it. Use when creating, refactoring, reviewing, or extending standalone functions, exported module functions, class methods, object methods, service methods, repository methods, and similar APIs that should expose explicit success and failure result types in their signatures. Prefer `Result<T, E>` for synchronous code and `ResultAsync<T, E>` for asynchronous code. Only skip a `neverthrow` return type when a framework, library, runtime interface, or externally imposed contract is incompatible and requires a different return shape.
16neverthrow-wrap-exceptions
Capture exceptions and promise failures with `neverthrow` instead of hand-written `try/catch` in TypeScript and JavaScript code. Use when wrapping synchronous functions that may throw, promise-returning functions that may throw before returning, existing `PromiseLike` values that may reject, or third-party APIs such as parsers, database clients, HTTP clients, file-system helpers, serializers, and SDK calls. Prefer `Result.fromThrowable` for synchronous throwers, `ResultAsync.fromThrowable` for promise-returning functions that may throw or reject, and `ResultAsync.fromPromise` when you already have a `PromiseLike` value in hand. Only keep `try/catch` when the language construct, cleanup requirement, or framework boundary truly requires it.
11atomic-design
Create or update web UI components with a strict reuse-first workflow. Use when building, refactoring, restyling, or extending frontend or template components while minimizing raw DOM or HTML by reusing or generalizing existing components first.
10write-persistence-representations
Create or update persistence-layer data representations in any stack, including ORM entities, schema definitions, table mappings, document models, collection definitions, and similar database-facing code. Use when agents needs to add or change persisted fields, identifiers, relationships, indexes, timestamps, auditing fields, or storage mappings in frameworks, libraries, or ORMs such as Prisma, TypeORM, Sequelize, Drizzle, Mongoose, Hibernate/JPA, Doctrine, Ecto, Active Record, or equivalent persistence technologies.
7business-logic
Identify, interpret, review, or write business logic in code. Use when an agent needs to decide whether code expresses business rules, business algorithms, or business workflows, or when it must implement, preserve, or refactor code that creates, stores, or transforms data according to real business policies.
7immutable-domain-entities
Require the immutable design pattern for domain entities. Use when an agent needs to create, modify, review, or interpret domain entities and should preserve identity while expressing state changes through new immutable instances. Domain entities must be modeled as immutable classes, not as plain type aliases or interfaces paired with standalone functions.
7