Skip to content

Non-human access is where vague identity language becomes security debt.

“Machine-to-machine” can mean a backend service calling an internal API on its own behalf. It can mean a worker continuing a job that a user started. It can mean an automation agent discovering tools at runtime. Those are not the same actor, and they should not receive the same token shape just because no browser is visible.

Client credentials are still useful. They are just not a complete design for service, worker, and agent access. The harder question is not “Can this process get a token?” It is “Who is this process acting as, which resource is the token meant for, and what should the next API be able to verify?”

Start by naming the actor

RFC 6749 defines the client credentials grant for cases where the client is acting on its own behalf or requesting access based on an authorization already arranged with the authorization server. That maps well to many backend jobs: a billing exporter, a scheduled synchronization, a deployment pipeline, or an internal service that owns the operation it performs.

It maps less cleanly when the caller is carrying user context across a chain.

Consider an application where a user initiates an order, a worker continues processing in the background, the Orders API calls Payments, and Payments calls Risk. If every hop receives the original broad token, each downstream service has to infer too much. Was this token meant for Payments? Does it carry the right user context? Which service is now acting? Should Risk accept a token issued for Orders?

The design conversation should split the cases early:

  • A service acting for itself can use client credentials with a narrow audience and scope.
  • A service acting on behalf of a user should avoid forwarding a broad upstream token through the chain.
  • A dynamic ecosystem may need metadata, federation, policy, or discovery in addition to ordinary OAuth client registration.

The word “machine” hides those differences. The token should not.

sequenceDiagram
    autonumber
    participant Worker as Worker
    participant Orders as Orders API
    participant ProAuth as ProAuth
    participant Payments as Payments API
    participant Risk as Risk API

    Worker->>Orders: Call with Orders token
    Orders->>ProAuth: Exchange for Payments token
    ProAuth-->>Orders: Audience is Payments
    Orders->>Payments: Call with Payments token
    Payments->>ProAuth: Exchange for Risk token
    ProAuth-->>Payments: Audience is Risk
    Payments->>Risk: Call with Risk token

Client identity is part of the authorization decision

For non-human callers, client authentication is not just a way to protect the token endpoint. It is part of the policy decision. The authorization server needs to know which client is requesting the token, whether that client is allowed to act in that context, and which credentials prove that identity.

RFC 9700 recommends asymmetric cryptography for client authentication where feasible, including mutual TLS and signed JWT-based client authentication. OpenID Connect Core defines private_key_jwt as a client authentication method. RFC 8705 defines mutual-TLS client authentication and certificate-bound access tokens for OAuth.

That guidance matters operationally. Shared secrets are easy to copy into build variables, incident notes, local scripts, and old deployment slots. Asymmetric credentials give teams better rotation patterns because the authorization server can know the public key while the client keeps the private key. They also make it easier to reason about which workload can authenticate, which key is current, and which credential must be removed when a deployment path changes.

The useful review questions are practical:

  • Does each non-human client have its own client identity?
  • Is the authentication method appropriate for how the workload is deployed?
  • Can keys or certificates be rotated without creating long overlap windows?
  • Are old credentials removed from inactive environments?
  • Does the authorization server enforce which client may request which audience and scope?

Client credentials without client lifecycle discipline become another long-lived password. Strong client identity turns the grant into something that can be reviewed and operated.

Token exchange narrows the next hop

Forwarding the original token through an API chain is convenient, but it is rarely the cleanest security boundary. The first API received a token for itself. The next API should receive a token intended for the next API.

RFC 8693 defines OAuth 2.0 Token Exchange. In a token exchange request, a client presents a subject_token and asks the authorization server for another security token. The request can include resource, audience, and scope values so the issued token is suitable for a different target service or a narrower operation. RFC 8693 also defines actor_token for cases where the caller needs to represent the acting party separately from the subject.

That distinction is useful in enterprise systems because service chains often combine two facts:

  • the user or workload on whose behalf the action is being performed
  • the service that is currently acting in the chain

Token exchange gives the authorization server a policy checkpoint between hops. Orders does not need to send Payments the same token it received from the worker. Orders can request a Payments token with the audience and scope Payments expects. Payments can validate a token meant for Payments instead of accepting a generic credential that happened to arrive over an internal network.

Token exchange is not magic delegation. RFC 8693 leaves many policy choices to the deployment, including which tokens are accepted, which exchanges are allowed, and how delegation or impersonation semantics are represented. That is the point: the exchange creates a place to make those decisions explicitly.

Resource context belongs in the token request

Scopes describe what access is requested. They do not always describe where the token will be used.

RFC 8707 defines resource indicators for OAuth 2.0 so a client can signal the protected resource it wants to access. The resource parameter identifies the target service or API, and RFC 8707 says authorization servers should audience-restrict issued access tokens to the indicated resource.

That turns a fuzzy token request into a concrete one:

  • “Give this worker read” is vague.
  • “Give this worker orders.read for the Orders API” is better.
  • “Exchange this Orders token for payments.authorize at the Payments API” is better still when the call crosses a service boundary.

The resource server has work to do as well. RFC 9700 says resource servers are obliged to verify whether an access token was meant to be used for the requested resource and action. In practice, that means APIs should validate issuer, expiration, audience, relevant scopes or authorization details, and any deployment-specific constraints before serving the request.

An API that accepts any token from the right issuer has only done part of the job. It still needs to ask whether the token was meant for this API.

Review non-human access as a chain

A single client registration rarely tells the whole story. Review non-human access by tracing the path from the original trigger to the final resource.

For each chain, ask:

  • What started the action: a user request, a schedule, an event, a deployment, or another service?
  • Is the caller acting for itself, for a user, or as a delegated actor in a chain?
  • Which token audience should each API receive?
  • Where should the chain exchange a token instead of forwarding the original one?
  • Which scopes or authorization details does each hop need?
  • Which service validates the aud value and rejects wrong-audience tokens?
  • Which client identities are allowed to request downstream tokens?
  • How are workload keys, certificates, or assertions rotated and revoked?
  • Are logs and traces able to show both the subject and the acting service where the deployment represents both?

These questions keep teams out of the false binary where everything is either “user token” or “service token.” Real systems have more texture than that.

Keep ProAuth behind the standards boundary

In ProAuth-based architectures, the standards model is still the right starting point: define client applications deliberately, keep resource and scope decisions explicit, and make APIs validate the tokens they receive. ProAuth can sit at the identity boundary while application and platform teams decide which callers act for themselves, which calls carry delegated context, and where downstream token boundaries belong.

For product-side configuration concepts, see the ProAuth documentation for Client Applications and the OIDC client documentation. For the standards discussed here, start with RFC 8693 for token exchange and RFC 8707 for resource indicators.

The design habit is simple: do not approve a non-human caller until you can name the actor, the target resource, the allowed action, and the next validation point. Client credentials may be one part of that answer. They should not be the whole answer.