Sender
A sender is an identity an auth provider can authenticate as. One provider, many users: admin, read-only viewer, a user in another tenant, a service account.
Without senders, testing “does this endpoint reject a non-admin?” means reconfiguring your auth or keeping a second provider around. With them it is a dropdown.
senders: - id: admin@example.com label: Staging admin tenant: acme secretRef: users-api.admin.PASSWORD vars: role: admin| Field | Meaning |
|---|---|
id | Required. The identifier the provider authenticates with — a username, an email, a subject id. |
label | Human name shown in the picker. |
tenant | An informational label for grouping in the UI. To choose which organization a token is for, see Organizations. |
secretRef | The name of this identity’s keychain entry. Never a value. |
vars | Non-secret per-sender values, available as {{var}} while the token is being resolved. |
A provider with no senders has a single implicit identity, and the script receives sender as
null.
Organizations
Section titled “Organizations”One person can belong to several organizations, and a token is minted for one of them — so
“this user can’t see that org’s data” may only mean the token was for the other org. A request
can name the organization its token is for with auth.org:
auth: provider: clerk-dev sender: user_2abc org: org_2xyz| Provider | What org becomes |
|---|---|
| Clerk | The session’s active organization (an org_… id) |
| Keycloak (26+) | An organization:<alias> scope |
| Auth0 | The organization parameter |
| Firebase | The Identity Platform tenantId |
| Request / script providers | {{org}} in the login request, org in the script |
| Supabase, generic OAuth 2.0, AWS SigV4 | Refused — they have no organization mechanism, and quietly using the default would give a wrong answer |
Tokens are cached per provider, sender and organization, so the same user in two orgs never
shares a token. Leave org out and the identity provider’s own default applies, as before.
Where senders come from
Section titled “Where senders come from”They are rows in the provider file, and there are three ways they get there.
Typed in. The manual form takes an id, a label, and — where the provider signs in per sender — a password, which goes to the keychain under a name the file then references.
Fetched from the identity provider. Clerk, Firebase and the rest can list their own users, so the picker shows real accounts rather than asking you to remember an email. A Firebase project configured with a service account can list users this way too. Refreshing re-reads labels and tenants; it never touches a stored secret.
From an OAuth provider’s own endpoints. A generic oauth2 provider populates senders from its
userinfo endpoint (the signed-in subject) and, if the server offers one, from an admin or
directory endpoint named by sendersUrl.
Not every provider signs in per sender. Clerk uses one shared secret key; an oauth2 sender is an
identity the authorization server already knows. The view only asks for a password where the
engine would actually refuse without one — password-grant Keycloak and Auth0, Firebase without a
service account, Supabase — and a sender missing one is flagged rather than silently failing at
send time.
AWS senders are roles
Section titled “AWS senders are roles”For an awsSigV4 provider a sender is not a person: it is an assume-role target, and the
Senders tab is labelled Roles.
The role lives in the sender’s ordinary non-secret vars — roleArn (required), plus optional
sessionName, externalId, durationSeconds and a region override. No new schema field was
added for this, which is the point: “send as the read-only role” is the same dropdown every other
provider has.
Selecting one makes the engine call STS AssumeRole with the provider’s base credentials and sign
with the returned temporary ones, cached under that sender until they expire. A provider with no
roles signs with the base credentials directly.
Choosing one
Section titled “Choosing one”A request’s auth block names the provider and, optionally, the sender:
auth: provider: users-api sender: admin@example.comLeave sender out and the provider’s default is used. The Auth tab has a picker; the Auth
providers view marks one sender as the default with a star.
Tokens are cached per sender, so switching back and forth does not re-authenticate each time.
In a script
Section titled “In a script”The selected sender is passed to an auth script, which is how one script serves every identity:
async function authenticate({ secret, sender, env, fetch }) { const res = await fetch(`${env.baseUrl}/login`, { method: "POST", body: JSON.stringify({ user: sender.id, pass: secret(sender.secretRef), }), }); return { token: (await res.json()).token };}Why it matters
Section titled “Why it matters”Authorization bugs are the ones that reach production, because “it worked when I tried it” usually means “it worked as me”. Senders make the other half cheap: send the same endpoint as an admin, as a viewer, and as somebody from a different tenant, and compare the statuses.
An agent can do that too. list_senders returns the identities of one
provider or of all of them — id, label, tenant, which is default, and whether the engine would
actually be able to use it (with the reason when it could not) — and send_request takes a
sender. So “probe this endpoint as each of these three identities and show me the statuses” is
one instruction.
Names only, as everywhere else: never a token, a password, or even a secret name.
The tinspec CLI takes --sender for the same reason, which is what lets a pipeline perform the
real login rather than replaying a stored token.