/integration.md_For engineers and coding agents wiring an application onto AutoX SSO, or migrating one off its own custom authentication._
This page is served live by the identity provider itself:
https://sso.autogrc.cloud/integration.mdhttps://sso.autogrc.cloud/integrationEvery value below reflects the running provider's configuration. When a fact here and the discovery document disagree, the discovery document wins.
AutoX SSO is an OpenID Connect (OIDC) identity provider. Migrating an app means: stop handling credentials yourself, and instead redirect users here to authenticate, then receive them back with tokens.
response_type=code only. No implicit, no hybrid, no password/ROPC grant.sub claim. Passkeys and MFA are owned entirely by the IdP; your app never sees them.autox:roles and autox:orgs claims.There is no self-service or dynamic client registration. Ask an AutoX SSO admin to register your app in the console (https://sso.autogrc.cloud/admin → Applications) and hand you:
https://sso.autogrc.cloudtoken_endpoint_auth_method: none).Configure your client from the discovery document — do not hardcode endpoint paths:
https://sso.autogrc.cloud/.well-known/openid-configuration
Current values (authoritative source is the discovery document above):
https://sso.autogrc.cloudhttps://sso.autogrc.cloud/authhttps://sso.autogrc.cloud/tokenhttps://sso.autogrc.cloud/mehttps://sso.autogrc.cloud/jwkshttps://sso.autogrc.cloud/session/endopenid (required) — yields subprofile — name, preferred_usernameemail — email, email_verifiedorgs — autox:org_type, autox:is_ey_employee, autox:orgsroles — autox:rolesoffline_access — issues a (rotating) refresh tokenA typical web app requests: openid profile email orgs roles. Add offline_access only if you need to refresh sessions without redirecting the user again.
Identity, org, and role claims are placed in the ID token, so you usually do not need a /userinfo round-trip:
sub — stable, unique user id. Use this as the foreign key to your local user record. It never changes and is never reused.email, email_verified, name, preferred_usernameautox:is_ey_employee — booleanautox:org_type — "EY" or "client"autox:orgs — array of { id, name, type, team, role }autox:roles — de-duplicated array of the user's role strings across all membershipsiss, aud (= your client_id), exp, and nonce./me, or hand it to services that introspect it. Default lifetime ~10 minutes (admin-configurable).resource=https://sso.autogrc.cloud/api on the authorization request. You then receive a JWT access token (ES256, aud=https://sso.autogrc.cloud/api) carrying autox:is_ey_employee, autox:org_type, and autox:roles. Validate it via JWKS. This is per-request and strictly opt-in.offline_access. It rotates on every use, with reuse detection — always persist the newest one and discard the previous. Replaying an old (e.g. stolen) refresh token revokes the whole chain.RP-initiated logout is enabled. Redirect the user to the end_session_endpoint with an id_token_hint and, optionally, a registered post_logout_redirect_uri.
sub. Store only the claims you need in a server-side session (or an httpOnly, Secure cookie).autox:roles / autox:orgs.sub.resource=https://sso.autogrc.cloud/api.// auth.ts
import NextAuth from "next-auth";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
{
id: "autox",
name: "AutoX ID",
type: "oidc",
issuer: "https://sso.autogrc.cloud", // discovery is automatic
clientId: process.env.AUTOX_CLIENT_ID!,
clientSecret: process.env.AUTOX_CLIENT_SECRET!, // omit for a public client
authorization: { params: { scope: "openid profile email orgs roles offline_access" } },
// Auth.js performs PKCE + state + nonce for oidc providers by default.
},
],
callbacks: {
async jwt({ token, profile }) {
if (profile) {
token.sub = profile.sub;
token.roles = profile["autox:roles"] ?? [];
token.orgs = profile["autox:orgs"] ?? [];
}
return token;
},
async session({ session, token }) {
session.user.id = token.sub;
session.roles = token.roles;
session.orgs = token.orgs;
return session;
},
},
});
Register the redirect URI https://your-app.example.com/api/auth/callback/autox in the console.
import { Issuer, generators } from "openid-client";
const issuer = await Issuer.discover("https://sso.autogrc.cloud");
const client = new issuer.Client({
client_id: process.env.AUTOX_CLIENT_ID,
client_secret: process.env.AUTOX_CLIENT_SECRET, // omit + token_endpoint_auth_method:'none' for a public client
redirect_uris: ["https://your-app.example.com/callback"],
response_types: ["code"],
});
// login: stash these in the session, then redirect the user to `url`
const code_verifier = generators.codeVerifier();
const code_challenge = generators.codeChallenge(code_verifier);
const state = generators.state();
const nonce = generators.nonce();
const url = client.authorizationUrl({
scope: "openid profile email orgs roles",
code_challenge,
code_challenge_method: "S256",
state,
nonce,
});
// callback:
const params = client.callbackParams(req);
const tokenSet = await client.callback(
"https://your-app.example.com/callback",
params,
{ code_verifier, state, nonce },
);
const claims = tokenSet.claims(); // sub, email, name, autox:roles, autox:orgs, ...
Have the front end request resource=https://sso.autogrc.cloud/api so your API receives a JWT it can verify offline:
import jwt
from jwt import PyJWKClient
ISSUER = "https://sso.autogrc.cloud"
AUDIENCE = "https://sso.autogrc.cloud/api"
_jwks = PyJWKClient(ISSUER + "/jwks")
def verify_access_token(token: str) -> dict:
key = _jwks.get_signing_key_from_jwt(token).key
return jwt.decode(token, key, algorithms=["ES256"], audience=AUDIENCE, issuer=ISSUER)
def require_role(claims: dict, role: str) -> bool:
return role in claims.get("autox:roles", [])
The identity provider may be cold — on a free or scale-to-zero host it spins down when idle, and the first request after that wakes it, which can take tens of seconds. If you redirect a user straight to the authorization endpoint while the IdP is asleep, they hit a hang or a connection error *mid-redirect*, with nothing to look at. Warm it first, then redirect.
When a user lands on your app's entry page (or clicks "Sign in"):
<link rel="preconnect" href="https://sso.autogrc.cloud"> (and adns-prefetch) in that page's <head> so the connection is being established while the user reads the page.
GET https://sso.autogrc.cloud/health (it returns{"status":"ok"}) with a short timeout and a few retries/backoff, showing a brief "Connecting to sign-in…" state. Only navigate to the authorization endpoint once the probe returns 2xx.
https://sso.autogrc.cloud/.well-known/openid-configuration)during the warm-up so the redirect resolves instantly.
// Warm the IdP (it may be cold), confirm it's reachable, THEN redirect.
async function goToSignIn(authorizeUrl) {
const issuer = "https://sso.autogrc.cloud";
const deadline = Date.now() + 60_000; // give a cold start up to ~60s
while (Date.now() < deadline) {
try {
const r = await fetch(issuer + "/health", { cache: "no-store" });
if (r.ok) { window.location.href = authorizeUrl; return; }
} catch { /* still waking */ }
await new Promise((res) => setTimeout(res, 2000));
}
// Never came up — show a friendly "sign-in is temporarily unavailable" message.
}
This is a deployment characteristic, not a protocol requirement: once the IdP runs always-on (a paid/warm plan), the probe is essentially instant and you can keep it as a cheap health gate or drop it.
iss, aud (= your client_id), exp, and nonce. A reputable library does this for you — do not hand-roll token validation.aud.kid.resource=https://sso.autogrc.cloud/api.autox:roles into your database as the source of truth. Read it from the token each session — roles are managed centrally and change without touching your app.https://sso.autogrc.cloud/admin; you have client_id (+ secret if confidential) and the redirect_urihttps://sso.autogrc.cloudsubautox:roles / autox:orgsend_session_endpointresource=https://sso.autogrc.cloud/api JWTs via JWKSCan't find a value? It is in the discovery document (https://sso.autogrc.cloud/.well-known/openid-configuration), or ask the admin who registered your client.