# Sign in with STX

> Sign members in to your app with their STX account and connect it in the same step, with a verified ID token.

Source: https://docs.stxapp.io/sdks/python/sign-in-with-stx/

> **Availability.** Available on US demo now. Live access is coming soon in the US and Ontario. See [Environments](https://docs.stxapp.io/environments/).

Sign in with STX is OpenID Connect on top of [connecting members](/sdks/python/apps/): one round trip gives your server both who the member is (a verified ID token whose `sub` is their stable STX id) and their tokens. It suits a new app with no login of its own. The protocol is on [Sign in with your STX account](/oauth/sign-in-with-your-stx-account/).

It needs a confidential client (with a secret) whose scopes include `openid`, on an exchange that publishes `/.well-known/openid-configuration`. Elsewhere `start()` raises `STXNotAvailableError` (`connect`); see [What you can do](/sdks/python/capabilities/).

```python
from stx import oauth

connect = oauth.Connect(
    issuer="https://demo.stxapp.io",
    client_id=CLIENT_ID,
    client_secret=CLIENT_SECRET,
    redirect_uri="https://your.app/auth/stx/callback",
    scopes=["balance.read", "orders.read", "orders.write"],
)

@route("/auth/stx/start")
def start(request):
    started = connect.start(connection=request.args.get("connection"))  # "google", "apple", "x" or None
    request.session["stx_sign_in"] = started.saved().to_dict()
    return redirect(started.url)

@route("/auth/stx/callback")
def callback(request):
    saved = oauth.SavedSignIn.from_dict(request.session.pop("stx_sign_in"))
    done = connect.finish(request.url, saved)
    user = users.find_or_create(stx_sub=done.identity.sub, email=done.identity.email)
    tokens.set(user.id, done.tokens.to_stored())
    return redirect("/")
```

`start()` always asks for `openid`, adds a `nonce`, and returns the URL plus what to keep in the session (`state`, PKCE verifier, nonce). `finish()` checks the `state` first, exchanges the code, and verifies the ID token: its signature against the issuer's published keys (RS256 or ES256), the issuer, that it is for your app, its expiry and the nonce. The callback errors are the same typed exceptions as for [connecting members](/sdks/python/apps#when-the-member-does-not-connect); a bad ID token raises `oauth.STXOAuthException` with `error == "invalid_id_token"`.

Key your user record on `done.identity.sub`, not the email. `done.tokens` work like any member's tokens: store them and use `member_client` from an `OAuthClient` for the same app. `connect.refresh()`, `connect.disconnect(tokens)` (ends the connection at STX) and `connect.user_info(access_token, expected_sub=sub)` (the member's claims again) cover the rest.

`oauth.AsyncConnect` is the asyncio version, with the same methods to `await`.
