Download

name: 3rd-party-auth description: Use when building an application that needs users to sign in with their Adom account ("Sign in with Adom"), or when integrating external OAuth providers (Google, GitHub, Slack) into an Adom service. Covers Authentication Intents (confirmation code flow), Google OAuth sign-in, the OAuth Gateway, and session management.

Third-Party Authentication

Let users sign in with their Adom account from any application — CLI tools, desktop apps, web apps, or embedded devices. The flow uses Authentication Intents with confirmation codes for security.

Authentication Intents (Sign in with Adom)

The primary way for 3rd party apps to authenticate users. The confirmation code prevents phishing — the user must verify what they see in the browser matches what the app shows them.

How It Works

1. App creates an intent         POST /auth/intents
2. App shows confirmation code   e.g. "BRTK-MXVZ" in the terminal
3. App opens browser             → hydrogen/auth/intents/{token}
4. User logs in (if needed)      email/password or Google
5. User enters confirmation code → matches what the app showed
6. Carbon creates a session      → links it to the intent
7. App receives session token    via polling or SSE

Step 1: Create an Intent

curl -X POST https://carbon.adom.inc/auth/intents \
  -H "Content-Type: application/json" \
  -d '{"max_age": 7776000}'

Response:

{
  "token": "V1StGXR8_Z5jdHi6B-myT",
  "confirmation_code": "BRTK-MXVZ",
  "created_at": "2026-05-15T10:00:00Z",
  "expires_at": "2026-05-15T10:15:00Z",
  "requested_max_age": 7776000,
  "updates_url": "https://carbon.adom.inc/auth/intents/V1StGXR8_Z5jdHi6B-myT"
}
Field Description
token Unique identifier for this intent (nanoid)
confirmation_code 8 consonants formatted as XXXX-XXXX — show this to the user
expires_at Intent expires in 15 minutes
requested_max_age Session TTL in seconds (default: 90 days)
updates_url Full URL for polling status

Step 2: Show the Code and Open the Browser

Display the confirmation code prominently in your app, then open the browser:

console.log(`\nConfirmation code: ${intent.confirmation_code}\n`);
console.log('Opening browser to complete sign-in...');

const loginUrl = `https://hydrogen.adom.inc/auth/intents/${intent.token}`;
open(loginUrl); // opens default browser

The user sees Hydrogen's login page. After logging in, they see the "Authorize Device" page with a code input field.

Step 3: Wait for Authentication

Option A — Server-Sent Events (recommended):

const eventSource = new EventSource(
  `https://carbon.adom.inc/auth/intents/${intent.token}/status`,
  { headers: { Accept: 'text/event-stream' } }
);

eventSource.addEventListener('authenticated', (e) => {
  const { session_token } = JSON.parse(e.data);
  console.log('Authenticated! Session token:', session_token);
  eventSource.close();
});

eventSource.addEventListener('timeout', () => {
  console.log('Intent expired. Please try again.');
  eventSource.close();
});

Option B — Short polling:

async function pollForAuth(token) {
  while (true) {
    const res = await fetch(
      `https://carbon.adom.inc/auth/intents/${token}/status`
    );
    const data = await res.json();

    if (data.state === 'authenticated') {
      return data.session_token;
    }

    // Still pending — wait 2 seconds
    await new Promise(r => setTimeout(r, 2000));
  }
}

Step 4: Use the Session Token

The returned session_token is a Carbon session. Use it as a Bearer token:

curl https://carbon.adom.inc/user \
  -H "Authorization: Bearer ${SESSION_TOKEN}"

Session tokens have the prefix u_ (user) or c_ (container). Application sessions created by intents use the Application session kind and respect the max_age you requested.

Complete Example (Node.js CLI)

import open from 'open';

const CARBON_API = 'https://carbon.adom.inc';

