Skip to main content

Authorization and resource boundary

Authentication identifies the user; authorization decides which guild or resource they may change. GuildGate leaves that decision to the application during action execution.

Resolve the resource on the server​

A guildId from route parameters or the request body is only a candidate. Verify on the server that the user belongs to the Discord guild and holds the required permission.

resource: (input) => ({ type: "guild", id: input.guildId }),

authorize: async (context, input) => {
if (!context.userId) {
return { allowed: false, code: "session_required" };
}

const access = await discordAccess.resolveGuildAccess({
userId: context.userId,
guildId: input.guildId,
});

return access.canManage
? { allowed: true }
: { allowed: false, code: "missing_manage_guild" };
},

Never treat client-provided isAdmin, owner, role, or permission fields as an authority source.

Separate user and bot permissions​

An action may have two distinct requirements:

  • the user may change the setting
  • the bot may perform the Discord operation

Even when the user is authorized, do not start an operation when the bot role is below the target role. Evaluate Discord permission bits as BigInt.

Refresh authorization at write time​

The guild list collected during OAuth may be cached for fast UI rendering, but the security decision should use current data at action time. When caching access data, use a short TTL, explicit invalidation, and permission-loss tests.

Owner policy​

The configured owners may change maintenance state or write block policies. An owner still:

  • passes origin checks
  • satisfies session and CSRF requirements
  • is rate limited
  • creates audit records

Owner status does not automatically inherit another user's normal action permissions. When such behavior is required, add an explicit and auditable rule to the action.

Maintenance and blocks​

await gate.owner.setMaintenance({
enabled: true,
reason: "database migration",
allowOwners: true,
});

await gate.owner.block({
subjectType: "guild",
subjectId: guildId,
reason: "abuse investigation",
});

await gate.owner.unblock("guild", guildId);

A block subject may be user, guild, or ip. Hash or narrow IP data according to the audit and logging policy; avoid retaining unnecessary raw personal data.

Permission loss and realtime​

Channel access is checked when a realtime subscription is opened. Revalidate session and channel access periodically for long-lived connections. Broadcast session revocation across workers and close matching connections.

Test matrix​

Test at least these cases for every guarded action:

  • no session
  • expired session
  • invalid origin or CSRF
  • user is not a guild member
  • insufficient user permission
  • insufficient bot permission
  • permission removed immediately before the write
  • maintenance mode or active block
  • owner and normal-user paths