AccelByte Blog: Insights on Game Development & Backend

Server-Authoritative Game Logic to Prevent Cheating in Multiplayer Games

Written by AccelByte Inc | Apr 21, 2026 7:09:21 PM

In a lot of multiplayer games, the client decides whether a shot hit and how much damage it did, then just tells the server what happened. That means the client is effectively defining reality, and the server is just logging it with no real validation, which makes it easy to cheat.

To actually secure your game, you need to flip that trust model and go server-authoritative. The client only sends intent (inputs, fire requests, etc.), and the server is the one that decides what really happens and updates everyone else. This is different from kernel-level anti-cheat like Easy Anti-Cheat or BattlEye, those watch the player’s machine, while server authority means you do not trust any client reports. Most competitive games need both. This article covers the second one:

  • What logic needs to move to the server and why
  • How lag compensation works without opening new exploits
  • Where custom backend services fit in a live environment

To be clear, this is not a production implementation guide, the code examples in this article are just to illustrate the pattern and the decision point, not to be dropped into a shipped game as-is.

What Runs on the Server vs. the Client

The rule is simple: if players can gain advantage by lying, it should run on the server.

Client-side prediction optimizes responsiveness in high-latency multiplayer games (100ms+ ping) by instantly showing player movement based on local input. The client predicts the outcome for rendering, but the server remains the authority. When the server's authoritative update arrives, it reconciles and overwrites the client's predicted position to correct discrepancies.

Server authority stops cheats that work by lying to the server about game state. It does not stop:

  • Aimbots that bypass validation by sending legitimate, inhumanly precise aim inputs. The server processes these as valid hits. Detecting aimbots requires behavioral analysis (e.g., snap-to-target timing, inhuman reaction times), a separate and distinct problem.
  • Data-only Wallhacks. Undetectable by validation layers, these cheats show enemy positions through walls without sending false data. To mitigate this, servers must use network relevancy/interest management, only informing clients about entities they could plausibly observe.
  • Automation. Scripts sending valid inputs at superhuman speed look identical to valid inputs at human speed from the server's perspective. The server cannot distinguish between a very fast human and a bot without input pattern analysis. That analysis is a separate problem.
  • Latency abuse. Intentionally spiking ping, disconnecting on death to deny a kill -- these manipulate the timing of valid state rather than fabricating invalid state.

Movement Validation in Unreal Engine

Movement is where most exploits start. Speed hacks, teleportation, no-clip -- these all work by sending position updates the server never questioned.

Unreal's UCharacterMovementComponent handles server-authoritative movement for standard locomotion out of the box. The server re-simulates each move from the client's inputs and sends corrections when positions diverge. What it does not cover is custom movement: wall-running, grapple hooks, dashes, any ability you write yourself. For those, the validation is your responsibility.

Send inputs, not outcomes

The dangerous version of a move RPC looks like this:

C/C++
// Never do this -- client sends where it moved to
void AMyCharacter::ClientMove_Implementation(FVector NewLocation)
{
    SetActorLocation(NewLocation); // a modified client can send any position it wants
}

Accepting a position from the client means any modified client can teleport anywhere by sending whatever vector it wants. The correct version sends what the player did and lets the server compute where that puts them:

C/C++
// Vulnerable: client reports its destination
UFUNCTION(Server, Reliable, WithValidation)
void ServerSetPosition(FVector NewPosition);

// Correct: client reports its input, server computes the result
UFUNCTION(Server, Reliable, WithValidation)
void ServerMoveForward(float AxisValue);

Always implement WithValidation

Every function marked Server must also carry WithValidation. This generates a _Validate function that runs before the implementation. Return false and the RPC is rejected, the client can be disconnected entirely.

C/C++
bool AMyCharacter::ServerMoveForward_Validate(float AxisValue)
{
    // Axis input should never exceed 1.0 -- anything beyond is invalid
    return FMath::Abs(AxisValue) <= 1.0f;
}

