Mediated Sign-In
Mediated Sign-In (PAP draft 0.1 ยง4.7) is for companies whose users cannot
leave for a browser. The company declares the fields it needs in
auth.mediated.fields and an endpoint; the personal agent relays what the
user types. It is the one sign-in mode where a password passes through the
agent's host, so prefer Direct Sign-In, then Device Sign-In, when the company
offers them.
Protected inputโ
The values are collected through the Reactor pap.protectedInput
contribution (ProtectedInputs in TypeScript): the host's own trusted
surface, such as a native password field or a secure form outside the chat.
They never come from a model, a tool argument, or conversation text.
from personal_agent_protocol import PROTECTED_INPUTS, ProtectedInputRequest
class PasswordSheet:
async def collect(self, request: ProtectedInputRequest) -> dict[str, SecretStr]:
# request.purpose: "credentials" or "one_time_code"
# request.company_name / company_domain: show who is asking
# request.fields: name, label, secret (mask secret fields)
# request.sent_to: where the company sent a code, as it masked it
return await native_secure_form(request)
The answers must be exactly the declared fields, none empty, as SecretStr.
Signing inโ
client = MediatedSignInClient(reactor=reactor, http_client=http)
result = await client.sign_in(
mediated=document.auth.mediated,
organization=document.organization,
issuer=document.auth.issuer,
scopes=("orders:read",),
token=signed_out_session,
tenant_id="application-42",
pairwise_user_id=pairwise_user_id,
)
if result.status == "code_required":
result = await client.submit_code(
result.reference, token=signed_out_session,
tenant_id="application-42", pairwise_user_id=pairwise_user_id, issuer=issuer,
)
const client = new MediatedSignInClient({ reactor, fetch });
let result = await client.signIn({ mediated, organization, issuer, scopes, token, tenantId, pairwiseUserId });
if (result.status === 'code_required') {
result = await client.submitCode(result.reference!, { token, tenantId, pairwiseUserId, issuer });
}
What the client enforcesโ
- One endpoint, on the company's domain. An endpoint whose host is not the
company's domain or a subdomain is
unsafe_mediated_endpoint, raised before the user is asked anything. Redirects are refused, never followed, so a credential cannot be carried to another URL. - Sent once, then dropped. The values are serialized into one request body and cleared; no result, error, or log carries them.
- A code continues alone.
code_requiredstores an opaque reference in thepap.authorizationStateStore. The continuation sends only the code, to{endpoint}/{sign_in_id}, and only from the Session that started it (session_mismatchotherwise, with the pending state kept). - Statuses, not errors.
failedandexpiredare results; a refused request (for examplerate_limited) is an error withretry_afterwhen the company sent one. - Same Session, no escalation. A completed sign-in must be a signed-in DPoP token for the initiating Session, with no scope that was not requested. The Account Token goes to the token store; only its reference is returned.
Security boundaryโ
The model may say that sign-in needs the user's credentials and where the code was sent. It must never ask for a password or code in chat. If a user pastes one into the conversation anyway, the host should not forward it.
See the runnable Mediated Sign-In example and the upstream Mediated Sign-In specification.