Every OAuth client delivered to the team that owns it, with no secret changing hands
Courier turns OAuth client registration into a reviewed request. After approval it creates the client in the identity provider and places the credentials in a vault path that only the owning team's identity group can open. Nobody copies, emails or pastes a client_secret.
The challenge
Every integration with the identity provider needs a client registration: web apps, backend jobs, and now MCP servers and AI agents that call APIs on a user's behalf. The number of clients grows with every team and every new tool, but the process for issuing them has not changed. An identity administrator creates the client in a console, then sends the client_id and client_secret to someone.
Secrets travel through people
Credentials are copied into email, chat and tickets. They stay there permanently, and nobody can say who has seen them.
The identity team becomes a queue
Every new client, redirect URI change or cleanup waits on a small group of administrators with console access.
Clients outlive their owners
Registrations carry no reliable owner. When a project ends or a person leaves, the client and its secret keep working.
Behind these sit two quieter problems. Configuration drifts from standards because each client is set up by hand: an extra grant here, a wildcard redirect there. And when an auditor asks who approved a client, who can read its secret and when it was last issued, the answers live in several systems or nowhere.
What changes with Courier
Today
- A team files a ticket or sends a message
- An administrator configures the client by hand
- The secret is sent to whoever asked
- Access to the secret follows whoever received it
- Removing a client depends on someone remembering
With Courier
- A team opens a pull request describing the client
- Automated checks enforce standards; the right people approve
- Courier creates the client and stores the secret directly in the vault
- Only members of the owning identity group can read it
- Deleting the request deletes the client and destroys the secret
The identity team moves from doing every registration to owning the rules every registration must follow. Teams get credentials in minutes instead of waiting in a queue, and the handoff that used to be the weakest step no longer involves a person.
How it works
Four stages, each owned by the system best suited to it: Git records intent and approval, the identity provider holds the client, and the vault is the only place the secret is ever kept.
Pull request
Review with guardrails
Courier
Owning team
The secret appears only in the amber box. It is generated inside Courier and written straight to the vault.
Under the hood, in plain terms
Six design choices carry the promise. Each has a one-line technical note for readers who want to know how it is enforced rather than just that it is.
| Principle | What it means | How it is enforced |
|---|---|---|
| Approval is a pull request | The decision, the reviewer and the reason sit in one permanent record. | Branch protection requires code-owner review and a passing courier validate check before merge. |
| Access follows identity groups | Joining or leaving a team in the identity provider grants or removes access to its secrets. No separate list to maintain. | Vault trusts the same IdP; the groups claim maps to a vault policy scoped to kv/teams/<team>/. |
| Courier can write, but not read | Even if Courier were compromised, it could not reveal secrets it has already delivered. | Courier's vault policy allows create, update, patch and delete, not read. |
| Stored before it is live | A failure at any moment can never leave a working secret that nobody has. | The secret is written as pending before the IdP accepts it, then promoted to active. |
| Only touches what it created | Existing, hand-built clients are safe from accidental changes. Taking one over is a deliberate, approved step. | Courier marks its applications with the owning team and refuses to modify any application without the marker, or owned by another team, unless an adoption request carries the identity team's approval label. |
| Every rule is checked twice | What reviewers see in the pull request is exactly what will be enforced after merge. | The CI check and the in-cluster controller run the same validation code. |
Many MCP clients (IDEs, desktop apps, CLI agents) are public clients that use PKCE and hold no secret, and newer MCP authorization guidance reduces how often they must be pre-registered at all. Courier still records their ownership. Its strongest value is for confidential clients: MCP servers, backends and machine-to-machine jobs, where a real secret exists and must reach the right workload safely.
Risks and the controls that address them
| Risk | Control | Evidence you can show an auditor |
|---|---|---|
| Secret disclosed in transit between people | No human handoff; secret generated and stored by Courier | Vault audit log shows every read, by identity |
| Wrong team obtains a client or its secret | Owner group is fixed by the team directory; vault path scoped per group | Pull request history; vault policy per team |
| Unreviewed or non-standard client configuration | Automated policy check plus code-owner approval before merge | Required status check and approvals on every merged PR |
| High-risk grants issued casually | Grants such as client_credentials need an explicit approval label | Label and approver recorded on the PR |
| Orphaned clients with live secrets | Every client has an owning team; removing the request deletes client and secret | Request inventory in Git; controller status per client |
| Automation itself becomes a liability | Least privilege: write-only vault access, IdP role limited to OAuth clients | Vault policy and IdP role definitions in the repository |
| A secret stays valid for years, or after a suspected leak | Rotation by pull request, or automatically after a maximum age; the IdP rejects the old secret as soon as the new one is stored | rotationHandled and lastSecretIssued in the request's status; version history in the vault |
| Clients built by hand before Courier, secrets already emailed | Adoption: Courier takes over the existing client, keeps its client ID and replaces the secret; needs an identity-team approval label | Label on the PR; application marked as managed by Courier in the IdP |
In Authentik, a client_credentials request that also carries a service account's username and app password gets a token whatever client secret is sent, including none. In that mode the app password is the real credential and the client secret is decoration. We found this during Phase 4, when an old, rotated secret still "worked".
Two consequences. For identity teams: treat service-account app passwords as the credential to govern, and do not read "a token was issued" as evidence a client secret is valid. For Courier: every check now proves the secret at the token revocation endpoint, which always authenticates the client, and also shows that a wrong secret is refused.
What it means for each role
Identity team
Owns the policy file and the vault and IdP integrations. Reviews only what needs judgment, such as new grant types or unusual redirect hosts, instead of every registration.
Application teams
Request, change and retire their own clients with a pull request. Read credentials with the identity they already use, from a laptop or a workload.
Security and audit
Get a single, reviewable record of who asked for what, who approved it, where the secret lives and who read it.
Status
Courier is running end to end in a reference environment built on Authentik, HashiCorp Vault Community Edition, Kubernetes and ArgoCD. The identity-provider integration is a small adapter, so Okta and similar providers can be added without changing the rest of the system.
Access model proven
Vault with automatic unsealing; identity groups decide who can read each team's secrets.
Delivery automated
A request becomes a client plus stored credentials; the owning team can use them and other teams cannot.
Pull request workflow
Requests live in Git with policy checks, code-owner approval and automatic sync after merge.
Consumption made easy
A Python sample and a per-team External Secrets store, both proven to serve only the owning team; the controller ships from a registry, pinned by digest.
Self-service portal
A Backstage form opens the pull request; each client appears on its team's page with live delivery status.
Lifecycle
Secret rotation by pull request or on a schedule, adoption of hand-built clients, and drift repair that writes to the IdP only when something really changed. Audit dashboards remain open.
Portability
Okta adapter and key-based client authentication (private_key_jwt).
Where to go next
The walkthrough follows a real request, pull request #1, from the policy gate through merge, delivery and a check that only the owning team can read the credentials. It shows the evidence captured at every step. The team-bravo case study lists everything that request created in GitHub, ArgoCD, Kubernetes, Authentik and Vault, and what the team can do now.
How it works
Components, the request lifecycle, the trust model and failure handling.
Application teamsRequest a client
Write a request, open the pull request, and read your credentials.
Implementation engineersImplementation guide
Install, verify and operate every component, with known pitfalls.
Everyone, when you need a detailReference
Request fields, status conditions, policy file, CLI flags and vault layout.