# `Arc.Identity.OnBehalfOf`
[🔗](https://github.com/Cratis/Arc.Elixir/blob/main/lib/arc/identity/on_behalf_of.ex#L4)

Acting on behalf of a user: verifies the RS256 assertion Cratis AuthProxy signs for an on-behalf
session and attaches the actor to the represented user's principal.

Inside such a request two identities matter. The **represented user** is the request principal,
and decides what the request may do: roles, tenant and authorization are theirs. The **actor**
is who actually acts, and is available as `principal.actor` and recorded by Chronicle as the
cause of every event, on behalf of the represented user.

The ingress forwards the usual `x-ms-client-principal*` headers plus `x-cratis-on-behalf-actor`
(the actor's base64 client principal) and `x-cratis-on-behalf-assertion` (a compact RS256 JWS).
Enable it with the `on_behalf_of` option, which also needs `trust_forwarded_identity_headers`:

    config :my_app, MyApp.Arc,
      trust_forwarded_identity_headers: true,
      on_behalf_of: [
        enabled: true,
        issuer: "https://authproxy.example.com",
        audience: "https://app.example.com",
        service: "portal",
        clock_skew: 30,
        signing_keys: [[key_id: "authproxy-2026", public_key_pem: "-----BEGIN PUBLIC KEY-----\n..."]]
      ]

`service` is optional: set it when the ingress fronts several services with the same keys,
so an assertion issued for another service is refused. `clock_skew` is in seconds. Signing keys
must be RSA public keys of at least 2048 bits in PEM form; a private key is refused so the
signing secret never reaches configuration.

Arc accepts a forward only when the assertion passes every check: RS256 with a configured `kid`
and no `crit` header; the issuer; the audience; `nbf` and `exp` present, now within them allowing
for the skew, and a lifetime of at most five minutes; the purpose `on-behalf-forward`; the service,
when configured; `jti` present and not used before; the represented user's principal hash,
subject and id header; the actor hash and subject; the tenant, when the request carries the
tenant header; and `htm` and `htu`, when present. The actor must be a valid client principal
whose `userId` differs from the represented user's.

The replay check is in memory and per application process. With several nodes a captured
assertion can be replayed once to each other node within its lifetime. The ingress must strip
every inbound `x-cratis-on-behalf-*` and `x-ms-client-principal*` header.

# `actor_header`

# `admit`

```elixir
@spec admit(keyword()) :: String.t() | nil
```

Whether on-behalf forwarding may be used at all: the host trusts forwarded identity headers and
the feature is enabled. Returns `nil` or the failure.

# `assertion_header`

# `attach`

```elixir
@spec attach(
  map(),
  Arc.Principal.t(),
  module(),
  keyword(),
  {:ok, map()} | {:error, String.t()},
  integer()
) :: {:ok, Arc.Principal.t(), integer()} | {:error, String.t()}
```

Verifies an on-behalf forward and returns the actor.

`forward` is `%{principal:, principal_id:, actor:, assertion:, tenant:, method:, path:}` with the
raw header values. `represented` is the represented user's principal. Returns `{:ok, actor,
valid_until}` (unix seconds) or `{:error, reason}`.

# `present?`

```elixir
@spec present?((String.t() -&gt; boolean())) :: boolean()
```

Whether the request carries either on-behalf header.

# `signing_keys`

```elixir
@spec signing_keys(keyword()) :: {:ok, map()} | {:error, String.t()}
```

Reads and validates the configured signing keys. `{:ok, %{key_id => rsa_public_key}}` or `{:error, problem}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
