/
Contact usSee pricingStart building

    About B2B Saas Authentication

    Introduction
    Stytch B2B Basics
    Integration Approaches
      Full-stack overview
      Frontend (pre-built UI)
      Frontend (headless)
      Backend
    Next.js
      Routing
      Authentication
      Sessions
    Migrations
      Overview
      Reconciling data models
      Migrating user data
      Additional migration considerations
      Zero-downtime deployment
      Defining external IDs for members
      Exporting from Stytch
    Custom Domains
      Overview

    Authentication

    Single Sign On
    • Resources

      • Overview
        External SSO Connections
        Standalone SSO
    • Integration Guides

      • Start here
        Backend integration guide
        Headless integration guide
        Pre-built UI integration guide
    OAuth
    • Resources

      • Overview
        Authentication flows
        Identity providers
        Google One Tap
        Provider setup
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
        Pre-built UI frontend integration
    Connected AppsBeta
      Setting up Connected Apps
      About Remote MCP Servers
    • Resources

      • Integrate with AI agents
        Integrate with a remote MCP server
    Sessions
    • Resources

      • Overview
        JWTs vs Session Tokens
        How to use Stytch JWTs
        Custom Claims
    • Integration Guides

      • Start here
        Backend integration
        Frontend integration
    Email OTP
      Overview
    Magic Links
    • Resources

      • Overview
        Email Security Scanner Protections
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
        Pre-built UI frontend integration
    Multi-Factor Authentication
    • Resources

      • Overview
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
        Pre-built UI frontend integration
    Passwords
      Overview
      Strength policies
    UI components
      Overview
      Implement the Discovery flow
      Implement the Organization flow
    DFP Protected Auth
      Overview
      Setting up DFP Protected Auth
      Handling challenges
    M2M Authentication
      Authenticate an M2M Client
      Rotate client secrets
      Import M2M Clients from Auth0

    Authorization & Provisioning

    RBAC
    • Resources

      • Overview
        Stytch Resources & Roles
        Role assignment
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
    SCIM
    • Resources

      • Overview
        Supported actions
    • Integration Guides

      • Using Okta
        Using Microsoft Entra
    Organizations
      Managing org settings
      JIT Provisioning

    Testing

    E2E testing
    Sandbox values
Get support on SlackVisit our developer forum

Contact us

B2B Saas Authentication

/

Guides

/

Authentication

/

OAuth

/

Integration Guides

/

Backend integration

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 prior to knowing the Organization context
  2. Organization-specific Authentication: used when you already know the Organization that the end user is trying to log into

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

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

1
Complete config steps

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

2
Configure 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>

3
Create 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()

4
Initiate 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>

Organization Login

If end users of your application login via a page that indicates which Organization they are trying to log into (e.g. <org-slug>.your-app.com or your-app.com/team/<org-slug>) you can offer organization login on that page.

The high level flow using a backend integration with Stytch is as follows:

Backend integration of organization-specific OAuth

1
Complete config steps

If you haven't done so already complete the steps in the OAuth Integration Guide Start Here, including creating an Organization.

2
Configure callback

Stytch will make a callback to the Login or Signup 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/authenticate you would add the following route to your application:

def authenticate() -> str:
  token_type = request.args["stytch_token_type"]
  if token_type == "oauth":
    resp = stytch_client.oauth.authenticate(oauth_token=request.arg["token"])
    if resp.status_code != 200:
      return "something went wrong authenticating token"
    else:
      return "unsupported authentication method"

  # member is successfully logged in
  member = resp.member
  session["stytch_session"] = resp.session_jwt
  return member.json()

3
Initiate OAuth

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

Using your Public Token (found in the Stytch Dashboard under API Keys) and either the slug or organization_id for Organization you want to log into, enter the following into your browser after replacing {provider} with “google” or “microsoft” depending on which OAuth provider you are testing:

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

This will automatically redirect your browser to the specified OAuth provider to start the flow.

4
(Optional) Build login page

In a real application, you’ll need a UI that will allow the end user to identify the Organization they wish to log into – and surfacing all allowed authentication methods for the Organization for the user to choose between.

For example, if your application offers Google and Microsoft OAuth and allows Organizations to restrict authentication to one of those methods, you might do something like the following to conditionally render the options to the user.

@app.route("/org/<string:slug>", methods=["GET"])
def org_index(slug: str):

  # Check for active member session, if present show logged in view
  # Otherwise show login screen

  resp = stytch_client.organizations.search(query=SearchQuery(operator="AND", operands=[{
    "filter_name": "organization_slugs",
    "filter_value": [slug]}
    ]))
  if resp.status_code != 200 or len(resp.organizations) == 0:
    return "Error fetching org"

  organization = resp.organizations[0]
  google_allowed = (
      organization.auth_methods == "ALL_ALLOWED" or
      "google_oauth" in organization.allowed_auth_methods
  )
  microsoft_allowed = (
    organization.auth_methods == "ALL_ALLOWED" or
    "microsoft_oauth" in organization.allowed_auth_methods
  )

  return render_template(
    "organizationLogin.html",
    public_token=STYTCH_PUBLIC_TOKEN,
    api_base=stytch_client.api_base.base_url,
    org_name=organization.organization_name,
    google_allowed=google_allowed,
    microsoft_allowed=microsoft_allowed
  )

That renders a template like:

<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>
      {{ organization.organization_name}} Organization
    </h1>
  </div>

  <button class="button" onclick="startOAuth('google')">
  Continue with Google
  </button>

  <button class="button" onclick="startOAuth('microsoft')">
  Continue with Microsoft
  </button>

  </div>
  <script>
    const public_token = "{{ public_token }}";
    const api_base = "{{ api_base }}";
    const slug = "{{ organization.organization_slug }}";
    function startOAuth(provider) {
    const url = `${api_base}/v1/b2b/public/oauth/${provider}/start?slug=${slug}&public_token=${public_token}`;
      // Redirect the browser to the constructed URL
      window.location.href = url;
    }
  </script>
<!-- Rest of your HTML file -->
</body>

Discovery Sign-Up or Login

1.

Complete config steps

2.

Configure callback and template for selecting organization

3.

Create routes for handling user selection

4.

Initiate OAuth

5.

(Optional) Build frontend for selecting OAuth login

Organization Login

1.

Complete config steps

2.

Configure callback

3.

Initiate OAuth

4.

(Optional) Build login page