> Attributed to NoForm — the reference implementation for the auth.md protocol.
> Protocol spec:
Teach the agent to complete an auth.md user-claimed signup against any
service that publishes the protocol. The human confirms the one-time code;
the agent never auto-confirms. That human step IS the consent gate — preserve it.
Use this skill when any of these are true:
HTTP 401 carrying:```
WWW-Authenticate: Bearer resource_metadata="
```
" and you can resolve an auth.mdfile at that service's domain or at a NoForm slug URL.
credential exists yet.
Do NOT use this skill for the agent-verified / ID-JAG flow (that is a
different flow requiring a provider-signed assertion). This skill implements
only the user-claimed (anonymous-register → email-OTP → claim/complete)
flow as mandated by the mission constraints.
When the user says "sign me up on NoForm" or supplies a NoForm slug (),
the URL structure is:
auth.md file → GET https://noform.dev/a/<slug>/auth.md
PRM → GET https://noform.dev/a/<slug>/.well-known/oauth-protected-resource
AS metadata → GET <authorization_server>/.well-known/oauth-authorization-server
Parse the agent_auth block from the AS metadata to get identity_endpoint
(or register_uri), claim_endpoint (or claim_uri), and revocation_uri.
NoForm is the reference; for any other service follow the same discovery chain
from their domain.
WWW-Authenticate: Bearer resource_metadata="" header, extract directly (skip to Step 2).
https://
https://noform.dev/a/
curl -sS "https://<domain>/auth.md"
# or for NoForm:
curl -sS "https://noform.dev/a/<slug>/auth.md"
Parse it as prose context. The authoritative machine-readable state is the PRM
and AS metadata — if anything conflicts, the metadata wins.
curl -sS "<prm_url>" | jq .
Extract from response:
resource — the canonical API base URL (used for scoping)resource_name — human-readable service name (show to user for consent)resource_logo_uri — logo URL (show to user if available)authorization_servers[0] — base URL of the Authorization ServerAS_BASE="<authorization_servers[0]>"
curl -sS "${AS_BASE}/.well-known/oauth-authorization-server" | jq .
From the agent_auth block extract and store:
agent_auth.identity_endpoint (may also appear as register_uri)agent_auth.claim_endpoint (may also appear as claim_uri)agent_auth.revocation_uriagent_auth.identity_types_supported — **verify anonymous is listed beforeproceeding.** If it is not, tell the user this service does not support the
user-claimed flow and stop.
Before registering, check whether the agent already holds a valid scoped token
for this service (keyed by resource URL). If yes, surface it to the user and
ask whether they want to re-register or skip.
Surface to the user IN-CHANNEL before doing anything:
I'm about to register an account on <resource_name> using the auth.md
user-claimed flow. Here's what will happen:
1. I register anonymously — no account exists yet.
2. I'll ask for your email address.
3. <resource_name> will email you a one-time code.
4. You read the code back to me.
5. I submit the code to bind the account to your email.
6. I store a scoped token for <resource_name> — only for that service.
Your email and primary credentials are never stored or sent anywhere else.
Proceed? (yes / no)
Wait for explicit confirmation before continuing.
curl -sS -X POST "<identity_endpoint>" \
-H "Content-Type: application/json" \
-d '{"type": "anonymous"}'
Expected success response:
{
"credential": "<pre-claim-token>",
"claim_token": "<claim-token>",
"credential_expires": "<iso8601>",
"scopes": ["api.read"]
}
Store claim_token. The credential (if present) is a pre-claim scoped token;
note it but do not hand it to the user — it is a low-scope placeholder.
If the service does not return a pre-claim credential (email-required variant),
that is fine — proceed to the claim step.
Error handling:
anonymous_not_enabled → tell user this service requires identity assertion;stop.
rate_limited → tell user to try again later; stop.To bind this account to you, I need your email address. What email should
I use to register with <resource_name>?
Wait for the user's reply. Never auto-fill the email.
curl -sS -X POST "<claim_endpoint>" \
-H "Content-Type: application/json" \
-d "{\"claim_token\": \"<claim_token>\", \"email\": \"<user_email>\"}"
Expected response (email-dispatch confirmation):
{
"status": "pending",
"message": "Verification email sent to <email>"
}
Some services return a verification_uri here — surface it to the user.
Error handling:
invalid_claim_token → the anonymous registration may have expired; restartfrom Step 6.
claim_expired → same; restart from Step 6.previously_claimed → tell the user this email is already registered withthis service; stop.
rate_limited → tell user to try again later.Tell the user IN-CHANNEL:
📧 A verification email is on its way to <email>.
Open it and look for a 6-digit code (or a "Verify email" link).
When you have it, paste the code here and I'll complete the registration.
If a verification_uri was returned in Step 8, include it:
You can also click this link to verify: <verification_uri>
⚠️ DO NOT PROCEED until the user provides the code.
NEVER auto-confirm. NEVER poll the service for code completion.
The human completing the claim IS the consent gate — preserve it absolutely.
When the user provides the code:
curl -sS -X POST "<claim_endpoint>/complete" \
-H "Content-Type: application/json" \
-d "{\"claim_token\": \"<claim_token>\", \"otp\": \"<otp>\"}"
Expected success response:
{
"credential": "<active-scoped-token>",
"credential_expires": "<iso8601 or null>",
"scopes": ["api.read", "api.write"]
}
Error handling:
otp_invalid → tell user the code was wrong; ask them to check and try again(allow up to 3 attempts before stopping).
otp_expired → tell user the code expired; offer to restart from Step 8(re-send OTP).
claim_expired → restart from Step 6.previously_claimed → account already claimed; stop.Store the returned credential scoped to this service:
Key format: auth_md_token:
Value: { "credential": "...", "scopes": [...], "expires": "...", "service": "
If AUTH_MD_TOKEN_STORE env var is set, append to that JSON file.
Otherwise, hold in session memory only.
**Never write the user's primary password, primary API keys, or other service
credentials anywhere.**
Tell the user:
✅ Registered successfully with <resource_name>!
Scopes granted: <scopes>
Bound to: <email>
Expires: <credential_expires or "never">
Your token is stored scoped to <resource_name> only. I'll use it
automatically for requests to this service.
When the user asks to revoke a token for a service:
curl -sS -X POST "<revocation_uri>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <credential>" \
-d "{\"token\": \"<credential>\"}"
On success, delete the stored token for that resource key and confirm to the
user. On a 401 from a previously-working credential, the token was already
revoked externally — delete it locally and notify the user.
separate, provider-bound flow outside the scope of this skill.
consent gate.
the auth.md claim/complete endpoint are stored.
resource URL as key). One service cannotaccess another service's token.
and can't confirm the OTP, stop and surface a message asking them to initiate
the flow when they're available.
match the domain you started discovery from (or the NoForm host) before
POSTing credentials.
| Step | HTTP call |
|---|---|
| ------ | ----------- |
| 1 | GET |
| 2 | GET (from WWW-Authenticate or /.well-known/oauth-protected-resource) |
| 3 | GET |
| 6 | POST {"type":"anonymous"} |
| 8 | POST {"claim_token":"…","email":"…"} |
| 10 | POST {"claim_token":"…","otp":"…"} |
| revoke | POST with Bearer + {"token":"…"} |
For NoForm ():
https://noform.dev/a//.well-known/oauth-protected-resource https://noform.dev/a//.well-known/oauth-authorization-server (or the authorization_servers[0] URL from PRM)
| Code | Endpoint | Action |
|---|---|---|
| ------ | ---------- | -------- |
anonymous_not_enabled | register | Stop; tell user |
identity_assertion_not_enabled | register | Stop; tell user |
rate_limited | any | Stop; ask user to retry later |
invalid_claim_token | claim | Restart from Step 6 |
claim_expired | claim / claim/complete | Restart from Step 6 |
previously_claimed | claim / claim/complete | Stop; tell user |
otp_invalid | claim/complete | Ask user to retry (max 3x) |
otp_expired | claim/complete | Offer to re-send OTP (Step 8) |
credential_expired | API call | Delete stored token; restart from Step 6 |
unsupported_credential_type | register | Stop; report unsupported |
noform.devapi.example.com" → discover from that domainapi.example.com — get me credentials" → use the WWW-Authenticate header to start discovery
*This skill implements only the user-claimed flow from the auth.md protocol.
The agent-verified (ID-JAG) flow is intentionally out of scope.*
共 1 个版本