Skip to content

SSO login cannot produce a User Management session #67

Description

@DJCrossman

Summary

An authorization code minted by /sso/authorize cannot be redeemed at POST /user_management/authenticate. The two endpoints use different stores: /sso/authorize inserts into ssoAuthorizations (src/workos/routes/sso.ts) while the authorization_code grant reads authCodes (src/workos/routes/auth.ts), which only /user_management/authorize writes. So an app that starts SSO with sso.getAuthorizationUrl and finishes it with AuthKit's callback gets invalid_grant.

POST /sso/token does redeem the code, but it returns a profile and an access token without creating a session, so it can't back session helpers like saveSession / withAuth.

Related, and possibly the same fix: no code path records a session with auth_method: "sso". AUTH_METHOD_SESSION_VALUES maps SSO: 'sso' in src/workos/helpers.ts, but no grant in src/workos/routes/auth.ts ever assigns authMethod = 'SSO'. The string appears only in sso.ts, inside emitAuthenticationEvent calls. Anything that gates on the session's auth method, such as hiding password management for federated users, therefore can't be exercised against the emulator.

Reproduction (v0.7.1)

# seed.yaml
organizations:
  - name: Acme
    domains:
      - domain: acme.test
        state: verified
users:
  - email: alice@acme.test
    password: test123
    email_verified: true
connections:
  - name: Acme SSO
    connection_type: GoogleOAuth
    organization: Acme
    domains: [acme.test]
    profiles:
      - email: alice@acme.test
workos-emulate --port 4100 --seed seed.yaml &

ORG=$(curl -s -H "Authorization: Bearer sk_test_default" \
  http://localhost:4100/organizations | jq -r '.data[0].id')

# follow the authorize redirect and pull the code out of it
curl -s -o /dev/null -w '%{redirect_url}\n' \
  "http://localhost:4100/sso/authorize?response_type=code&client_id=client_x&organization=$ORG&redirect_uri=http://localhost:3000/callback"
# http://localhost:3000/callback?code=sso_code_01M089ZB8KMCB9VFYPA2DNX7Z1

curl -s -w "\nHTTP %{http_code}\n" -X POST http://localhost:4100/user_management/authenticate \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"authorization_code","code":"sso_code_01M089ZB8KMCB9VFYPA2DNX7Z1","client_id":"client_x","client_secret":"sk_test_default"}'
{"error":"invalid_grant","error_description":"The code 'sso_code_01M089ZB8KMCB9VFYPA2DNX7Z1' has expired or is invalid."}
HTTP 400

Expected

The code is accepted, and the response carries a session whose auth_method is sso.

Note on the workaround

Routing the user through /user_management/authorize?login_hint=... instead does work, and mints a code the authenticate endpoint accepts. It isn't equivalent though: it puts a hosted sign in screen in front of the user, which defeats setups that deliberately send people straight to their IdP. The resulting session also comes back as oauth rather than sso.

Happy to send a PR if you can confirm which way you'd want it done.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions