ExtremeX CLIENT

Guides

Practical, end-to-end walkthroughs. Each assumes you have registered an app on the dashboard and have your client ID and secret.

Guide 1 — Add “Sign in with ExtremeX ONE” to a web app

1. Send the user to ONE

// GET /auth/login  (your server)
const state = crypto.randomBytes(16).toString("base64url");
res.cookie("oauth_state", state, { httpOnly: true, secure: true, maxAge: 600_000 });
res.redirect("https://one.extremextechnology.com/oauth/authorize?" + new URLSearchParams({
  client_id: process.env.EXONE_CLIENT_ID,
  redirect_uri: "https://app.yourco.com/auth/callback",
  state,
}));

2. Handle the callback

// GET /auth/callback?code=…&state=…
if (req.query.state !== req.cookies.oauth_state) return res.sendStatus(400);

const tokens = await fetch("https://api.extremextechnology.com/one/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    grant_type: "authorization_code",
    code: req.query.code,
    client_id: process.env.EXONE_CLIENT_ID,
    client_secret: process.env.EXONE_CLIENT_SECRET,
    redirect_uri: "https://app.yourco.com/auth/callback",
  }),
}).then(r => r.json());

const user = await fetch("https://api.extremextechnology.com/one/oauth/userinfo", {
  headers: { Authorization: `Bearer ${tokens.access_token}` },
}).then(r => r.json());

// user.sub is the stable id — create or match your local account with it.

3. Handle what the user declined

// Optional fields simply don't appear in userinfo:
const displayName = user.name ?? user.username ?? "there";
const email = user.email ?? null;   // null → user declined; don't nag, offer value

Guide 2 — Keep users signed in (refresh tokens)

Request the Stay signed in (offline) field in Data access. The token response then includes a refresh_token.

// Access tokens live 1 hour. On 401 invalid_token, refresh:
const next = await fetch("https://api.extremextechnology.com/one/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    grant_type: "refresh_token",
    refresh_token: stored.refresh_token,
    client_id: process.env.EXONE_CLIENT_ID,
    client_secret: process.env.EXONE_CLIENT_SECRET,
  }),
}).then(r => r.json());

// Tokens ROTATE: always persist next.refresh_token, discard the old one.
// A 403 access_revoked means the user disconnected you — wipe tokens, restart flow.

Guide 3 — Store per-user app data

With the App data (attributes) field granted, each user gets a private key-value store scoped to your app — settings, progress, preferences — without you running a database for it.

await fetch("https://api.extremextechnology.com/one/data", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ level: 7, theme: "dark" }),
});

const all = await fetch("https://api.extremextechnology.com/one/data", {
  headers: { Authorization: `Bearer ${access_token}` },
}).then(r => r.json());   // { level: 7, theme: "dark" }

Guide 4 — Design a consent screen users accept

Guide 5 — Launch checklist

  1. Production redirect URL added; dev localhost URL removed.
  2. Secret in a secrets manager; CI has no access to it.
  3. state verified in the callback.
  4. 401 → refresh, 403 → sign-out paths tested.
  5. Icon, description and website set (Settings) — they appear on the consent screen.
  6. Legal: your own privacy policy covers the ONE data you receive — see legal hub.