AuthenticationIntent Integration
UnreviewedGuide for integrating with the Adom AuthenticationIntent API — device auth flows, headless login, intent-based auth for 3rd-party apps.
AuthenticationIntent Integration
AuthenticationIntent lets 3rd-party apps authenticate users via Adom without handling credentials. The flow:
- Your app calls
POST /auth/intents→ receives atokenandauth_url - Direct the user to
auth_url(browser, QR code, deep link, etc.) - The user logs in on Adom; your app receives a
session_tokenvia SSE or polling - Use
session_tokenfor authenticated API requests
Intents expire after 15 minutes. Sessions default to 90 days but accept a custom max_age.
If $ARGUMENTS is set, focus examples and explanations on: $ARGUMENTS
API reference
Base prefix: /auth
POST /auth/intents — Create an intent
Request body (JSON, all fields optional):
{ "max_age": 2592000 }
max_age is the desired session lifetime in seconds. Defaults to 7,776,000 (90 days).
Response 201 Created:
{
"token": "abc123xyz",
"created_at": "2024-01-01T00:00:00Z",
"expires_at": "2024-01-01T00:15:00Z",
"requested_max_age": 2592000,
"auth_url": "https://hydrogen.adom.inc/auth/intent?token=abc123xyz",
"updates_url": "https://carbon.adom.inc/auth/intents/abc123xyz/status",
"completed": false
}
GET /auth/intents/{token} — Fetch intent state
Returns the same shape as the POST response. completed becomes true once the user has logged in.
PATCH /auth/intents/{token} — Complete intent (internal)
Called by the Adom frontend when a logged-in user visits auth_url. Your application does not call this endpoint — the user's browser does.
GET /auth/intents/{token}/status — Poll or stream for completion
Short polling — no special headers required:
{ "state": "pending" }
// or, once the user has logged in (token is consumed on first retrieval):
{ "state": "authenticated", "session_token": "tok_..." }
SSE streaming — send Accept: text/event-stream:
Holds the connection open until the user logs in or the intent expires.
| Event | Data |
|---|---|
authenticated |
{"session_token": "tok_..."} |
timeout |
(no data — intent expired) |
The server sends SSE keep-alive pings, so normal connection timeouts won't fire prematurely.
Integration examples
TypeScript — SSE (recommended for long-lived CLI / desktop apps)
async function authenticateWithAdom(
apiBase: string,
maxAge?: number,
): Promise<string> {
const res = await fetch(`${apiBase}/auth/intents`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(maxAge ? { max_age: maxAge } : {}),
});
if (!res.ok) throw new Error(`Failed to create intent: ${res.status}`);
const intent = await res.json();
console.log("Authenticate here:", intent.auth_url);
return new Promise((resolve, reject) => {
const es = new EventSource(intent.updates_url);
es.addEventListener("authenticated", (e) => {
es.close();
resolve(JSON.parse(e.data).session_token);
});
es.addEventListener("timeout", () => {
es.close();
reject(new Error("Authentication timed out — intent expired"));
});
es.onerror = () => {
es.close();
reject(new Error("SSE connection failed"));
};
});
}
TypeScript — Short polling (simpler, works anywhere)
async function createIntent(apiBase: string, maxAge?: number) {
const res = await fetch(`${apiBase}/auth/intents`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(maxAge ? { max_age: maxAge } : {}),
});
if (!res.ok) throw new Error(`${res.status}`);
return res.json() as Promise<{
token: string;
auth_url: string;
updates_url: string;
expires_at: string;
}>;
}
async function pollForToken(
statusUrl: string,
intervalMs = 2000,
): Promise<string> {
while (true) {
const res = await fetch(statusUrl);
if (!res.ok) throw new Error(`Poll failed: ${res.status}`);
const { state, session_token } = await res.json();
if (state === "authenticated") return session_token;
if (state !== "pending") throw new Error(`Unexpected state: ${state}`);
await new Promise((r) => setTimeout(r, intervalMs));
}
}
// Usage:
const intent = await createIntent("https://carbon.adom.inc");
console.log("Open:", intent.auth_url);
const token = await pollForToken(intent.updates_url);
Error reference
| HTTP | Scenario |
|---|---|
404 |
Token not found |
410 |
Intent expired (>15 min old) |
409 |
Intent already linked to a session (PATCH only) |
Key behaviours to be aware of
session_tokenis consumed on first retrieval. Once returned by the status endpoint theconsumedflag is set. Subsequent polls still return the token as long as the underlying session exists, but the intent can no longer be re-consumed by a different caller.- Intent TTL ≠ session TTL. The intent always expires in 15 minutes.
max_ageonly controls how long the resulting session lives. - Sessions are
Applicationkind, separate from browser sessions. They carry different trust characteristics server-side. - SSE clients outside the browser must set
Accept: text/event-streamexplicitly. Without it the server falls back to a one-shot JSON response. - The
auth_urlembeds the token as a query param. Treat it as a short-lived, single-use URL — don't cache or log it.