You're building a new app with no login yet
Use this when you do not want to run sign-up, passwords or email verification yourself. People register or log in to your app with their STX account.
- 1Your app has no loginOne button: continue with your STX account
- 2They register or log inOn a page STX hosts, then back to your appEmail, Google, X or Apple
- 3Your app knows who they areTheir STX id, from a signed ID token
- 4Your app holds tokensAnd trades for them
What STX gives you
Section titled “What STX gives you”- STX hosts the sign-up and login page. Your app sends people there and gets them back; it never sees a password.
- A new person creates their STX account there, identity verification included. An existing member logs in.
- Four ways to sign up or log in: email and password, Google, X and Apple.
- Your app gets back who the member is, and tokens to act for them, using standard OAuth 2.0 and OpenID Connect.
Not your situation? See the OAuth overview. If your app’s login already runs on a service such as Auth0 or Clerk, see You use a login service like Auth0 or Clerk instead.
What the member sees
Section titled “What the member sees”- In your app they choose Continue with your STX account, or whatever you call the button.
- On the STX page they log in, or register if they have no STX account yet, with email and password, Google, X or Apple.
- STX shows your app’s name and logo and the access you asked for. They choose Allow.
- They are in your app, logged in, with their STX account connected.
A returning member who has already approved your app skips step 3. A new person whose account STX is still verifying does not reach step 3 yet; see Members still being verified.
Step by step
Section titled “Step by step”How it works underneath
Section titled “How it works underneath”It is the OAuth 2.0 authorization code flow with OpenID Connect added: ask for the openid scope and the token response also carries an ID token naming the member. Everything in Authorization code flow applies. Sign-in adds four things. $STX in the examples is the base URL of the exchange you are calling. Environments lists them.
1. Ask for openid and send a nonce
Section titled “1. Ask for openid and send a nonce”GET $STX/oauth/authorize ?response_type=code &client_id=<your client_id> &redirect_uri=<one you registered, exactly> &scope=openid%20profile.read%20balance.read &state=<opaque, unguessable, per request> &nonce=<opaque, unguessable, per request> &code_challenge=<the challenge> &code_challenge_method=S256Keep nonce on your server with state and the PKCE verifier. Two optional parameters shorten the member’s path:
| Parameter | Effect |
|---|---|
connection |
google, apple or x sends a member who is not signed in straight to that sign-in, skipping the STX login page. Where that sign-in is not enabled, the STX login page is shown as usual. |
login_hint |
The member’s email, if you know it. With connection=google it preselects that Google account. |
2. Read the ID token from the token response
Section titled “2. Read the ID token from the token response”The code exchange is unchanged. Because the grant includes openid, the response has one more field, id_token: a JWT signed with RS256, valid for 10 minutes.
{ "access_token": "stxapp_at_…", "refresh_token": "stxapp_rt_…", "id_token": "eyJhbG…", "token_type": "bearer", "expires_in": 3600, "scope": "openid profile.read balance.read"}Decoded, the ID token’s header and claims look like this. aud is your client_id, nonce is the one you sent, and iss is the STX host you called, shown here as <exchange>.
{ "alg": "RS256", "kid": "if5GofKAtl2gWmp-99w5d0J3qLaEGcHkcBA4qpzg7NU", "typ": "JWT" }{ "iss": "https://<exchange>", "aud": "stxapp_client_2s1p…", "sub": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "email": "member@example.com", "email_verified": true, "iat": 1791660409, "exp": 1791661009, "nonce": "QPHuFZ…"}| Claim | Value |
|---|---|
iss |
The STX host you called. |
sub |
The member’s STX id. Stable for the life of the account; key your user record on it. |
aud |
Your client_id. |
iat, exp |
Issued and expiry times, in seconds. |
nonce |
The nonce you sent. |
email |
The member’s email. |
email_verified |
Whether STX has verified that email. |
A refresh returns new access and refresh tokens and no ID token. You need the ID token once, at sign-in.
3. Verify it before you trust it
Section titled “3. Verify it before you trust it”Check all of these, or let an OpenID Connect library do it:
- the header’s
algisRS256; accept no other algorithm, whatever the token says; - the signature, against the key at
jwks_uriwhosekidmatches the header’s; issis the STX host you are configured for;audis yourclient_id;nonceis the one you stored for this sign-in;exphas not passed.
Then find or create your user by sub, start your own session, and store the STX tokens on your server against that user.
4. Discover the endpoints
Section titled “4. Discover the endpoints”GET /.well-known/openid-configuration lists the endpoints, the signing keys and the claims. Point your library at the STX host and it reads the rest. See Discovery.
UserInfo
Section titled “UserInfo”GET /oauth/userinfo returns the same sub, email and email_verified for a member access token that carries openid. Any other token gets 401 with {"error":"invalid_token"}.
curl -s "$STX/oauth/userinfo" -H "Authorization: Bearer $ACCESS_TOKEN"{ "sub": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "email": "member@example.com", "email_verified": true }What you need from STX
Section titled “What you need from STX”A registered app of type Web app, with your callback URL and the scopes you will request, openid among them. See Get demo access.
When sign-in does not complete
Section titled “When sign-in does not complete”The member is returned to your callback with error instead of code. Leave them signed out and let them try again.
error |
Meaning |
|---|---|
access_denied |
The member chose Deny, or cannot use STX right now. |
account_pending |
The member has not finished STX verification. Show a “finish verifying” message. See Members still being verified. |
With the TypeScript SDK
Section titled “With the TypeScript SDK”createConnect builds the request, exchanges the code and verifies the ID token. See With the TypeScript SDK.
Live example
Section titled “Live example”Playbook is a demo app built this way: it has no login of its own, and one button takes you to STX to register or log in. Try it at playbook.sportsxapp.com. It runs against the US demo exchange, so no real money is involved.

