Log in with the OAuth Discovery flow

Stytch's Discovery flow and OAuth product allow your end users to discover all of their Organizations upon authentication. Instead of logging in to each Organization separately, the end user can use the Discovery flow to log in once, see all of their memberships, and select an Organization to authenticate into.

In this guide, you'll learn how to use OAuth with the Discovery flow. By the end, you'll have:

  • Created a client-side OAuth URL to start the Discovery flow for login.
  • Returned a list of Discovered Organizations.
  • Authenticated the end user into the desired Organization via OAuth.

Before you start

In order to complete this guide, you'll need the following:

  • A Stytch B2B project. If you don't have one already, in the Dashboard, click on your existing project name in the top left corner of the Dashboard, click Create a new project, and then select B2B Authentication.
  • The project Test environment's project_id and secret from the API keys section. You'll need to pass these values into the Authorization request header for every Stytch API call.
  • An end user who is a Member of multiple Organizations.
  • An OAuth client (Google or Microsoft) that has authorized your project's redirect URI. Refer to this OAuth guide if you need instructions.

Step 1: Add or update redirect URLs in the Dashboard

As a security precaution, we require that you explicitly set URLs in the Redirect URLs section of the Dashboard. Stytch redirects the end user back to the redirect URLs during the OAuth flow.

By default, all new projects have redirect URLs set to http://localhost:3000/authenticate for the Test environment in the Dashboard.

If your project doesn't have a redirect URL or if you need a different URL, click + Create redirect URL and provide a URL for ALL or Discovery.

For more information on why this step is necessary, please check out the resource on URL validation.

Step 2: Copy your project's public_token

In the API Keys section of the Dashboard for your project, copy the public_token value under the Public tokens section. You'll need the public_token to complete the OAuth flow.

Stytch project public token

Step 3: Embed the Start OAuth Discovery URL into a UI element

Use the Start OAuth Discovery endpoint to create a client-side URL for Google or Microsoft. You need to pass in the public_token from Step 2.

https://test.stytch.com/v1/b2b/public/oauth/google/discovery/start?public_token={PUBLIC_TOKEN}

Embed the client-side URL into a UI element, like a login button, for the end user to click on. For the purposes of this guide, you can paste the client-side URL into the browser to simulate the OAuth discovery flow as the end user.

Step 4: Log in with the provider

Addressed to the client-side URL, the browser will navigate the end user to the provider's login screen (Google or Microsoft). Once the end user authenticates with their provider's credentials, they are then taken back to the redirect URL defined in Step 1 with additional values in the query parameters.

Your application should have a route and page that handles the redirect URL and its query parameters. The redirect URL will look like this:

{REDIRECT_URL}?stytch_token_type=discovery_oauth&token=KQCRsuXM_DVOsHpW4ktcTyJkb06iKwzM9SZkO1JgOGrj

Copy the token query param value.

Step 5: Authenticate the Discovery OAuth token

Call the Authenticate Dsicovery OAuth endpoint with:

  • The token query param's value from Step 6
  • An optional session_duration_minutes length to create a Session. If left empty, the default length for session_duration_minutes will be set to 60.
curl --request POST \
    --url https://test.stytch.com/v1/b2b/oauth/discovery/authenticate \
    -u '{PROJECT_ID}:{SECRET}' \
    -H 'Content-Type: application/json' \
    -d '{
        "discovery_oauth_token": "KQCRsuXM_DVOsHpW4ktcTyJkb06iKwzM9SZkO1JgOGrj",
        "session_duration_minutes": 60
    }'

After a successful API call:

  • discovered_organizations, an array of Organizations and membership data, will be returned in the response.
  • An intermediate_session_token will be returned in the response.

The response will look like this:

