thygon

Identity & Access Control

Let users and organizations authenticate, manage identities, and enforce permissions.

API & Machine Access

Not every caller is a person at a browser. External developers call the product's API, customers connect their own systems, internal services call each other, and AI agents act on behalf of users. Each needs its own identity, credentials that can be scoped and revoked, and permissions no broader than the task requires.

Actors and Credentials

ActorCredentialActs as
Script or quick integrationPersonal API keyThe user who created it, limited by scopes
Long-running customer integrationService account keyA non-human member of the organization
Third-party or marketplace appOAuth applicationA user who authorized it, limited by consented scopes
Internal serviceWorkload identity: mTLS certificate, client credentials, or cloud-issued tokenThe service itself
AI agentRegistered agent identity plus a delegated tokenThe agent, on behalf of a user

Architecture

Rendering diagram…

Diagram source
flowchart LR
  devs([Developers]) --> portal[Developer portal]
  portal --> registry[(Applications, keys, agents, scopes)]
  clients[API clients and agents] --> gateway[API gateway]
  gateway -->|verify key or token| registry
  gateway -->|rate limits per client| limits[(Quotas)]
  gateway --> api[Product API]
  issuer[Workload identity issuer] -.->|short-lived certificates or tokens| services[Internal services]
  api <-->|mTLS or signed token| services
  api -->|"check(actor, action, resource)"| engine[Policy engine]
  services --> engine

Delegated Access

When a machine acts for a person, as an OAuth app or an AI agent does, it holds a token that names both. The effective permission is the intersection of what the person may do and what the machine was granted. Neither side can expand the other.

Rendering diagram…

Diagram source
sequenceDiagram
  autonumber
  actor U as User
  participant G as Agent or app
  participant I as Authorization server
  participant T as Product API
  U->>I: Approve requested scopes
  G->>I: Token exchange: client credentials + user's grant, scopes billing:read refunds:request
  I-->>G: Delegated token (actor: agent, subject: alice, 15 minutes)
  G->>T: GET /invoices with delegated token
  T->>T: Allowed: alice can read, agent has billing:read
  G->>T: POST /refunds amount=900
  T-->>G: Requires approval above agent limit
  T-->>U: Approval request

Internal services face the same problem. When a service calls another with only its own broad credentials, the downstream service cannot tell whose request it is serving, and the intermediary is tricked into using its access for someone who should not have it: the confused deputy.

Rendering diagram…

Diagram source
flowchart LR
  user([user:bob, viewer]) -->|"export project 7"| api[API service]
  api -->|"service credentials: full access"| storage[Storage service]
  storage -->|returns everything| api

OAuth 2.0 Token Exchange (RFC 8693) prevents this. The calling service exchanges the user's token for a new one that names the subject (Bob) and the actor (the API service), is limited to one audience and narrow scopes, and expires in minutes. The user's identity never travels as a plain header that any service could set.

SituationPattern
Synchronous call between servicesExchanged token per hop, scoped to the next service.
Background job for a userThe job stores who requested it and re-checks that user's permissions when it runs, not only when it was queued.
Support staff in a customer accountTime-limited, approved elevation; every action is recorded as staff acting in the customer's organization, never as the customer.
AI agent or OAuth app for a userDelegated token naming the machine and the user, as above.

Service Policy

Calls between services are deny by default, and the policy is kept in the repository so every new dependency is visible in review:

caller            callee     operation          decision
service:orders    billing    POST /charges      allow
service:orders    billing    POST /refunds      deny
service:support   billing    POST /refunds      allow
any               billing    GET /health        allow

Design Points

  • Keys are shown once and stored hashed. Keys carry a recognizable prefix so leaked keys can be detected by secret scanners.
  • Scopes map to permissions. A scope such as invoices:read is a named bundle of product permissions, documented publicly.
  • Rotation without downtime. Two keys can be active at once during rotation, and every key records when it was last used.
  • Rate and spend limits per client. Quotas apply per key, application, or agent, not per IP, so one integration cannot exhaust another's capacity and a looping agent is bounded.
  • Agents are registered actors. Each agent has an owner, a description, and an allow-list of tools, so it can be listed, restricted, and disabled.
  • Human approval for high-impact actions. Refunds, deletions, and external messages above a threshold wait for a person.
  • Admin control. Organization admins can see and revoke every key, authorized app, and agent in their organization.

Across Ownership Scopes

ScopeWho owns what
ManagedThe broker issues API clients, machine identities, credentials, tokens, and scopes.
HybridThe application owns API clients, machine identities, credentials, token issuance, scopes, and policies.
BuiltMachine authentication, credential management, token issuance, and authorization are owned end to end.

Watch For

  • Keys tied to departed users. Personal keys must stop working when their owner loses access. Integrations that should persist belong to service accounts.
  • Over-broad scopes. Offer narrow scopes from the start; widening later is easy, narrowing breaks integrations.
  • Shared secrets. A single API key used by every internal service gives no isolation. Each service needs its own identity.
  • Network location as identity. "It came from inside the cluster" is not authorization.
  • Credential lifetime. Long-lived machine credentials are the most valuable thing an attacker can steal; prefer certificates and tokens that expire in minutes or hours.
  • Prompt injection as an authorization problem. Content an agent reads can instruct it to misuse its access. Permissions, not prompts, must be the boundary.
  • Shared user sessions. Giving an agent the user's own session makes its actions indistinguishable from the user's and impossible to restrict.
  • Delegation chains. Each hop should narrow scope, never widen it, and the chain should be visible in audit records.
  • Implicit elevation. An API key must not carry more authority than the user who created it, and must lose it when the user does.

Implementation

Implement this subsystem

Implementation starts with technical scoping. We review your existing architecture, agree on boundaries, interfaces, and acceptance criteria, and adapt this design to fit before committing to delivery milestones.

There is no off-the-shelf package or fixed price: scope, timeline, and cost follow from that alignment.

Implement this subsystem (opens in a new tab)