void AMyCharacter::ServerMoveForward_Implementation(float AxisValue)
{
    FVector MoveDelta = GetActorForwardVector() * AxisValue * MoveSpeed * GetWorld()->GetDeltaSeconds();
    FVector NewPosition = GetActorLocation() + MoveDelta;

    if (IsValidMovement(GetActorLocation(), NewPosition))
    {
        SetActorLocation(NewPosition);
    }
}

bool AMyCharacter::IsValidMovement(FVector From, FVector To)
{
    // 1.2x tolerance accounts for network jitter and frame timing variance
    // Tighten or loosen this based on your game type and real latency data
    float MaxDistance = MoveSpeed * GetWorld()->GetDeltaSeconds() * 1.2f;
    return FVector::Distance(From, To) <= MaxDistance;
}

The 1.2x tolerance factor is load-bearing. Set it too tight and you start disconnecting legitimate players on variable-latency connections. You can also configure how aggressively the engine sends corrections in DefaultGame.ini.

Custom movement abilities

Standard CharacterMovementComponent handles its own save-and-replay cycle during reconciliation. Custom movement abilities do not get that for free. If your game has wall-running, grapples, or dashes, you need to extend FSavedMove_Character so the server can replay those inputs:

C/C++
class FSavedMove_MyGame : public FSavedMove_Character
{
public:
    bool bWantsToGrapple;

    virtual void Clear() override
    {
        Super::Clear();
        bWantsToGrapple = false;
    }

    virtual void SetMoveFor(ACharacter* Character, float InDeltaTime,
        FVector const& NewAccel, FNetworkPredictionData_Client_Character& ClientData) override
    {
        Super::SetMoveFor(Character, InDeltaTime, NewAccel, ClientData);
        AMyCharacter* MyCharacter = Cast<AMyCharacter>(Character);
        if (MyCharacter) { bWantsToGrapple = MyCharacter->bWantsToGrapple; }
    }

    virtual void PrepMoveFor(ACharacter* Character) override
    {
        Super::PrepMoveFor(Character);
        AMyCharacter* MyCharacter = Cast<AMyCharacter>(Character);
        if (MyCharacter) { MyCharacter->bWantsToGrapple = bWantsToGrapple; }
    }
};

Without this, the custom ability runs on the client during prediction but never replays on the server. The server has no basis for validation.

Hit Registration and Lag Compensation in Multiplayer

Movement validation is important, but getting hit registration right is a challenge and where a lot of solutions go wrong. The root problem is latency. Imagine a player with 80ms ping; they're shooting at where the target was 80ms ago on the server. If the server just checks the shot now, it'll feel like a miss, making the game feel broken.

The standard fix? Server-Side Rewind. The server holds a short history of game world, rewinds the entities' positions back to when the player fired, and then checks for hit.

Server keeps: ring buffer of world snapshots, last ~500ms
Client fires at tick 4821 (their view of the world)
Server receives the event at tick 4826 (5 ticks later, latency)
Server rewinds target positions to tick 4821
Server performs raycast against rewound state
Server applies damage if hit -- writing to current-tick state, not the rewound state

Building this requires two pieces: a position history buffer on every validated entity, and a hit validation function that rewinds to the client's timestamp before tracing.

When a shot arrives, the server needs to validate it against the world state the shooter was actually seeing, not the current state. The process is:

  1. Take the timestamp the client reported and clamp it to your rewind window. This is the most important step because without it, a cheat client can claim a shot against any past world state.
  2. Look up where the target was at that rewound timestamp using your position history buffer.
  3. Temporarily move the target to that rewound position and run a line trace from the shot origin along the shot direction.
  4. Restore the target's position before applying anything.
  5. Only apply damage if the trace confirmed a hit.

The specifics like which trace channel to use, how to handle multi-hit or AOE scenarios, how to manage concurrent state safely if other server-side queries are running the same frame, are implementation details that depend heavily on your game. The pattern above describes the decision logic. How you wire it into your game server is a separate engineering problem.

