# Function access control

> Control who can invoke individual functions using an access parameter.

Within the context of [Stateful Cloud Code](/cloud-code/3.0.0/modules/stateful-cloud-code/state-scopes.md), you control who can invoke each Cloud Code function by setting the `Access` parameter on the `[CloudCodeFunction]` attribute. This parameter provides function-level authorization that restricts callers based on their relationship to the scope, for example, session membership, session host status, player ownership, or service account identity.

> **Note:**
>
> Function access control is separate from [project-level access control policies](/cloud-code/3.0.0/modules/how-to-guides/access-control.md), which restrict access to entire service APIs. The `Access` parameter controls authorization at the individual function level within your module.

## Access levels

The `Access` enum defines five access levels plus a default (`Unspecified`). Each level is either valid in any scope, or restricted to a single scope. The following table describes each value and the scope it requires:

| Level           | Required scope     | Description                                                                                                 |
| --------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- |
| `Global`        | Any                | Any caller can invoke the function, regardless of session membership or ownership.                          |
| `Service`       | Any                | Only service account callers can invoke the function.                                                       |
| `Player`        | Player             | Only the owning player, whose player ID matches the scope ID, and service accounts can invoke the function. |
| `SessionMember` | MultiplayerSession | Any member of the session, including the host, and service accounts can invoke the function.                |
| `SessionHost`   | MultiplayerSession | Only the host of the session and service accounts can invoke the function.                                  |
| `Unspecified`   | Any                | The attribute default. Cloud Code resolves the effective access from the class scope when the module loads. |

> **Important:**
>
> Cloud Code validates every function against this table when the module loads, and rejects the whole module if any function declares an incompatible combination. If you don't specify an access attribute, the module uses `Unspecified` access, for which scope defaults apply. For more information, refer to [Default access](#default-access).

> **Note:**
>
> Although there's some hierarchy between the access levels, the values aren't ordinal. Don't compare them numerically, for example with `granted >= required`, because the levels don't form a single ordered chain.

## Set an access level on a function

The following code sample passes an `Access` value as the second argument to `[CloudCodeFunction]`:

```csharp title="Setting access on a function"
using Unity.Services.CloudCode.Core;

[StateScope(Scope.MultiplayerSession)]
public class GameLobby
{
    [CloudCodeFunction("StartGame", Access.SessionHost)]
    public string StartGame(IExecutionContext ctx)
    {
        return $"Game started by host {ctx.PlayerId}";
    }

    [CloudCodeFunction("GetGameState", Access.SessionMember)]
    public string GetGameState(IExecutionContext ctx)
    {
        return $"State requested by {ctx.PlayerId}";
    }

    [CloudCodeFunction("ResetSeason", Access.Service)]
    public string ResetSeason(IExecutionContext ctx)
    {
        return "Season reset by service";
    }

    [CloudCodeFunction("Ping", Access.Global)]
    public string Ping() => "pong";
}
```

You can also omit the function name and use the method name as the endpoint. In this case, pass `Access` as the only argument:

```csharp title="Using the method name with an access level"
[CloudCodeFunction(Access.SessionMember)]
public string GetScore(IExecutionContext ctx)
{
    return "100";
}
```

## Default access

If you don't specify an access level, the attribute defaults to `Access.Unspecified`. Cloud Code then resolves the effective access from the scope of the function's class when the module loads:

| Class scope                              | Effective access |
| ---------------------------------------- | ---------------- |
| No `[StateScope]` attribute              | `Global`         |
| `[StateScope(Scope.Player)]`             | `Player`         |
| `[StateScope(Scope.MultiplayerSession)]` | `SessionMember`  |

Scoped functions are restricted by default. In the following code sample, `UpdateProfile` doesn't declare an access level, so it resolves to `Access.Player`, and only the owning player and service accounts can invoke it:

```csharp title="Default access on a Player-scoped class"
[StateScope(Scope.Player)]
public class PlayerProfile
{
    [CloudCodeFunction("UpdateProfile")]
    public string UpdateProfile(IExecutionContext ctx, string displayName)
    {
        return $"Profile updated for {ctx.PlayerId}";
    }
}
```

To make a function on a scoped class callable by anyone, declare `Access.Global` explicitly.

## Behavior by scope type

Cloud Code enforces an access level based on the scope of the function's class.

### Session-scoped functions

For `MultiplayerSession`-scoped classes, Cloud Code checks the caller's relationship to the multiplayer session:

* `SessionHost`: The caller's player ID must match the session's host ID.
* `SessionMember`: The caller's player ID must be in the session's member list.

```csharp title="Session-scoped access control"
[StateScope(Scope.MultiplayerSession)]
public class SessionGame
{
    [CloudCodeFunction("KickPlayer", Access.SessionHost)]
    public string KickPlayer(IExecutionContext ctx, string targetPlayerId)
    {
        return $"Player {targetPlayerId} kicked by host {ctx.PlayerId}";
    }

    [CloudCodeFunction("SubmitMove", Access.SessionMember)]
    public string SubmitMove(IExecutionContext ctx, string move)
    {
        return $"Move '{move}' submitted by {ctx.PlayerId}";
    }
}
```

### Player-scoped functions

For `Player`-scoped classes, use `Access.Player` to restrict a function to the owning player. Cloud Code compares the caller's player ID with the scope ID, and denies any other player.

```csharp title="Player-scoped access control"
[StateScope(Scope.Player)]
public class PlayerInventory
{
    [CloudCodeFunction("GrantItem", Access.Player)]
    public string GrantItem(IExecutionContext ctx, string itemId)
    {
        return $"Item {itemId} granted to {ctx.PlayerId}";
    }
}
```

### Non-scoped functions

For classes without a `[StateScope]` attribute, only `Global` and `Service` are valid, because there's no scope to check the caller against.

```csharp title="Non-scoped access control"
public class AdminTools
{
    [CloudCodeFunction("MigratePlayers", Access.Service)]
    public string MigratePlayers(IExecutionContext ctx)
    {
        return "Migration started";
    }

    [CloudCodeFunction("GetServerTime")]
    public DateTime GetServerTime() => DateTime.UtcNow;
}
```

## Error handling

When a caller doesn't have the required access level, Cloud Code returns a `403 Forbidden` response with error code `9034`. The following code sample shows how to handle this error on the client side:

```csharp title="Handling access denied errors"
try
{
    var result = await CloudCodeService.Instance.CallModuleEndpointAsync<string>(
        "GameLobby",
        "StartGame",
        scope: new CloudCodeScope(ScopeType.MultiplayerSession, sessionId));
}
catch (CloudCodeException ex) when (ex.ErrorCode == 9034)
{
    Debug.Log("Access denied: you are not the session host.");
}
```