{
    "discovered_organizations": [
        {
            "member_authenticated": true,
            "membership": {
                "details": null,
                "member": {
                    "email_address": "example-user@exampleorg.com",
                    "is_breakglass": false,
                    "member_id": "member-test-3ef4e63c-9bfc-476b-bf4b-58189e445ae3",
                    "member_password_id": "",
                    "mfa_enrolled": false,
					"mfa_phone_number": "",
                    "name": "",
                    "oauth_registrations": [],
                    "organization_id": "organization-test-e87f5fcf-24e7-41c0-9611-c84faf6c4fe0",
                    "sso_registrations": [],
                    "status": "active",
                    "trusted_metadata": {},
                    "untrusted_metadata": {}
                },
                "type": "active_member"
            },
            "organization": {
                "allowed_auth_methods": [],
                "auth_methods": "ALL_ALLOWED",
                "email_allowed_domains": [
                    "exampleorg.com"
                ],
                "email_invites": "NOT_ALLOWED",
                "email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
                "organization_id": "organization-test-e87f5fcf-24e7-41c0-9611-c84faf6c4fe0",
                "organization_logo_url": "",
                "organization_name": "Org A",
                "organization_slug": "org-a",
                "sso_active_connections": [],
                "sso_default_connection_id": null,
                "sso_jit_provisioning": "NOT_ALLOWED",
                "sso_jit_provisioning_allowed_connections": [],
                "trusted_metadata": {}
            }
        },
        {
            "member_authenticated": true,
            "membership": {
                "details": null,
                "member": {
                    "email_address": "example-user@exampleorg.com",
                    "is_breakglass": false,
                    "member_id": "member-test-539f0cc9-1b39-4f2c-8481-6e190e20fc0a",
                    "member_password_id": "",
					"mfa_enrolled": false,
					"mfa_phone_number": "",
                    "name": "",
                    "oauth_registrations": [],
                    "organization_id": "organization-test-591034d2-a59a-4b01-814b-0a35709c02dc",
                    "sso_registrations": [],
                    "status": "invited",
                    "trusted_metadata": {},
                    "untrusted_metadata": {}
                },
                "type": "invited_member"
            },
            "organization": {
                "allowed_auth_methods": [],
                "auth_methods": "ALL_ALLOWED",
                "email_allowed_domains": [
                    "exampleorg.com"
                ],
                "email_invites": "RESTRICTED",
                "email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
                "organization_id": "organization-test-591034d2-a59a-4b01-814b-0a35709c02dc",
                "organization_logo_url": "",
                "organization_name": "Org B",
                "organization_slug": "org-b",
                "sso_active_connections": [],
                "sso_default_connection_id": null,
                "sso_jit_provisioning": "NOT_ALLOWED",
                "sso_jit_provisioning_allowed_connections": [],
                "trusted_metadata": {}
            }
        },
        {
            "member_authenticated": true,
            "membership": {
                "details": null,
                "member": {
                    "email_address": "example-user@exampleorg.com",
                    "is_breakglass": false,
                    "member_id": "member-test-71b6e5c7-0c09-4831-be5a-b0a85d9c3448",
                    "member_password_id": "",
					"mfa_enrolled": false,
					"mfa_phone_number": "",
                    "oauth_registrations": [],
                    "organization_id": "organization-test-c773858f-5686-4574-a7b7-1768fc6a3388",
                    "sso_registrations": [],
                    "status": "active",
                    "trusted_metadata": {},
                    "untrusted_metadata": {}
                },
                "type": "active_member"
            },
            "organization": {
                "allowed_auth_methods": [],
                "auth_methods": "ALL_ALLOWED",
                "email_allowed_domains": [
                    "exampleorg.com"
                ],
                "email_invites": "ALL_ALLOWED",
                "email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
                "organization_id": "organization-test-c773858f-5686-4574-a7b7-1768fc6a3388",
                "organization_logo_url": "",
                "organization_name": "Org C",
                "organization_slug": "org-c",
                "sso_active_connections": [],
                "sso_default_connection_id": null,
                "sso_jit_provisioning": "ALL_ALLOWED",
                "sso_jit_provisioning_allowed_connections": [],
                "trusted_metadata": {}
            },
        },
        {
            "member_authenticated": true,
            "membership": {
                "details": {
                    "domain": "exampleorg.com"
                },
                "member": null,
                "type": "eligible_to_join_by_email_domain"
            },
            "organization": {
                "allowed_auth_methods": [],
                "auth_methods": "ALL_ALLOWED",
                "email_allowed_domains": [
                    "exampleorg.com"
                ],
                "email_invites": "ALL_ALLOWED",
                "email_jit_provisioning": "RESTRICTED",
				"mfa_policy": "OPTIONAL",
                "organization_id": "organization-test-3a8eb793-089f-46e7-92b6-7074a7c2b515",
                "organization_logo_url": "",
                "organization_name": "Org D",
                "organization_slug": "org-d",
                "sso_active_connections": [],
                "sso_default_connection_id": null,
                "sso_jit_provisioning": "ALL_ALLOWED",
                "sso_jit_provisioning_allowed_connections": [],
                "trusted_metadata": {}
            }
        },
    ],
    "email_address": "example-user@exampleorg.com",
    "intermediate_session_token": "D4c1cFGX7jS9QH6o0reHjbCKPParZnGptcQsyRuAtJCW",
    "request_id": "request-id-test-e8d65fc3-08ca-4911-9e1e-2d5291833718",
    "status_code": 200
}

