Backend Integration of OAuth

In Stytch’s B2B product there are two different versions of the OAuth authentication flow:

  1. Discovery Authentication: used for self-serve organization creation or login without Organization context
  2. Organization-specific Authentication: used when you already know the Organization that the end user is trying to log into

This quickstart walks through how to offer OAuth for both scenarios.

OAuth Discovery Sign-Up or Login

The discovery flow is designed for situations where your end users are signing up or logging in from a central landing page, and have not specified which organization they are trying to access or are attempting to create a new Organization.

The sequence for how this flow works when using a backend integration approach is as follows:

Backend integration of discovery OAuth

1Complete config steps

If you haven't done so already complete the steps in the OAuth Quickstart Start Here

2Configure callback and template for selecting organization

Stytch will make a callback to the Discovery RedirectURL that you specified in the Stytch dashboard. Your application should handle checking the stytch_token_type for the callback, and call the appropriate authentication method to finish the login process.

If your RedirectURL was http://localhost:3000/discovery you would add the following route to your application:

@app.route("/discovery", methods=["GET"])
def discovery() -> str:
    token_type = request.args["stytch_token_type"]
    token = request.args["token"]
    if token_type != "discovery_oauth":
        return "Unsupported auth method"

    resp = stytch_client.oauth.discovery.authenticate(discovery_oauth_token=token)
    if resp.status_code != 200:
        return "Authentication error"

    # store IST as cookie or other mechanism for use in subsequent request to exchange
    session['ist'] = resp.intermediate_session_token
    orgs = []
    for discovered in resp.discovered_organizations:
        org = {
            "organization_id": discovered.organization.organization_id,
            "organization_name": discovered.organization.organization_name,
        }
        orgs.append(org)

    return render_template(
        'discoveredOrgs.html',
        discovered_organizations=orgs,
        email_address=resp.email_address
    )

Create a template that surfaces the available organizations to the end user as well as the option to create a new Organization.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Dashboard</title>
    <link rel="stylesheet" href="/static/css/styles.css">
</head>
<body>
    <div class="card">
    <div class="card-content">
        <h1>
            Discovered Organizations for {{ email_address }}
        </h1>
    </div>
    <p>Login to existing Organization or create a new one!</p>
    <div id="button-containers"></div>
    <div class="divider">
        <hr class="line" />
    </div>
        <button class="button" onclick="createOrg()"> Create New Organization </button>
    </div>
    <script>
        function selectOrg(organization_id) {
            window.location.href = `/login/${organization_id}`;
        }
        function createOrg() {
            window.location.href = `/create_org`;
        }
        const unparsedOrgs = "{{ discovered_organizations }}"
        const orgs = JSON.parse(unparsedOrgs.replaceAll("&#39;", "\""))
        function iterateOverOrgs() {
            document.getElementById('button-containers').innerHTML = orgs.map(org => (
            `<button class="button" onclick="selectOrg('${org.organization_id}')">
            ${org.organization_name}
            </button>`
            )).join('\n\n');
        }
        iterateOverOrgs();
    </script>
</body>
</html>

3Create routes for handling user selection

Create two routes to handle the options presented to the end user: logging into an existing Organization or creating a new Organization.

@app.route("/login/<string:organization_id>", methods=["GET"])
def login_to_org(organization_id):
    ist = session.get('ist')
    if not ist:
        return "No IST found"

    resp = stytch_client.discovery.intermediate_sessions.exchange(
        intermediate_session_token=ist,
        organization_id=organization_id
    )
    if resp.status_code != 200:
        return "Error logging into org"

    # Clear IST and set stytch session
    session.pop('ist', None)
    session['stytch_session'] = resp.session_token
    return member.json()

@app.route("/create_org", methods=["GET"])
def create_org() -> str:
    ist = session.get('ist')
    if not ist:
        return "No IST found"
    
    # Created org name and slug will be based on user's email
    # Can also prompt end user to provide these 
    resp = stytch_client.discovery.organizations.create(
        intermediate_session_token=ist,
        organization_slug='',
        organization_name=''
    )
    if resp.status_code != 200:
        return "Error creating org"

    # Clear IST and set stytch session
    session.pop('ist', None)
    session['stytch_session'] = resp.session_token
    return member.json()

4Initiate OAuth

Now that your application is ready to handle the authentication callback, you can test out an end-to-end authentication flow!

Enter the following into your browser – replacing {provider} with “google” or “microsoft” and replacing {public_token} with your Public Token (found in the Stytch Dashboard under API Keys).

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

This will automatically redirect your browser to the OAuth provider (Google or Microsoft) to start the flow.

5(Optional) Build frontend for selecting OAuth login

You can create a simple login UI that renders the authentication options you want to support for discovery and serve this from your index route.

<head>
    <title>Login</title>
    <link rel="stylesheet" href="{{ url_for('static', filename= 'css/styles.css') }}">
</head>
<body>
    <div class="card">
        <div class="card-content">
            <h1>Login to Find Your Orgs!</h1>
        </div>
        <button class="button" onclick="startDiscoveryOAuth('google')">
            Continue with Google
        </button>
        <button class="button" onclick="startDiscoveryOAuth('microsoft')">
            Continue with Microsoft
        </button>
    </div>
    <script>
        const public_token = "{{ public_token }}";
        const api_base = "{{ api_base }}"
        function startDiscoveryOAuth(provider) {
        const url = `${api_base}/v1/b2b/public/oauth/${provider}/discovery/start?public_token=${public_token}`;
            // Redirect the browser to the constructed URL
            window.location.href = url;
        }
    </script>
    <!-- Rest of your HTML file -->
</body>