Skip to main content

authorize

authorize is the one rule an RPC handler applies before it does anything on behalf of a player: an operator reaches anything, anyone else reads world state and reaches only their own entity, and a request with no acting player is an addon acting for itself.

Nothing here defends against a hostile pack, which runs arbitrary script and can write the underlying dynamic properties directly. What it enforces is that a player driving a UI or a command cannot reach what is none of their business — see the trust model.

Import​

import { authorize, denyReason, isOperator } from '@bedrock-core/server';
import type { AccessTarget, Operation } from '@bedrock-core/server';

Signature​

authorize(target: AccessTarget, actorId: string | undefined, operation: Operation): void

Parameters​

ParameterTypeDefaultDescription
target *AccessTarget—What the request reaches: { world: true }, { dimension: id }, { entity: id } or { block: id }
actorId *string | undefined—The player the request is made on behalf of; undefined for an addon acting programmatically
operation *'read' | 'write'—What the request does

Returns​

Nothing. Throws Error('refused: <reason>') when the request must be refused, so a handler's promise rejects with the reason and the caller learns why.

Usage​

core.rpc.serve<EconomyApi>({
balance: ({ playerId, actorId }) => {
authorize({ entity: playerId }, actorId, 'read');

return balances.for(playerOf(playerId)).get();
},

deductGold: ({ playerId, gold, actorId }) => {
authorize({ entity: playerId }, actorId, 'write');

const doc = balances.for(playerOf(playerId));

doc.patch({ gold: (doc.get()?.gold ?? 0) - gold });

return doc.get();
},
});

The caller passes actorId when a player is behind the request — a screen, a command — and omits it when the addon acts for itself.

The rule​

SituationOutcome
No actorIdAllowed. An addon acting programmatically, not a player.
Actor is not in the worldRefused — acting player '<id>' is not in the world
Actor is a world operatorAllowed anywhere
Anyone else, reading a world, dimension or block targetAllowed — world state is not a secret from the player in it
Anyone else, writing a world, dimension or block targetRefused — a <kind> target may only be changed by an operator
Anyone else, their own entityAllowed, to read and to write
Anyone else, another entityRefused — a non-operator may only reach their own document

denyReason​

denyReason(target: AccessTarget, actorId: string | undefined, operation: Operation): string | undefined

The same rule as a value: the reason a request must be refused, or undefined when it is allowed. authorize is denyReason, thrown.

isOperator​

isOperator(player: Player): boolean

Whether the player is a world operator. Reads player.playerPermissionLevel, which is readonly on Player, and deliberately not commandPermissionLevel, which is mutable and could be rewritten by any script in the world — authorization must never rest on a value another addon can hand itself. PlayerPermissionLevel.Custom is a separate bucket, not a tier above Operator, so it is not accepted. A player whose handle has been invalidated is not an operator.

Notes​

  • The rule keys off the actor, never the calling pack. Script events carry no sender identity a rule could rest on.