Three things that go wrong most often in lag compensation implementations:

  • Unlimited rewind windows are risky.
    Right now, FMath::Clamp is the only thing stopping cheaters from using super-old timestamps to exploit past locations. You should limit the rewind window based on the target's latency. Think 200ms for solid connections, maybe up to 350ms for a wider net. Anything over 400ms is going to cause trouble.
  • Rewinding only players is a problem.
    If you only track position history for player pawns but not for things like destructible cover or props, you create mismatches that cheaters can exploit like shooting through a wall that was just destroyed on their screen.
  • Don't trust client timestamps alone; verify the tick.
    The server can't just accept the timestamp the client sends. It has to confirm that the reported game tick actually falls within the current acceptable rewind window. Otherwise, cheats can reference ticks from before they even joined.

State Authority: Health, Inventory, and Economy

Movement and hit validation cover the physics of the match. State authority covers everything the physics produces: health, ammo, items, currency, progression.

The server owns all game state. The client owns nothing.

Replicated properties synchronize server state down to clients automatically. The client's job is to read them and update the UI. Writing to them from the client is a design error and a cheat vector.

C/C++
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health;

void AMyCharacter::TakeDamage_Server_Implementation(float DamageAmount,
    AMyCharacter* Instigator)
{
    if (!HasAuthority()) return;

    Health = FMath::Max(0.0f, Health - DamageAmount);
    if (Health <= 0.0f) { HandleDeath(Instigator); }
}

void AMyCharacter::OnRep_Health()
{
    // Client reads the server's value and updates the display
    // It does not compute this value itself
    UpdateHealthUI(Health);
}

Ammo follows the same pattern the distinction is worth seeing explicitly because client-authoritative ammo is common in early prototypes and painful to fix later:

C/C++
// Wrong: client controls whether it can fire
void AMyWeapon::FireWeapon()
{
    if (CurrentAmmo > 0)
    {
        CurrentAmmo--;
        SpawnProjectile(); // client decides this is valid
    }
}

// Right: client asks the server, server decides
void AMyWeapon::ServerFireWeapon_Implementation()
{
    if (!HasAuthority()) return;

    if (CurrentAmmo > 0)
    {
        CurrentAmmo--;
        MulticastSpawnProjectile(); // server broadcasts the result to all clients
    }
    // If CurrentAmmo is 0, the server does nothing
    // The client's optimistic prediction gets corrected on the next replication tick
}

The backend layer most studios miss

Most discussions of server authority stop at the dedicated game server like position checks, damage calculations, hit registration. That only covers the in-match logic.

A significant portion of cheat-prone logic runs after the match ends: reward claims, loot box rolls, inventory credits, XP calculations. If any of these trust what the client reports, the attack surface is identical to client-authoritative movement. A client claiming a 50-kill streak is not more trustworthy than a client claiming it moved 500 meters per second. Both layers need server authority.

Your options for session-end validation

Server-authoritative logic splits across two environments for a reason. Movement validation and real-time combat have to run inside your game server they need tick-rate access to the live world state. The logic that doesn't need to live in the tick loop -- damage formulas, loot generation, economy transactions, anti-fraud checks -- is a different problem. 

Studios that choose to run these services themselves have to own the full stack that comes with it: provisioning, scaling, securing, and monitoring a fleet of backend services on top of shipping the game itself. For most teams, that's a significant operations burden on top of actually shipping the game. Here are the paths studios can take for that second layer:

  • Build it yourself

    Full control over every decision, but you own the deployment pipeline, uptime monitoring, scaling, and on-call rotation. The engineering cost is real and ongoing, not just at build time.

  • Integrate with an existing backend provider:

    If you are already using a backend provider like Epic Online Services for identity and sessions, you can extend that with custom validation services that call into your existing infrastructure. The advantage is familiarity; the tradeoff is that most provider-native solutions are not designed for the kind of custom authoritative logic this article describes.

    Studios already using EOS can use AccelByte Extend alongside EOS without replacing anything to cover what EOS does not, including the server hosting and custom backend logic layers. If that's your setup, this page walks through exactly how AGS and EOS work together.

  • Use a managed backend platform:

    Hand off the infrastructure entirely and focus on writing the logic itself. The tradeoff is less control over underlying systems with most providers.

  • Some combination:

    Studios also use a platform for core services -- matchmaking, identity, economy -- and run custom validation logic on top of it. This is often the most practical path for teams that want to ship without a dedicated backend team.

