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
- Mark required only what is truly required. An app that requires everything reads as greedy; grant rates drop.
- Write reasons users understand. “To email your receipts” beats “for communication purposes”.
- Ask late if you can. You can start with name only, and re-run the authorize flow later when a feature needs email — users grant more when the context is obvious.
- Watch the numbers. The Data-field grants chart on your app's Overview shows exactly which optional fields users decline.
Guide 5 — Launch checklist
- Production redirect URL added; dev localhost URL removed.
- Secret in a secrets manager; CI has no access to it.
stateverified in the callback.- 401 → refresh, 403 → sign-out paths tested.
- Icon, description and website set (Settings) — they appear on the consent screen.
- Legal: your own privacy policy covers the ONE data you receive — see legal hub.
Extreme