async function signIn() {
  // 1. Create intent
  const res = await fetch(`${CARBON_API}/auth/intents`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ max_age: 60 * 60 * 24 * 90 }),
  });
  const intent = await res.json();

  // 2. Show code and open browser
  console.log(`\n  Confirmation code: ${intent.confirmation_code}\n`);
  console.log('  Opening browser to sign in...\n');
  await open(`https://hydrogen.adom.inc/auth/intents/${intent.token}`);

  // 3. Wait for authentication via SSE
  const session_token = await new Promise((resolve, reject) => {
    const es = new EventSource(`${intent.updates_url}/status`, {
      headers: { Accept: 'text/event-stream' },
    });
    es.addEventListener('authenticated', (e) => {
      resolve(JSON.parse(e.data).session_token);
      es.close();
    });
    es.addEventListener('timeout', () => {
      reject(new Error('Login timed out'));
      es.close();
    });
  });

  console.log('Signed in successfully!');
  return session_token;
}

Confirmation Code Details

  • Format: 8 uppercase consonants, split as XXXX-XXXX
  • Alphabet: B C D F G H J K L M N P Q R S T V W X Z (no vowels — avoids accidental profanity)
  • Verification: Case-insensitive, dashes/spaces stripped before comparison
  • Purpose: Prevents phishing — a malicious link can't trick a user into authorizing the wrong app because they'd need to see the code displayed by the legitimate app

Intent Lifecycle

State Description
Created Intent exists, session_id is null, waiting for user
Authenticated User entered correct code, session created and linked
Consumed Application retrieved the session token
Expired 15 minutes elapsed without completion

Intents are one-time use. Once a session is linked, the intent cannot be reused. The session token can only be retrieved once (consumed flag).

Google OAuth Sign-In

Users can now sign in to Hydrogen with their Google account. This is built into the login page — no extra integration needed from 3rd party apps.

Flow:

  1. User clicks "Sign in with Google" on the login page
  2. Redirected to Google's consent screen (OIDC)
  3. Google sends an auth code back to Carbon
  4. Carbon verifies the ID token, extracts email + profile
  5. If the email matches an existing user → logs them in
  6. If new user → redirects to username selection page

The Google sign-in is part of the login page that Authentication Intent users see, so 3rd party apps get it for free.

OAuth Gateway (External Providers)

For integrating external OAuth providers (YouTube, GitHub, Slack) into Adom services, use the OAuth Gateway. This is a separate pattern from Authentication Intents — see the oauth skill for the full reference.

Key difference:

  • Authentication Intents = "Sign in with Adom" (user authenticates TO your app via Adom)
  • OAuth Gateway = "Connect YouTube/GitHub/etc." (user grants your Adom service access to an external provider)

API Reference

POST /auth/intents

Create a new authentication intent.

Body:

{ "max_age": 7776000 }

max_age is optional (default: 90 days). Sets the TTL of the session that will be created.

Response: 201 Created with the intent object.

GET /auth/intents/{token}

Retrieve intent details. Returns the same object as creation.

Errors:

  • 404 — Intent not found
  • 410 — Intent expired

GET /auth/intents/{token}/status

Check authentication status. Supports two modes:

JSON (default):

{ "state": "pending" }
// or
{ "state": "authenticated", "session_token": "u_..." }

SSE (Accept: text/event-stream):

  • Event authenticated with { "session_token": "u_..." } data
  • Event timeout when the intent expires
  • Keep-alive pings to maintain connection

PATCH /auth/intents/{token}

Link a user session to the intent. Requires authentication (session cookie) and the confirmation code.

Body:

{ "confirmation_code": "BRTK-MXVZ" }

Errors:

  • 403 — Wrong confirmation code
  • 409 — Intent already has a linked session
  • 410 — Intent expired

Security

  • Confirmation codes prevent session fixation and phishing attacks
  • Auth codes and session tokens are never exposed in URLs or logs
  • Intents expire after 15 minutes
  • Sessions created by intents use the Application kind (distinguishable from user sessions)
  • The consumed flag ensures one-time retrieval of session tokens