Every path above involves a tradeoff between control and cost or both.

AccelByte is the only option in the market that provides a fully managed backend that covers backend services, dedicated server hosting and orchestration and the ability to customize the logic however you want. It handles the full stack so studios can focus on writing game logic rather than operating the infrastructure that runs it:

  • AccelByte Gaming Services (AGS) is the core backend layer that provides services for identity, matchmaking, session management, economy, leaderboards, cloud save, and more, available as modular services that work together out of the box.
  • AccelByte Extend is the layer where custom logic lives. Studios can write their own validation rules like damage formulas, reward conditions, and anti-fraud checks in Go, C#, Java, or Python, we host and operate it without any overhead.
  • AccelByte Multiplayer Servers (AMS) handles dedicated server orchestration including fleet management, regional deployment, and scaling, without you handling anything manually.

Whether it is backend services or custom logic or server hosting, there is no separate infrastructure to stand up or monitoring stack to build with AccelByte meaning developers get the most control at the least cost of ownership and maintenance.

How AccelByte Extend handles server authoritative logic

Extend works through three mechanisms: Service Extensions, Override apps and Event Handlers.

  • An Override app replaces a specific AGS behavior with your own logic. This loot box roll is the clearest example -- deploy an Override app to add eligibility checks, pity systems, or an audit trail before the result reaches the client.

  • A Service Extension allows you to expand the platform’s capabilities by building entirely new, standalone APIs that live within the AccelByte ecosystem. Unlike an Override, which replaces existing logic, a Service Extension adds functionality that didn't exist before.

  • An Event Handler fires in response to AGS events -- a match session closing, a login, a season pass expiring. For match reward validation, an event handler on session-complete receives the session data, validates it against your rules, and calls the AGS economy service to grant rewards. The client is not in this path.

To reduce the friction in building Extend apps such knowing which SDK functions to call, how to shape requests correctly, and how AGS services connect to each other, we did two things:

A public collection of ready-to-fork Extend apps covering the patterns that come up most often across game studios like custom matchmaking functions, MMR calculators, rotating shop handlers, reward event processors, guild progression services, and third-party integrations. For this article's specific context: there is also a published Extend app that connects Easy Anti-Cheat signals to your backend ban service, so detection events from EAC flow into your enforcement logic without you wiring that integration from scratch.

  • Two MCP servers to use with AI assistants:

    Most AI coding assistants write generic backend code that call the wrong functions or miss required fields because they are not working from live knowledge of your specific SDK.

    AccelByte's two MCP servers fix that: the AGS API MCP server exposes the full AGS service and endpoint structure, and the AGS Extend SDK MCP server covers SDK patterns and correct function signatures for the version you are targeting. You can connect to Claude Code, VS Code Copilot, or your preferred command-line setup and you can describe what you need and get back a scaffolded Extend app that follows the right layout, calls the right services, and shapes requests correctly. The full walkthrough with a working example is in the AccelByte blog post on AI and MCPs for game backend development.

    Unreal developers can use the same MCP server knowledge inside the Unreal Editor through Aura, which uses it to generate integrations that match Unreal's project structure.

Get Started for Free

If you're setting up server infrastructure or implementing server authoritative logic to support this architecture, you can get started with AccelByte for free without any upfront commitment. That includes access to our backend services, AMS for dedicated server hosting, and Extend for custom backend logic.