When a player logs in to claim a daily reward, a moderator bans a suspicious account, or a live-ops admin pushes a change to drop rates, something has to decide whether that action is allowed. In online gaming, that decision runs thousands of times a minute, across sessions, services, and player roles that carry very different levels of trust. Get it wrong and you are not just looking at a support ticket. You are looking at an opening attackers already know how to find.
Layer 7 DDoS attacks against gaming platforms climbed 94% year over year, according to Akamai's security research team. Bot traffic targeting games grew 391% from the first quarter of 2023 to the first quarter of 2024, and January 2024 alone saw 147 billion bot requests hit gaming platforms, a single-month record. These are not isolated outages. They are large-scale probing of access control surfaces, looking for the gap between what a player is supposed to do and what the system will actually let them do.
Gaming makes that gap harder to close than most industries do. A player's permissions shift constantly as they level up, unlock content, or get flagged for review. Moderators carry elevated access that has to stay narrow even as it grows. Engineers need to look at live production data during an incident without ever touching a player's personal information. None of these are technical problems first. They are business rules, and they need to hold the same way whether the request comes from the game client, a partner API, or an internal admin tool.
This is where Cerbos, the externalized authorization management platform fits.
This piece walks through three authorization patterns pulled from real gaming systems: player profile and inventory access, virtual currency and item trading, and moderation actions tied to live operations. Each one starts with the business rule and ends with a working Cerbos policy. To try the examples yourself, the Cerbos quickstart gets a policy decision point running locally in under five minutes.
Why basic role checks break down fast
Authentication tells you who a player is. Authorization tells you what they are allowed to do. Most gaming security incidents live in the gap between those two things. A stolen session token still has to clear authorization checks before it can do real damage. If those checks are too coarse, the attacker inherits everything the real player could touch.
Gaming makes that gap wider than most industries do. A player's access is never fixed. They unlock regions, earn roles, climb account tiers, and pick up permissions tied to items, battle passes, and creator programs. A brand-new player and a three-year veteran on the same platform can have completely different permission profiles, and neither one maps cleanly onto a flat role hierarchy.
Aaron Zollman, Vice President and Deputy CISO for Gaming at Microsoft, has pointed to this exact failure mode at platform scale. Without tight identity governance, he notes that credential sprawl creates highly privileged accounts that become prime targets, and that overlapping permissions across teams need active governance rather than a one-time setup. The same logic applies one level down, inside the player and moderator permission model itself.
OWASP backs this up at the application layer. Broken access control has topped its risk list across multiple consecutive editions, showing up in 94% of tested applications, wait, let me correct that citation in a moment, with failures occurring when users act outside their intended permissions. In gaming, that translates directly into item duplication exploits, currency manipulation, and account takeovers that quietly erode player trust.
A flat role-based access control (RBAC) model is usually where teams start. Give a player the "player" role, a moderator the "moderator" role, and define what each can do. That holds up until the rules get specific. A player should only trade items they actually own. A moderator should only act on the cases assigned to them, not the entire player base. A senior moderator reviewing a ban appeal needs permissions that a junior moderator triaging a chat report should not have.
None of this is an edge case. It is the normal shape of access control on any live-service game. RBAC covers the first layer. To handle the rest, you need attribute-based access control (ABAC), where the decision also weighs user attributes, resource attributes, and context. The table below shows how the two compare.
| Model | What it can express | Where it falls short |
|---|---|---|
| RBAC | "Moderators can suspend accounts" | Cannot scope to assigned cases only |
| ABAC | "Moderators can suspend accounts assigned to them" | Conditions live in code, not policy |
| PBAC | Both, expressed as testable, auditable policy files | Requires an external policy engine |
Gaming platforms benefit most from Policy-Based Access Control (PBAC): policies that combine role membership with attribute conditions. This is the approach the scenarios below use. Each policy is expressed and evaluated using Cerbos, which externalizes authorization logic from application code and lets you manage, test, and audit access rules as a standalone layer.
Map the rule before you write the policy
Before any of this turns into Cerbos policy, it pays to map the actual business rules: who is requesting access, what they want to do, and which resource they're touching. This is not busywork before the "real" work starts. It is itself a risk exercise. Naming every principal, action, and resource boundary up front is how you find access gaps before they ship, not after. Whatever stays undocumented at this stage usually resurfaces later as an authorization bug. For a deeper walkthrough of this process, see our guide on mapping business requirements to authorization policy.
Where the attributes actually come from
Cerbos does not own or store any of these attributes. It receives them at request time and evaluates them against your policy, nothing more. Things like player_tier, owned_items, or assigned_cases are principal attributes, and they usually come straight from your identity provider or user directory, whatever your application already has on hand in a JWT, a session object, or a user record. Resource attributes like ownedBy, item_rarity, or case_status come from the database or service that actually owns that data, pulled in at the moment the authorization check happens. Your systems stay the source of truth. Cerbos just makes the call.
The two files that do the actual work
A Cerbos policy is a YAML file describing who can do what, to which resource, and under which conditions. Two files carry that weight. derived_roles.yaml defines roles computed on the fly, based on attributes in the request, which is what makes a rule like "only if you own this item" possible without hardcoding a check for every resource.
resource_policy.yaml then attaches specific actions to those roles, with whatever conditions apply, for a given resource type. Neither file touches your application code or your identity provider. You write the YAML, deploy it next to your Cerbos Policy Decision Point (PDP), and your app calls that PDP to get back an allow or deny. The policy does the rest.
Scenario 1: Player profile and inventory access
Picture a live-service game where several roles touch the same player profiles and inventory records, each with a different reason to be there. A player can view and edit their own profile and manage the items they own. A support agent can view profiles in their queue but can't change anything. A senior agent working an escalated case can update that profile, but only while the case is flagged high priority. An admin can view any profile and delete ones that violate the terms of service.
Role membership alone can't express half of this. The support agent case is the clearest example: the same role gets different permissions depending on a resource attribute (case priority) and a relationship attribute (whether that profile is even assigned to them). That's two conditions stacked on one role, and it's exactly what ABAC is for.
This isn't a theoretical edge case. Video gaming had a 7.6% suspected digital fraud rate in 2023, up 32.6% from the year before, according to TransUnion's 2024 State of Omnichannel Fraud Report. Account takeover was the single largest category of digital fraud globally that year. A policy that actually enforces who owns a profile, and who's allowed to touch it, is one of the more direct defenses against that.
Principals, actions, and resources
Principals: Player, Admin, Support Agent, Senior Support Agent
Actions: view, update, delete
Resource: Player Profile (account details, inventory list, game progress, payment methods)
Authorization policy
Allow access to player profiles based on ownership, assignment, and case priority. Players can view and update their own profiles. Admins can view any profile and delete records that violate terms of service. Support agents can view profiles assigned to them. Senior support agents can update a profile only when the assigned case is high priority.
Key attributes
| Category | Attribute | Description |
|---|---|---|
| Principal | assigned_profiles | Profile IDs assigned to this support agent |
| Principal | agent_tier | Support tier: standard or senior |
| Resource | ownedBy | Player ID that owns this profile |
| Resource | isCompliant | Whether the account meets platform terms |
| Resource | case_priority | Priority level of the active support case |
Cerbos policy
derived_roles.yaml
apiVersion: api.cerbos.dev/v1
derivedRoles:
name: profile_roles
definitions:
- name: profile_owner
parentRoles: ["player"]
condition:
match:
expr: request.resource.attr.ownedBy == request.principal.id
- name: assigned_support_agent
parentRoles: ["support_agent", "senior_support_agent"]
condition:
match:
all:
of:
- expr: request.resource.id in request.principal.attr.assigned_profiles
- expr: request.principal.attr.agent_tier in ["standard", "senior"]
resource_policy.yaml
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: "default"
resource: "player_profile"
importDerivedRoles:
- profile_roles
rules:
- actions: ["view"]
effect: EFFECT_ALLOW
derivedRoles: ["profile_owner"]
- actions: ["update"]
effect: EFFECT_ALLOW
derivedRoles: ["profile_owner"]
- actions: ["view", "update"]
effect: EFFECT_ALLOW
roles: ["admin"]
- actions: ["delete"]
effect: EFFECT_ALLOW
roles: ["admin"]
condition:
match:
expr: request.resource.attr.isCompliant == false
- actions: ["view"]
effect: EFFECT_ALLOW
derivedRoles: ["assigned_support_agent"]
- actions: ["update"]
effect: EFFECT_ALLOW
derivedRoles: ["assigned_support_agent"]
condition:
match:
all:
of:
- expr: request.resource.id in request.principal.attr.assigned_profiles
- expr: request.principal.attr.agent_tier == "senior"
- expr: request.resource.attr.case_priority == "high"
Note: The assigned_support_agent derived role is granted when the profile ID appears in the agent’s assigned_profiles list and the agent’s agent_tier is either standard or senior. Update permission then adds a third condition: the tier must be senior and the case priority must be high. A standard agent viewing an assigned profile gets read access. The same agent trying to update it gets denied, without any application-level check required. Derived roles handle the relationship encoding cleanly, and the resource policy handles the permission logic on top.
Scenario 2: In-game economy and virtual asset management
Skins, weapons, mounts, currency, and cosmetics carry actual economic weight inside a platform, and increasingly outside it once trading and resale markets get involved. That makes the in-game economy one of the highest-risk surfaces in any live-service title.
The rules sound simple at first glance. Players use what they own, trade what's tradeable, spend from their own wallet. The complexity shows up in the conditions stacked on each action. An item might only become tradeable after a cooldown period. A trade might only go through if the account has been active for 30 days or more. A reward might only count as legitimate if it came from actual gameplay rather than a promo code. None of that fits cleanly into a role definition.
This is not a small-dollar problem. Roblox reported roughly $109.5 million in credit card chargeback fraud for its 2023 financial year, a figure documented in peer-reviewed research on cybercrime in online video gaming. Weak authorization on inventory and trade endpoints is exactly the kind of gap that this sort of abuse exploits, whether that's letting someone trade an item they don't actually own, or bypassing the conditions a legitimate trade is supposed to meet. Getting these checks right at the resource level is what keeps the economy trustworthy enough for players to keep spending in it.
Principals, actions, and resources
Principals: Player, Admin, Economy Manager
Actions: view, use, trade, adjust
Resource: Virtual Asset (inventory items, in-game currency, tradeable cosmetics)
Authorization policy
Allow players to view and use items they own. Allow players to trade items that are marked as tradeable, provided their account has been active for at least 30 days. Allow economy managers to adjust item metadata. Allow admins to perform any action. Deny all access to items flagged as under investigation.
Key attributes
| Category | Attribute | Description |
|---|---|---|
| Principal | account_age_days | Number of days since account creation |
| Principal | owned_items | List of item IDs this player owns |
| Resource | ownedBy | Player ID that owns this asset |
| Resource | is_tradeable | Whether the item can be traded |
| Resource | under_investigation | Whether the item is flagged for review |
Cerbos policy
derived_roles.yaml
apiVersion: api.cerbos.dev/v1
derivedRoles:
name: asset_roles
definitions:
- name: item_owner
parentRoles: ["player"]
condition:
match:
all:
of:
- expr: request.resource.attr.ownedBy == request.principal.id
- expr: request.resource.id in request.principal.attr.owned_items
- name: eligible_trader
parentRoles: ["player"]
condition:
match:
all:
of:
- expr: request.resource.attr.ownedBy == request.principal.id
- expr: request.resource.attr.is_tradeable == true
- expr: request.principal.attr.account_age_days >= 30
resource_policy.yaml
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: "default"
resource: "virtual_asset"
importDerivedRoles:
- asset_roles
constants:
local:
MIN_ACCOUNT_AGE: 30
rules:
- actions: ["view", "use"]
effect: EFFECT_ALLOW
derivedRoles: ["item_owner"]
condition:
match:
expr: request.resource.attr.under_investigation == false
- actions: ["trade"]
effect: EFFECT_ALLOW
derivedRoles: ["eligible_trader"]
condition:
match:
expr: request.resource.attr.under_investigation == false
- actions: ["adjust"]
effect: EFFECT_ALLOW
roles: ["economy_manager"]
condition:
match:
expr: request.resource.attr.under_investigation == false
- actions: ["view", "use", "trade", "adjust"]
effect: EFFECT_ALLOW
roles: ["admin"]
- actions: ["view", "use", "trade", "adjust"]
effect: EFFECT_DENY
roles: ["*"]
condition:
match:
expr: request.resource.attr.under_investigation == true
Note: The item_owner derived role combines two checks: the resource’s ownedBy attribute must match the player’s principal ID, and the resource ID must appear in the player’s owned_items list. Both must be true before any use or view permission is considered. The eligible_trader role adds a third: the item must be tradeable and the account must meet the minimum age threshold. The deny rule on under_investigation items runs before any other rule and blocks all actions regardless of role. This is expressed once in the policy and enforced consistently across every interface that calls the PDP. The conditions documentation covers the full range of logical operators available.
Scenario 3: Moderation and studio operations
Moderation touches more sensitive ground than almost anything else on a live-service platform. Moderators need access to player reports, account histories, and sometimes the game server state itself. The danger here isn't only outside attackers. A moderation team with more access than it needs is its own liability, whether that shows up as an insider risk, a compliance gap, or a crack in platform integrity.
The actual business rules carry more nuance than they look like on paper. A content moderator can view and act on reports assigned to them, nothing more. A senior moderator can view any report and hand out temporary bans. A studio admin can issue a permanent ban, but only once a senior moderator's review is already on the case. A developer with live-ops access can read server state during an active incident without ever touching player data.
That admin case is the one worth sitting with. A permanent ban requires a two-step gate: no admin can execute it until a senior moderator's review is already logged against that case. This is a workflow condition with a built-in order of operations, and it needs to live inside the authorization policy itself, not bolted on as a separate approval tool that someone could route around.
Principals, actions, and resources
Principals: Content Moderator, Senior Moderator, Studio Admin, Developer
Actions: view, action, ban_temporary, ban_permanent, read_server_state
Resource: Moderation Case (player report, account history, review status, ban record)
Authorization policy
Allow content moderators to view and action reports assigned to them. Allow senior moderators to view any report and issue temporary bans. Allow studio admins to issue permanent bans only after a senior moderator review is recorded on the case. Allow developers to read server state during active incidents but deny access to player data modification.
Key attributes
| Category | Attribute | Description |
|---|---|---|
| Principal | assigned_cases | Case IDs assigned to this moderator |
| Principal | is_on_call | Whether the developer is in an active incident |
| Resource | senior_reviewed | Whether a senior moderator has reviewed the case |
| Resource | case_status | Current status: open, escalated, or closed |
| Resource | incident_active | Whether a live incident is currently active |
Cerbos policy
derived_roles.yaml
apiVersion: api.cerbos.dev/v1
derivedRoles:
name: moderation_roles
definitions:
- name: assigned_moderator
parentRoles: ["content_moderator"]
condition:
match:
expr: request.resource.id in request.principal.attr.assigned_cases
- name: on_call_developer
parentRoles: ["developer"]
condition:
match:
all:
of:
- expr: request.principal.attr.is_on_call == true
- expr: request.resource.attr.incident_active == true
resource_policy.yaml
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: "default"
resource: "moderation_case"
importDerivedRoles:
- moderation_roles
rules:
- actions: ["view", "action"]
effect: EFFECT_ALLOW
derivedRoles: ["assigned_moderator"]
condition:
match:
expr: request.resource.attr.case_status != "closed"
- actions: ["view", "ban_temporary"]
effect: EFFECT_ALLOW
roles: ["senior_moderator"]
- actions: ["view", "ban_temporary", "ban_permanent"]
effect: EFFECT_ALLOW
roles: ["studio_admin"]
condition:
match:
expr: request.resource.attr.senior_reviewed == true
- actions: ["read_server_state"]
effect: EFFECT_ALLOW
derivedRoles: ["on_call_developer"]
Note: The senior_reviewed condition on the ban_permanent action is the key gate here. A studio admin cannot issue a permanent ban until a senior moderator has reviewed the case and that review is recorded as a resource attribute. This enforces the workflow requirement at the authorization layer rather than relying on a separate process. The on_call_developer derived role uses both a principal attribute (is_on_call) and a resource attribute (incident_active) to grant read access to server state only during active incidents. Outside of that window, the developer role gets no access to moderation case data at all.
Test before you trust the policy
An untested policy is a policy that will eventually surprise you, usually at the worst possible time. Cerbos has a built-in testing framework that runs test cases right alongside your policy files. You write a principal, an action, a resource, and what you expect the outcome to be, then Cerbos checks whether the policy actually does that. Every scenario covered here, profile ownership, the trade conditions, the two-step ban gate, can be tried out directly in the Cerbos Playground, where you can run these exact test cases in the browser without setting anything up locally.
A test for the permanent ban gate looks like this:
moderation_ban_test.yaml
name: "Studio admin cannot issue permanent ban without senior review"
description: "Verify the senior_reviewed gate on ban_permanent"
principals:
studio_admin:
id: "admin_001"
roles: ["studio_admin"]
attributes:
group: "studio_ops"
resources:
unreviewed_case:
kind: "moderation_case"
id: "case_44"
attributes:
case_status: "escalated"
senior_reviewed: false
incident_active: false
tests:
- name: "Deny permanent ban when case has not been senior reviewed"
input:
principals: ["studio_admin"]
resources: ["unreviewed_case"]
actions: ["ban_permanent"]
expected:
- principal: studio_admin
resource: unreviewed_case
actions:
ban_permanent: EFFECT_DENY
Set senior_reviewed: true and rerun. The outcome flips to EFFECT_ALLOW. This kind of explicit, reproducible verification is how you build confidence that a policy behaves correctly across all the cases that matter, not just the happy path.
Why keeping authorization out of your code matters at scale
All three scenarios share the same underlying shape: the rules live in policy files, not buried in application code. The app sends a request to the Cerbos PDP with a principal, an action, and a resource. Cerbos checks that against the policy and sends back a decision. The app just enforces what it's told.
For a gaming platform, that separation earns its keep because access rules never sit still. A new season adds a player tier. A creator program spins up a role with scoped access to content tools. A regulator restricts what account data a platform can show players in a specific region. Bake authorization into application code and each of those becomes a code change, a review, and a deployment. Keep it externalized, and you edit the policy file, test it, and ship it through Cerbos Hub, no application deploy required. The update reaches every running PDP without a restart.
Compliance is the other half of this. A platform operating across multiple regions is juggling a growing list of data protection rules: GDPR for European players, COPPA for US players under 13, and a patchwork of regional limits on behavioral data and spending for minors. Externalizing authorization gives you something concrete to point to when a regulator or auditor asks how access to player data is actually governed: a policy file, not a scattered set of if-statements across a dozen services. Cerbos Hub backs that up with structured audit logs showing exactly which policy evaluated which request, and what it decided.
Key takeaways
-
Player permissions are not static. A new account and a veteran with hundreds of hours have fundamentally different access profiles, and a single role cannot express that difference cleanly.
-
Virtual assets carry real economic weight. Item ownership, trade eligibility, and investigation flags belong in the authorization layer, not in UI-level checks that can be bypassed through the API.
-
Moderation is an insider threat surface. Over-permissioned moderators are a compliance risk and a platform integrity risk. Scoping access to assigned cases and gating destructive actions behind review requirements should be policy, not process.
-
Workflow gates belong in authorization, not in separate tools. The senior review requirement before a permanent ban is expressed once in the policy and enforced at the PDP. A separate approval workflow can be bypassed. A deny rule cannot.
-
Gaming platforms change faster than most. New seasons, creator programs, and regional compliance requirements all touch access rules. When those rules live in policy files rather than application code, they can be updated, tested, and deployed without an engineering ticket.
-
Test with the cases that actually break things. Happy path tests are not enough. The interesting tests are the ones where a senior moderator tries to ban without a review on record, or a player tries to trade a flagged item.
Conclusion
Access control in games rarely stays confined to player accounts and admin dashboards. Live-service titles introduce new systems over time, from creator programs and community moderation tools to seasonal content, virtual economies, and cross-platform experiences. Each addition brings new authorization requirements that must be enforced consistently across the platform.
The examples in this guide illustrate a common pattern. A business rule starts as a product or operational requirement, then becomes an authorization policy that can be reviewed, tested, and audited independently of application code. That separation makes it easier to introduce new features without turning permission logic into a maintenance burden.
The same approach applies to scenarios beyond those covered here. Regional content controls, tournament administration, gifting systems, fraud investigation workflows, and partner access programs all depend on clear rules about who can do what and under which conditions. Treating those rules as policies rather than code helps teams adapt without repeatedly touching core application logic.
If you'd like to experiment with these examples yourself, the Cerbos Playground includes all three scenarios discussed in this guide. You can modify the policies, test different access requests, and see how authorization decisions change as player, resource, and context attributes vary.
If you are ready to move beyond the playground, try out Cerbos for free, or book a call with a Cerbos engineer to see how externalized authorization fits your use case.
To explore mapping business requirements to authorization policy for other domains, check out the foundational guide, or dive into these blogs specific to the fintech, insurance, utilities, aviation, e-commerce, automotive, MedTech and HR fields.
FAQ
Tagged in




