Skip to main content
Wildo.ai Coming soon

Machine and agent access

Let services act under their own identity

Automated work can belong to a service rather than impersonating a person. Wildo gives registered clients their own scoped roles and tokens, so the application can authorize and attribute machine actions explicitly.

A service obtains a token to act within Acme, with people shown separately beneath the workspace.

Let services act under their own identity

Automated work can belong to a service rather than impersonating a person. Wildo gives registered clients their own scoped roles and tokens, so the application can authorize and attribute machine actions explicitly.

The client requests a token for the resource it will call, keeping the credential’s intended destination part of the contract.

Example — A scheduled service updates account records

An organization-owned client receives the roles needed by the scheduled service. Its token identifies that client and account; it does not pretend a human performed the update.

For engineers

Create the organization or application OAuth client through its resource operation. Set its allowed grants and roles, retain the one-time plainSecret, then use the token endpoint. A client using client_credentials acts as itself.

The token issuer carries the resolved scope and principal into the signed claim:

This implementation excerpt from machine-token-issuer.backend.service.ts shows the decision in context; explanatory source comments are omitted.

public async issueClientAccessToken(request: MachineAccessTokenRequest): Promise<MachineAccessTokenResponse> {
    const maxMinutes = this.appConfigService.config.jwt.accessTokenExpirationMinutes;
    const requestedMinutes = request.expiresInMinutes ?? maxMinutes;
    const minutes = Math.max(1, Math.min(requestedMinutes, maxMinutes));

    const claim: Jwt_MachineToken_CreationParameter = {
      type: request.scopeType === ResourcePrimaryScope.ORGANIZATIONS
        ? ExecutionContext_ExecutionType.ORGANIZATION_MACHINE
        : ExecutionContext_ExecutionType.APPLICATION_MACHINE,
      clientId: request.clientId,
      scopeId: request.scopeId,
      roles: request.roles,
    };

    const accessToken = await this.jwtService.createJwtForMachineToken(claim, {
      audience: request.audience,
      expiresInMinutes: minutes,
    });

    this.logger.debug('Issued machine client-credentials access token', {
      clientId: request.clientId,
      scopeType: request.scopeType,
      scopeId: request.scopeId,
      audience: request.audience,
      expiresInMinutes: minutes,
    });

    return { accessToken, tokenType: 'Bearer', expiresIn: minutes * 60 };
  }

Request the intended resource

The token request names grant_type=client_credentials, the client credential and the resource audience. Present the returned access_token as a Bearer credential to that resource. The token endpoint checks that this client allows the grant and that the requested resource is known.

The public token endpoint uses the application’s configured access-token lifetime. It does not accept a requested lifetime. The expiresInMinutes option shown above belongs to the internal issuer API; its callers may shorten the policy maximum.

The following request follows external-access-auth.e2e.ts. Set BACKEND_URL to the application backend, CLIENT_SECRET to the one-time secret and RESOURCE_AUDIENCE to a resource identifier advertised by that deployment.

curl "$BACKEND_URL/oauth/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode "client_secret=$CLIENT_SECRET" \
  --data-urlencode "resource=$RESOURCE_AUDIENCE"

The response supplies access_token, token_type and expires_in. Save access_token as ACCESS_TOKEN; set RESOURCE_URL to an endpoint in that audience for which the client has permission:

curl "$RESOURCE_URL" -H "Authorization: Bearer $ACCESS_TOKEN"

An audience match is necessary but does not grant an operation role. A valid organization client still cannot use its token to operate on another organization’s records.

Separate the client secret from tokens already issued

The client secret is used to obtain tokens. A Bearer token is a signed snapshot of the client’s roles and scope with its own expiry. Changing the first does not rewrite the second.

ChangeNew token requestsMachine Bearer tokens already issued
Rotate the secretNew secret works; previous secret works until oldSecretInvalidationDate, defaulting to immediate cutoverRetain their signed claims and expiry
Regenerate the secretNew secret is returned; the previous secret is retained with open-ended overlapRetain their signed claims and expiry
Client is no longer active or has expiredRefused when the client is read during exchangeClient status is not re-read by the machine Bearer authentication paths
Change the client’s rolesSubsequent tokens receive the current rolesExisting tokens retain the roles signed into them

There is no dedicated OAuth-client deactivate/reactivate operation pair in this resource contract. The status row above describes an admission condition, not an extra management endpoint.

Rotation and regeneration update the same client record; the returned plainSecret is the one-time value to install in the integration. Regeneration is therefore not an immediate retirement of the previous secret. Choose rotation with a deliberate cutoff when the outgoing secret must stop obtaining tokens.

The ordinary API and supported external machine-token paths verify the signature, issuer, audience and token expiry, then construct machine authority from the claims. They do not look up the client again in those Bearer branches. Operation authorization and scope checks still apply; this is not a promise that every request succeeds until expiry.

Direct authentication with the OAuth client secret is different: that path reads the client and checks its status and expiry for the request. User-delegated agent tokens also follow a different validation path. Do not extend this machine-Bearer behavior to every kind of credential.

Make the cutover observable

Use the application’s configured access-token lifetime and the token response’s expires_in when planning the overlap. The public token endpoint does not accept a shorter lifetime requested by the integrating service.

For a controlled rotation, keep a pre-rotation token and test these separate outcomes in a disposable integration:

  1. Obtain a new token with the replacement secret and call an operation the client is allowed to use.
  2. After the chosen cutoff, confirm the old secret can no longer obtain a token.
  3. Check the pre-rotation Bearer separately: the secret cutoff does not itself revoke that token. Token expiry and the resource’s other authorization checks remain its boundaries.
  4. Confirm the same token is refused after expiry. Keep an authorized fresh-token call as a control so a missing route or stopped service is not mistaken for revocation.

These checks distinguish a successful secret replacement from withdrawal of already issued access. If the product requires immediate client-wide invalidation of machine Bearers, the current authentication paths do not supply that guarantee.

Design business code for a machine caller

Machine identity is recorded independently from user identity. A machine-created record may have no creator user ID, so custom business logic should use the execution context’s principal rather than assuming every authorized action has a human userId.

User delegation is a separate flow: agent tokens represent a consenting person, while this client represents the service itself.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.