In the example response, the end user is an active Member in two different Organizations, Org A and Org C, an invited Member in another Organization, Org B, and eligible to join Org D.

From here, you have two options for completing the Discovery flow.

Step 6a: Call the Exchange Intermediate Session Token endpoint

To complete the Discovery flow and authenticate into an Organization, call the Exchange Intermediate Session Token endpoint and provide the following:

  • The intermediate_session_token from the response in Step 3.
  • The desired organization_id from the discovered_organizations list in Step 3.
  • The desired session_duration_minutes length.
curl --request POST \
    --url https://test.stytch.com/v1/b2b/discovery/intermediate_sessions/exchange \
    -u '{PROJECT_ID}:{SECRET}' \
    -H 'Content-Type: application/json' \
    -d '{
        "intermediate_session_token": "D4c1cFGX7jS9QH6o0reHjbCKPParZnGptcQsyRuAtJCW=",
        "organization_id": "organization-test-c773858f-5686-4574-a7b7-1768fc6a3388",
        "session_duration_minutes": 60
    }'

After a successful API call:

  • The end user will be authenticated as a Member of the Organization.
  • A Member Session will be returned in the response.
  • The Member will be updated to active if their status was pending or invited.

Step 6b: Call the Create an Organization via Discovery endpoint

Alternatively, to complete the Discovery flow and create a new Organization, call the Create an Organization via Discovery endpoint and provide the following:

  • The intermediate_session_token from the response in Step 3.
  • The organization_name and any other Organization attributes.
  • The desired session_duration_minutes length. If left empty, the default length for session_duration_minutes will be set to 60.
curl --request POST \
    --url https://test.stytch.com/v1/b2b/discovery/organizations \
    -u '{PROJECT_ID}:{SECRET}' \
    -H 'Content-Type: application/json' \
    -d '{
        "intermediate_session_token": "D4c1cFGX7jS9QH6o0reHjbCKPParZnGptcQsyRuAtJCW",
        "session_duration_minutes": 60,
        "organization_name": "Example Org Inc.",
        "organization_slug": "example-org"
    }'

After a successful API call:

  • An Organization will be created and returned in the response.
  • A Member will be created and returned in the response.
  • A Member Session will be returned in the response.
  • The end user will be authenticated as a Member of the Organization.

What's next

Build a user interface that allows end users to log in and sign up with OAuth in Step 3. You'll also need to build a page to handle the redirect back to your application in Step 4 after an end user authenticates with their provider.

Clone our B2B Next.js example app for helpful templates that can get you started quickly. Also check out our interactive B2B demo app to see the app in action.