Backend Integration of OAuth
In Stytch’s B2B product there are two different versions of the OAuth authentication flow:
- Discovery Authentication: used for self-serve organization creation or login without Organization context
- 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:
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("'", "\""))
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>