# API reference
Source: https://stytch.com/docs/consumer-auth/api-reference-link
# Integration options
Source: https://stytch.com/docs/consumer-auth/build-auth/custom/integration-options
Use Stytch's SDKs or API to build your own custom auth flow.
Stytch is architected to be **embedded directly into your application**, offering you flexibility and control over your integration and creating a seamless and native experience for users.
There are three different high level approaches to integrating Stytch directly into your application:
1. **Backend Integration:** entirely server-side integration, where your backend calls Stytch's API through our backend SDK
2. **Headless Frontend Integration:** your client invokes Stytch's headless frontend SDKs, which handles things like client-side session management out of the box, while providing flexibility in UX and UI
3. **Pre-built UI Frontend Integration:** your client mounts the Stytch UI component on relevant routes, and the Stytch SDK handles the full login flow automatically
While we describe these as distinct integration options, most customers mix-and-match approaches to fit their needs. If you are using our pre-built UI for login, you might still leverage the headless methods for post-login changes (e.g. updating profile information) and if you are using our frontend SDKs, it is important to still do server-side session authentication checks with our backend SDKs for security reasons.
## Choosing an integration approach
Stytch's frontend SDKs are built on top of our backend SDKs, and you can always change integration approaches in the future without any disruption to your application. Picking which approach to use to start largely comes down to how much you want to offload to Stytch vs how much flexibility and control you want.
The below are key differences to consider when deciding which approach is right for your use case and needs:
## Pre-built UI integration
Stytch UI is the fastest way to ship login and signup. Your frontend renders pre-built UI components, while the SDK handles authentication and session creation.
At a high level, the flow looks like this:
1. The end user attempts to log into your application with Stytch's pre-built UI components configured on your frontend.
2. The frontend SDK handles UI events and calls the Stytch API with the authentication data.
3. The Stytch API processes the request and returns a response.
4. Your frontend handles the response via callbacks or redirect URLs, and may relay data to your backend.
5. The frontend SDK manages session tokens in browser cookies or mobile storage.
### When to use headless methods
Even with Stytch UI, some flows require headless SDK calls (or backend integration), such as completing a magic link redirect or handling advanced session logic. The headless integration guide covers these shared considerations.
### Unsupported auth methods
Pre-built UI supports Email Magic Links, OAuth, OTPs, Passwords, and more. Auth methods like TOTP and WebAuthn require headless SDK or backend integration.
## Frontend JS SDKs
## Mobile SDKs
## API & Backend SDKs
Read the [backend implementation guide](/docs/consumer-auth/build-auth/custom/via-api) for more in-depth implementation details and considerations.
## Still unsure what to choose?
If you have additional questions about our different integration options, please feel free to reach out to us in our [community Slack](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw) or at [support@stytch.com](mailto:support@stytch.com) for further guidance.
# Via backend SDK or API
Source: https://stytch.com/docs/consumer-auth/build-auth/custom/via-api
Examples and considerations when doing a server-side integration with Stytch.
Our [frontend SDK](./via-sdk) provide automatic session management, prebuilt login or signup, multi-step flow handling, and more. They can also be used alongside our backend API or SDKs.
## Quickstart
This quickstart guides you through building a login or signup flow. Select a framework:
End-to-end consumer examples
Stytch Node.js library
API & SDK references
* Have your `project_id` and `secret_key` from your Stytch project & environment.
* Add `http://localhost:3000/authenticate` as a [redirect URL](https://stytch.com/dashboard/redirect-urls) in your Dashboard.
1. Install the SDK:
```bash npm icon="terminal" theme={null}
npm install stytch express express-session
```
2. Then add your Stytch API keys to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
# Use your Project's 'live' or any available 'test' credentials
```
1. Initialize the Stytch client with your environment variables.
2. Create a `/login` route that takes in a user's email address and initiates the login or signup flow.
```javascript title="POST '/login'" lines theme={null}
const express = require('express');
const session = require('express-session');
const stytch = require('stytch');
const app = express();
app.use(express.json());
app.use(session({
resave: true,
saveUninitialized: false,
secret: 'session-signing-secret',
}));
const stytchClient = new stytch.Client({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
app.post('/login', async (req, res) => {
const params = {
email: req.body.email,
login_magic_link_url: "http://localhost:3000/authenticate",
signup_magic_link_url: "http://localhost:3000/authenticate",
};
const resp = await stytchClient.magicLinks.email.loginOrCreate(params);
res.json(resp);
});
```
1. Stytch calls your authenticate redirect URL with a token to complete the flow.
2. Add an `/authenticate` route to mint a session:
```javascript title="GET '/authenticate'" lines theme={null}
app.get('/authenticate', async (req, res) => {
const token = req.query.token;
const resp = await stytchClient.magicLinks.authenticate({
token,
session_duration_minutes: 60,
});
req.session.session_jwt = resp.session_jwt;
res.redirect('/dashboard');
});
```
1. Create a helper method that authenticates a session.
2. Use the method to gate any protected route.
```javascript title="GET '/dashboard'" lines theme={null}
app.get('/dashboard', async (req, res) => {
const sessionJWT = req.session.session_jwt;
if (!sessionJWT) {
res.send("Log in to view this page");
return;
}
const resp = await stytchClient.sessions.authenticate({
session_jwt: sessionJWT,
});
if (resp.status_code !== 200) {
req.session.session_jwt = undefined;
res.send("Log in to view this page");
return;
}
res.send("You're logged in.");
});
```
```javascript expandable lines theme={null}
const express = require('express');
const session = require('express-session');
const stytch = require('stytch');
const app = express();
app.use(express.json());
app.use(session({
resave: true,
saveUninitialized: false,
secret: 'session-signing-secret',
}));
const stytchClient = new stytch.Client({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
app.post('/login', async (req, res) => {
const params = {
email: req.body.email,
login_magic_link_url: "http://localhost:3000/authenticate",
signup_magic_link_url: "http://localhost:3000/authenticate",
};
const resp = await stytchClient.magicLinks.email.loginOrCreate(params);
res.json(resp);
});
app.get('/authenticate', async (req, res) => {
const token = req.query.token;
const resp = await stytchClient.magicLinks.authenticate({
token,
session_duration_minutes: 60,
});
req.session.session_jwt = resp.session_jwt;
res.redirect('/dashboard');
});
app.get('/dashboard', async (req, res) => {
const sessionJWT = req.session.session_jwt;
if (!sessionJWT) {
res.send("Log in to view this page");
return;
}
const resp = await stytchClient.sessions.authenticate({
session_jwt: sessionJWT,
});
if (resp.status_code !== 200) {
req.session.session_jwt = undefined;
res.send("Log in to view this page");
return;
}
res.send("You're logged in.");
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click the link to finish the login or signup flow.
End-to-end consumer examples
Stytch Go library
API & SDK references
Have your `project_id` and `secret_key` from your Stytch project & environment.
Use the Go standard library (`net/http`, `encoding/json`) or your preferred HTTP client.
```go lines theme={null}
payload := map[string]any{
"email": email,
"login_magic_link_url": "http://localhost:3000/authenticate",
"signup_magic_link_url": "http://localhost:3000/authenticate",
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://test.stytch.com/v1/magic_links/email/login_or_create", bytes.NewReader(body))
req.SetBasicAuth(os.Getenv("STYTCH_PROJECT_ID"), os.Getenv("STYTCH_SECRET"))
req.Header.Set("Content-Type", "application/json")
```
```go lines theme={null}
payload := map[string]any{
"token": token,
"session_duration_minutes": 60,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://test.stytch.com/v1/magic_links/authenticate", bytes.NewReader(body))
req.SetBasicAuth(os.Getenv("STYTCH_PROJECT_ID"), os.Getenv("STYTCH_SECRET"))
req.Header.Set("Content-Type", "application/json")
```
```go lines theme={null}
payload := map[string]any{"session_jwt": sessionJWT}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://test.stytch.com/v1/sessions/authenticate", bytes.NewReader(body))
req.SetBasicAuth(os.Getenv("STYTCH_PROJECT_ID"), os.Getenv("STYTCH_SECRET"))
req.Header.Set("Content-Type", "application/json")
```
```go expandable lines theme={null}
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
func main() {
http.HandleFunc("/login", sendLoginHandler)
http.HandleFunc("/authenticate", authenticateHandler)
http.HandleFunc("/dashboard", dashboardHandler)
http.ListenAndServe(":3000", nil)
}
// Implement sendLoginHandler/authenticateHandler/dashboardHandler using the
// requests shown above.
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click the link to finish the login or signup flow.
End-to-end consumer examples
Stytch Python library
API & SDK references
Have your `project_id` and `secret_key` from your Stytch project & environment.
```bash pip icon="terminal" theme={null}
pip install requests
```
```python lines theme={null}
import requests
resp = requests.post(
"https://test.stytch.com/v1/magic_links/email/login_or_create",
auth=(project_id, secret),
json={
"email": email,
"login_magic_link_url": "http://localhost:3000/authenticate",
"signup_magic_link_url": "http://localhost:3000/authenticate",
},
)
```
```python lines theme={null}
resp = requests.post(
"https://test.stytch.com/v1/magic_links/authenticate",
auth=(project_id, secret),
json={"token": token, "session_duration_minutes": 60},
)
session_jwt = resp.json()["session_jwt"]
```
```python lines theme={null}
resp = requests.post(
"https://test.stytch.com/v1/sessions/authenticate",
auth=(project_id, secret),
json={"session_jwt": session_jwt},
)
```
```python expandable lines theme={null}
# Combine the login, authenticate, and session check requests in your
# framework of choice (Flask, Django, FastAPI, etc.).
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click the link to finish the login or signup flow.
End-to-end consumer examples
Stytch Ruby gem
API & SDK references
Have your `project_id` and `secret_key` from your Stytch project & environment.
Use Ruby's built-in `net/http` or your preferred HTTP client.
```ruby lines theme={null}
uri = URI("https://test.stytch.com/v1/magic_links/email/login_or_create")
req = Net::HTTP::Post.new(uri)
req.basic_auth(project_id, secret)
req["Content-Type"] = "application/json"
req.body = {
email: email,
login_magic_link_url: "http://localhost:3000/authenticate",
signup_magic_link_url: "http://localhost:3000/authenticate"
}.to_json
```
```ruby lines theme={null}
uri = URI("https://test.stytch.com/v1/magic_links/authenticate")
req = Net::HTTP::Post.new(uri)
req.basic_auth(project_id, secret)
req["Content-Type"] = "application/json"
req.body = { token: token, session_duration_minutes: 60 }.to_json
```
```ruby lines theme={null}
uri = URI("https://test.stytch.com/v1/sessions/authenticate")
req = Net::HTTP::Post.new(uri)
req.basic_auth(project_id, secret)
req["Content-Type"] = "application/json"
req.body = { session_jwt: session_jwt }.to_json
```
```ruby expandable lines theme={null}
# Combine the login, authenticate, and session check requests in your
# Rails controllers and routes.
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click the link to finish the login or signup flow.
End-to-end consumer examples
Stytch Java library
API & SDK references
Have your `project_id` and `secret_key` from your Stytch project & environment.
Use `java.net.http.HttpClient` or your preferred HTTP client library.
```java theme={null}
var payload = """
{
"email": "%s",
"login_magic_link_url": "http://localhost:3000/authenticate",
"signup_magic_link_url": "http://localhost:3000/authenticate"
}
""".formatted(email);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://test.stytch.com/v1/magic_links/email/login_or_create"))
.header("Content-Type", "application/json")
.header("Authorization", basicAuth(projectId, secret))
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
```
```java theme={null}
var payload = """
{
"token": "%s",
"session_duration_minutes": 60
}
""".formatted(token);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://test.stytch.com/v1/magic_links/authenticate"))
.header("Content-Type", "application/json")
.header("Authorization", basicAuth(projectId, secret))
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
```
```java theme={null}
var payload = """
{
"session_jwt": "%s"
}
""".formatted(sessionJwt);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://test.stytch.com/v1/sessions/authenticate"))
.header("Content-Type", "application/json")
.header("Authorization", basicAuth(projectId, secret))
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
```
```java theme={null}
import java.nio.charset.StandardCharsets;
import java.util.Base64;
private static String basicAuth(String projectId, String secret) {
var token = projectId + ":" + secret;
return "Basic " + Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
}
// Wire the requests above into your controllers/routes and store sessionJwt
// in a server-side session or signed cookie.
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click the link to finish the login or signup flow.
## Next steps
* [Handle a session](/docs/consumer-auth/manage-sessions/overview)
# Via headless SDK
Source: https://stytch.com/docs/consumer-auth/build-auth/custom/via-sdk
Our frontend SDKs can be utilized headlessly for developing and managing your auth client-side.
Our frontend SDKs can be utilized headlessly for developing and managing your auth client-side. With a robust set of headless features, our frontend SDKs not only allow you to create custom UIs and screens but also provide you with controls to customize auth flows. This guide covers the fundamental aspects and key considerations when implementing our frontend SDKs headlessly.
Refer to the [SDK reference](/docs/api-reference/consumer/frontend-sdks/vanilla-js/prebuilt-ui/login/configuration) page for full documentation. You can also explore the [example apps](/docs/get-started/example-apps) to get hands-on with the code.
## Overview
At a high-level, implementing Stytch headlessly on the client-side involves the following steps:
1. The end user attempts to log into your application with your custom-built UI.
2. Your frontend handles the UI events, collects all the necessary authentication data, and then utilizes Stytch's frontend SDK methods to call the Stytch API to perform an auth-related operation.
3. Stytch API processes the request and returns a response with pertinent data.
4. Your frontend handles the response as needed, which may involve calling the Stytch API again, updating your UI, or relaying the data to your backend.
5. Once the end user successfully authenticates, Stytch's frontend SDK automatically manages the storage of session tokens using browser cookies or mobile storage.
Using the frontend SDK headlessly enables you to build your own custom UI, customize auth flows, and handle user and session management — directly on the frontend. Our frontend SDK supports most features of the Stytch platform; however, there are certain operations that can only be done with a backend implementation.
## Code examples
Here are some example code snippets that demonstrate the general idea of implementing Stytch headlessly.
### Authentication flows
```jsx theme={null}
// Start the authentication flow
import { useStytch, useStytchUser, useStytchSession } from '@stytch/nextjs';
const Login = () => {
const stytchClient = useStytch();
const sendEmailMagicLink = async () => {
await stytchClient.magicLinks.email.send('sandbox@stytch.com', {
login_magic_link_url: "http://localhost:3000/authenticate",
login_expiration_minutes: 5,
});
};
return ;
};
...
```
```jsx theme={null}
// Complete the authentication flow
const AuthenticateEML = () => {
const stytchClient = useStytch();
const router = useRouter();
useEffect(() => {
const token = router?.query?.token?.toString();
stytchClient.magicLinks.authenticate(token, {
session_duration_minutes: 60,
});
}, [router, stytchClient]);
...
```
### Session management
```jsx theme={null}
// Validate session for protected pages and resources
const isAuthenticated = () => {
const { session, fromCache } = useStytchSession();
const router = useRouter();
useEffect(() => {
if (session && !fromCache) {
// there's an active session
router.replace("/profile");
}
}, [session, fromCache, router]);
...
```
```jsx theme={null}
// Revoke session
const LogOut = () => {
const stytchClient = useStytch();
const revokeSession = async () => {
await stytchClient.session.revoke();
router.push("/");
};
return
...
```
### User management
```jsx theme={null}
// Manage user records
const UpdateProfile = (user) => {
const stytchClient = useStytch();
const updateUser = async (e) => {
e.preventDefault();
const data = new FormData(e.target);
await stytchClient.user.update({
name: {
first_name: data.get('first-name'),
last_name: data.get('last-name'),
},
untrusted_metadata: {
profile_pic_url: data.get('profile-pic-url'),
}
});
};
return
...
```
## Considerations when using our frontend SDKs
### Session token storage
Our frontend JavaScript SDK automatically stores all minted Stytch `session_token` and `session_jwt` values in browser cookies or mobile storage. To strike the best balance between developer experience and security, by default, these cookies are **not** HttpOnly. If HttpOnly cookies are a requirement for you, you can enable them [following our documentation](/docs/api-reference/consumer/frontend-sdks/vanilla-js/resources/cookies-and-session-management#httponly-cookies). Alternatively, you can use a backend-only Stytch integration, where you can fully customize where and how session tokens are stored.
You can also allow session cookies to be available to all subdomains, e.g. api.yourapp.com, by configuring the StytchClientOptions. See [this documentation](/docs/api-reference/consumer/frontend-sdks/vanilla-js/resources/cookies-and-session-management) for all options for cookie settings.
### Hydrating sessions on the backend
Session authentication, specifically after the initial user login, should be implemented on the backend in order to protect your application's sensitive routes, data, or actions by authenticating each request's session token or JWT.
If you're authenticating users and minting sessions with our frontend SDKs, browser cookies will automatically be created for the session. As long as your backend shares a domain with your frontend, the `session_token` and `session_jwt` cookies will be included in the request headers on every call made to your backend. Your application can then use Stytch's backend API endpoints to authenticate the session before returning any protected resources to your frontend.
You can read more about [session hydration](/docs/api-reference/consumer/frontend-sdks/vanilla-js/resources/cookies-and-session-management#authenticating-sessions-on-your-backend) here.
### MFA requirements
For security purposes, our frontend SDK requires users to complete MFA (multi-factor authentication) before they can take certain sensitive user management actions. You can read more about this in our [Multi-factor authentication SDK resource](/docs/api-reference/consumer/frontend-sdks/vanilla-js/resources/multi-factor-authentication). When using a backend-only Stytch integration, you can fully control when to require MFA.
### Backend-restricted operations
Certain Stytch operations and features are only available as backend implementations. These include but are not limited to:
* [Setting custom claims on sessions](/docs/api-reference/consumer/api/sessions/authenticate-session)
* [Deleting Stytch Users](/docs/api-reference/consumer/api/users/delete/delete-user)
* [Searching for Stytch Users](/docs/api-reference/consumer/api/users/search-users)
* [Updating a User](/docs/api-reference/consumer/api/users/update-user)'s `trusted_metadata`
### PKCE (Proof Key for Code Exchange)
PKCE (Proof Key for Code Exchange) is a specification that makes certain login flows more secure by cryptographically validating that the flow starts and ends on the same device. When using our frontend JavaScript SDK, you can turn PKCE on or off for each Stytch product by simply flipping a toggle in the [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration) in the Stytch [Dashboard](https://stytch.com/dashboard).
For security purposes, PKCE is **required** when using any of our frontend mobile SDKs and when using a deep link as a redirect URL. When using a backend-only Stytch integration, you will be required to complete some additional implementation steps to enable PKCE, if desired.
### Account enumeration
Some of our frontend methods do **not** fully protect against [account enumeration](/docs/resources/policies/platform/account-enumeration) by design. To create the optimal UX, our frontend SDK sometimes returns errors that indicate whether or not an account associated with a given email address or phone number exists.
If account enumeration is a concern for your application, we recommend taking one of the following approaches:
1. Turn off certain frontend functionality like the Passwords product and the Send flow for OTP and Email Magic Links, which can be toggled via the [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration) in the Stytch Dashboard.
2. Enable opaque errors, which can be found under optional configuration on the [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration). Toggling this feature transforms any errors returning by the frontend SDK that reveal whether a given email or phone number is associated with an account. Additionally, turning on this feature prevents the Send methods from returning an error if a phone number or email is not found and disables the Passwords product entirely.
3. Use a backend-only Stytch integration.
### Toll fraud
When our SMS OTP or WhatsApp OTP products are enabled for your project via the [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration) in the Stytch Dashboard, anyone with access to your Stytch `public_token` can trigger SMS or WhatsApp messages to be sent on your project's behalf.
Unless you plan to use our [Device Fingerprinting Protected Auth feature](/docs/fraud-risk/device-fingerprinting/protected-auth), you may be vulnerable to toll fraud attacks – particularly if you're interested in enabling SMS to phone numbers outside of the US and Canada.
See our [toll fraud resource](/docs/resources/policies/messaging/toll-fraud) for additional information.
## What's next
Read our [backend implementation guide](/docs/consumer-auth/build-auth/custom/via-api).
If you have additional questions about our different integration options, please feel free to reach out to us in our [community Slack](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw) or at [support@stytch.com](mailto:support@stytch.com) for further guidance.
# Add login or signup
Source: https://stytch.com/docs/consumer-auth/build-auth/login-or-signup
Build a login or signup page.
## Quickstart
Select a framework to start:
Next.js code example
Try out the login flow
Next.js SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
1. In your Next.js app, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/nextjs --save
```
2. Then add your Stytch `public_token` to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN=$PUBLIC_TOKEN
```
1. Create a custom `Stytch` wrapper component that initializes the Stytch client.
```jsx components/stytch-provider.jsx icon=file-code lines theme={null}
'use client';
import { ReactNode } from 'react';
import { createStytchClient, StytchProvider } from '@stytch/nextjs';
const stytchClient = createStytchClient(
process.env.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN
);
export default function Stytch({ children }: { children: ReactNode }) {
return {children};
}
```
2. Wrap your application with `` to provide context to all child components:
```jsx app/layout.jsx icon=file-code lines {1,11} theme={null}
import Stytch from './components/stytch-provider';
export default function RootLayout({
children,
}: {
children: ReactNode
}) {
return (
{children}
);
}
```
1. Create a `` component.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/consumer/frontend-sdks/react/prebuilt-ui/login/configuration).
```jsx components/LoginOrSignupForm.jsx icon=file-code lines theme={null}
import { Products, StytchLogin } from '@stytch/nextjs';
const config = {
products: [Products.emailMagicLinks, Products.oauth],
oauthOptions: {
providers: [{ type: 'google' }],
},
sessionOptions: {
sessionDurationMinutes: 60,
},
};
export const LoginOrSignupForm = () => {
return ;
};
```
1. Add `` to your `/login` page.
```jsx app/login/page.jsx icon=file-code theme={null}
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useStytchSession } from '@stytch/nextjs';
import { LoginOrSignupForm } from "src/components/LoginOrSignupForm";
export default function Login() {
const { session, isInitialized } = useStytchSession();
const router = useRouter();
useEffect(() => {
if (session && isInitialized) {
router.replace("/home");
}
}, [session, isInitialized, router]);
if (!isInitialized || session) {
return
Loading...
;
}
return ;
}
```
2. If you're using separate routes, add `` to your `/authenticate` redirect route as well, e.g. `app/authenticate/page.jsx` or `pages/authenticate.jsx`.
At this point, you can retrieve [Session data](/docs/api-reference/consumer/api/sessions/session-object) and [User data](/docs/api-reference/consumer/api/users/get-user) via the SDK. For example, [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/nextjs/hooks/use-stytch-session) lets you easily check for an active session.
Next.js code example
Try out the login flow
Next.js SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
1. In your Next.js app, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/nextjs --save
```
2. Then add your Stytch `public_token` to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN=$PUBLIC_TOKEN
```
1. Initialize the Stytch client through `createStytchClient()`.
2. Pass the Stytch UI client to the `` component at the root of your application, making it accessible to all child components.
```jsx pages/_app.jsx icon=file-code lines theme={null}
// For Page Router setups
import { createStytchClient, StytchProvider } from '@stytch/nextjs';
const stytch = createStytchClient(process.env.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN!);
export default function MyApp({ Component, pageProps }: AppProps) {
return (
);
}
```
1. Create a `` component.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/consumer/frontend-sdks/react/prebuilt-ui/login/configuration).
```jsx components/LoginOrSignupForm.jsx icon=file-code lines theme={null}
import { Products, StytchLogin } from '@stytch/nextjs';
const config = {
products: [Products.emailMagicLinks, Products.oauth],
oauthOptions: {
providers: [{ type: 'google' }],
},
sessionOptions: {
sessionDurationMinutes: 60,
},
};
export const LoginOrSignupForm = () => {
return ;
};
```
1. Add `` to your `/login` page.
```jsx app/login/page.jsx icon=file-code theme={null}
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useStytchSession } from '@stytch/nextjs';
import { LoginOrSignupForm } from "src/components/LoginOrSignupForm";
export default function Login() {
const { session, isInitialized } = useStytchSession();
const router = useRouter();
useEffect(() => {
if (session && isInitialized) {
router.replace("/home");
}
}, [session, isInitialized, router]);
if (!isInitialized || session) {
return
Loading...
;
}
return ;
}
```
2. If you're using separate routes, add `` to your `/authenticate` redirect route as well, e.g. `app/authenticate/page.jsx` or `pages/authenticate.jsx`.
At this point, you can retrieve [Session data](/docs/api-reference/consumer/api/sessions/session-object) and [User data](/docs/api-reference/consumer/api/users/get-user) via the SDK. For example, [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/nextjs/hooks/use-stytch-session) lets you easily check for an active session.
React code example
Try out the login flow
React SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
In your React app, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/react --save
```
1. Initialize the Stytch UI client through `createStytchClient()`.
2. Pass the Stytch UI client to the `` component at the root of your application, making it accessible to all child components.
```jsx lines theme={null}
import { createStytchClient, StytchProvider } from '@stytch/react';
const stytch = createStytchClient($YOUR_PUBLIC_KEY);
// Add the component at the root of your application
export function App() {
return (
{/* Your app code */}
);
}
```
1. Create a `` component.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/consumer/frontend-sdks/react/prebuilt-ui/login/configuration).
```jsx lines theme={null}
import { Products, StytchLogin } from '@stytch/react';
const config = {
products: [Products.emailMagicLinks, Products.oauth],
oauthOptions: {
providers: [{ type: 'google' }],
},
sessionOptions: {
sessionDurationMinutes: 60,
},
};
const LoginOrSignup = () => {
return ;
};
```
1. Add `` to your `/login` page:
```jsx Login.jsx icon=file-code lines theme={null}
import { useStytchSession } from "@stytch/react";
import { Navigate } from "react-router";
import { LoginOrSignup } from "./LoginOrSignup";
export const Login = () => {
const { session } = useStytchSession();
if (session) {
return ;
}
return ;
};
```
2. If you're using separate routes, add `` to your `/authenticate` redirect route as well, e.g. `Authenticate.jsx`.
At this point, you can retrieve [Session data](/docs/api-reference/consumer/api/sessions/session-object) and [User data](/docs/api-reference/consumer/api/users/get-user) via the SDK. For example, [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/nextjs/hooks/use-stytch-session) lets you easily check for an active session.
Vanilla JS code example
Try out the login flow
Vanilla JS SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
In your project, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/vanilla-js --save
```
1. Initialize the Stytch UI client through `createStytchClient()`.
2. Export the initialized client as `stytch` so it can be used across your app.
```js js/stytch-client.js icon=file-code lines theme={null}
import { createStytchClient } from "@stytch/vanilla-js";
export const stytch = createStytchClient($YOUR_PUBLIC_KEY);
```
1. Create a `login.js` file to register and initialize the custom element.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/consumer/frontend-sdks/vanilla-js/prebuilt-ui/login/configuration).
```js js/login.js icon=file-code lines theme={null}
import { Products, StytchUI } from '@stytch/vanilla-js';
import { stytch } from "./stytch-client.js";
// Register the custom element
customElements.define('stytch-ui', StytchUI);
// Pass in the StytchClient and config
const login = document.getElementById('stytch-ui');
login.render({
client: stytch,
config: {
products: [Products.emailMagicLinks, Products.oauth],
oauthOptions: {
providers: [{ type: 'google' }],
},
sessionOptions: {
sessionDurationMinutes: 60,
},
},
});
```
1. Add `` on your `/login` page:
```html login.html {5,6} icon=file-code lines theme={null}
...
```
2. If you're using separate routes, add `` on your `/authenticate` redirect route as well, e.g. `authenticate.html`.
At this point, you can retrieve [Session data](/docs/api-reference/consumer/api/sessions/session-object) and [User data](/docs/api-reference/consumer/api/users/get-user) via the SDK. For example, [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/react/hooks/use-stytch-session) lets you retrieve the current session and can be used to determine if a user is already logged in.
## Next steps
* Authentication methods
* Session management
# Stytch UI
Source: https://stytch.com/docs/consumer-auth/build-auth/overview
How to build a log in and sign up flow for your app using the Stytch UI components included in the frontend SDK.
Stytch UI gives you pre-built login and signup components backed by the Consumer Frontend SDKs. Configure auth methods, customize styling, and ship a production-ready login experience quickly.
## Get started
Step-by-step guide to add Stytch UI to your app.
Theme and customize the pre-built UI components.
Full UI config reference for Stytch Login.
Explore sample implementations.
Stytch SDKs and UI are hosted by your application.
## What's next
See [Integration options](/docs/consumer-auth/build-auth/custom/integration-options) to compare pre-built UI, headless SDK, and backend integration approaches.
# Customize Stytch UI
Source: https://stytch.com/docs/consumer-auth/build-auth/theming
How to customize Stytch UI to fit the look and feel of your app
Many elements of Stytch UI can be customized. You can use our pre-built, bring your own shadcn/ui themes, or write your own theme. You can also replace any string appearing in Stytch UI. Select your package to see all customization options.
Here are some examples of customized Stytch UI components:
[From tweakcn](https://tweakcn.com/editor/theme?theme=neo-brutalism)
[From tweakcn](https://tweakcn.com/editor/theme?theme=bubblegum)
[From tweakcn](https://tweakcn.com/editor/theme?theme=amethyst-haze)
# Consumer data model
Source: https://stytch.com/docs/consumer-auth/data-model
Users own and control their identity and how they authenticate.
## Key objects
An end user's account in your application.
## Overview
In consumer applications, users are the sole owners of their accounts and are in control of their identity and how they authenticate. Stytch's consumer data model is designed to support this ownership model and is designed for simplicity, working well for applications that don't require complex identity management features like SSO or SCIM.
Users are identified by either email or phone number:
```mermaid theme={null}
---
config:
title: Data model for consumer auth
layout: elk
elk:
mergeEdges: true
---
erDiagram
p["Consumer project"]
u1[User] {
string id "user-583..."
string name "Ada"
string email "ada@stytch.com"
}
u2[User] {
string id "user-9a1..."
string name "Baker"
string email "baker@gmail.com"
}
u3[User] {
string id "user-30w..."
string name "Baker"
string phone "+10000000000"
}
p ||--|| u1 : ""
p ||--|| u2 : ""
p ||--|| u3 : ""
```
### Unique identifiers
* `user_id` is unique to each specific user
* `email` or `phone_number`
***
## Need teams or organizations?
Use [multi-tenant auth](/docs/multi-tenant-auth/data-model) to support org-based account ownership and tenancy.
# Check for an active session
Source: https://stytch.com/docs/consumer-auth/manage-sessions/check-active-session
Use the SDK to control your application's UI or navigation based on whether a user has an active session.
## Use case
* **Gate your app's UI or navigation:** Restrict access to application features that require authentication.
* **Route authenticated users:** Direct users with active sessions to your main application experience.
* **Redirect unauthenticated users:** Send users without sessions to your login or signup flow.
## Retrieve a user session
Select a framework:
Follow our [Next.js quickstart](/docs/consumer-auth/build-auth/login-or-signup#next-js-app) for an example routing a user based on whether there's an active session.
We recommend placing this logic close to the entry point to each page you want to control in your application.
You can use the [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/nextjs/hooks/use-stytch-session) hook to retrieve a user's session data and listen for changes:
```jsx lines theme={null}
import { useStytchSession } from '@stytch/nextjs';
import { useRouter } from 'next/navigation';
// Protected page example that routes a user to login if there's no active session
export default function Home() {
const { session, isInitialized } = useStytchSession();
const router = useRouter();
useEffect(() => {
// Route the user to login if there's no session
if (!session && isInitialized) {
router.replace("/login");
}
}, [session, isInitialized, router]);
if (!isInitialized || !session) {
// Show loading while redirecting or if the SDK hasn't finished initializing
return
Loading...
;
}
// If there's an active session, show the home content
return
Welcome! You have an active session.
;
}
```
The [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/nextjs/hooks/use-stytch-session) hook returns the values:
The active [User Session](/docs/api-reference/consumer/api/sessions/session-object) object. `null` if there's no active session.
Whether or not the SDK has finished initializing. Ensure this is `true` before checking for an active session.
Whether the SDK is returning cached session data based on if the SDK has completed an [Authenticate Session](/docs/api-reference/consumer/frontend-sdks/nextjs/methods/sessions/authenticate-session) call or not.
For most cases, you can rely on checking the `session` value as long as `isInitialized` is `true`.
The frontend SDK utilizes [stale-while-revalidate (SWR)](https://web.dev/case-studies/ads-case-study-stale-while-revalidate#what%27s-it-mean) for faster time-to-interactivity and UX:
1. The page first loads.
2. Stytch's SDK checks and returns cached User and Session data to your application.
3. The SDK refreshes User and Session data in background session authentication while your application starts.
For use cases where you'd prefer to wait until the session data finishes updating instead of relying on cached data, you can verify that `fromCache` is `false` before checking the session value.
Follow our [React quickstart](/docs/consumer-auth/build-auth/login-or-signup#react) for an example routing a user based on whether there's an active session.
We recommend placing this logic close to the entry point to each page you want to control in your application.
You can use the [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/react/hooks/use-stytch-session) hook to retrieve a user's session data and listen for changes:
```jsx lines theme={null}
import { useStytchSession } from '@stytch/react';
import { Navigate } from "react-router";
// Protected page example that routes a user to login if there's no active session
export const Home = () => {
const { session } = useStytchSession();
// Route users to login if there's no active session
if (!session) {
return ;
}
return
Welcome! You have an active session.
;
};
```
The [`useStytchSession()`](/docs/api-reference/consumer/frontend-sdks/react/hooks/use-stytch-session) hook returns the values:
The active [User Session](/docs/api-reference/consumer/api/sessions/session-object) object. `null` if there's no active session.
Whether the SDK has completed initialization.
Whether the SDK is returning cached session data based on if the SDK has completed an [Authenticate Session](/docs/api-reference/consumer/frontend-sdks/react/methods/sessions/authenticate-session) call or not.
For most cases, you can rely on checking the `session` value.
The frontend SDK utilizes [stale-while-revalidate (SWR)](https://web.dev/case-studies/ads-case-study-stale-while-revalidate#what%27s-it-mean) for faster time-to-interactivity and UX:
1. The page first loads.
2. Stytch's SDK checks and returns cached User and Session data to your application.
3. The SDK refreshes User and Session data in background session authentication while your application starts.
For use cases where you'd prefer to wait until the session data finishes updating instead of relying on cached data, you can verify that `fromCache` is `false` before checking the session value.
Follow our [Vanilla JS quickstart](/docs/consumer-auth/build-auth/login-or-signup#vanilla-js) for an example routing a user based on whether there's an active session.
We recommend placing this logic close to the entry point to each page you want to control in your application.
You can use the [`session.getSync()`](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/sessions/get-session-sync) method to retrieve a user's session data:
```javascript lines theme={null}
import { stytch } from "./stytch-client.js"; // or where Stytch is initialized
function init() {
const session = stytch.session.getSync();
if (!session) {
// Redirect to login if there's no active session
window.location.href = "/login";
return;
}
// If there's an active session, show the protected content
document.getElementById('app').innerHTML = '
Welcome! You have an active session.
';
}
// Initialize when the page loads
document.addEventListener("DOMContentLoaded", init);
```
The `session.getSync()` method returns the value:
The active [User Session](/docs/api-reference/consumer/api/sessions/session-object) object. `null` if there's no active session.
You can use the [`session.onChange()`](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/sessions/on-change-session) method to listen for changes in the user's session:
```javascript lines theme={null}
// Listen for session changes
const unsubscribeSession = stytch.session.onChange((newSession) => {
session = newSession;
// Trigger any relevant actions in your application based on the session update
handleSessionChange();
});
window.addEventListener('beforeunload', () => {
unsubscribeSession && unsubscribeSession();
});
```
* The method takes in a callback that gets called whenever the Session object changes.
* It returns an unsubscribe method for you to call when you no longer want to listen for changes.
# Cookies & sessions
Source: https://stytch.com/docs/consumer-auth/manage-sessions/cookies
Options for cookie configuration options and HttpOnly cookies.
## Overview
The User Session is automatically saved in two cookies:
Contains the opaque `session_token` returned from the API.
Contains the `session_jwt` returned from the API.
For example, if the SDK is loaded on `https://app.example.com`, the cookies will be set as:
* `document.cookie = "stytch_session=secret-session; domain=app.example.com; path=/; SameSite=Lax; Secure; max-age=whenever-it-expires"`
* `document.cookie = "stytch_session_jwt=eyJhxxx.xxx.xxx; domain=app.example.com; path=/; SameSite=Lax; Secure; max-age=whenever-it-expires"`
The cookie will not be marked as secure if the SDK is loaded on `localhost`.
***
## Configure cookie options
The Stytch SDK allows you to configure the following directives and values for cookie management via `cookieOptions`:
```json expandable theme={null}
{
"cookieOptions": {
/**
* The name of the cookie containing the opaque Stytch session token.
* Defaults to `stytch_session`.
*/
"opaqueTokenCookieName": "string",
/**
* The name of the cookie containing the opaque Stytch session token.
* Defaults to `stytch_session_jwt`.
*/
"jwtCookieName": "string",
/**
* What HTTP paths the cookies should be available on.
* Equal to configuring the `;path=${}` param in the set-cookie directive.
* Defaults to unset.
*/
"path": "string",
/**
* What domain the cookies should be available on.
* Equal to configuring the `;domain=${}` param in the set-cookie directive.
* Requires setting availableToSubdomains to have any effect.
* Defaults to unset.
*/
"domain": "string",
/**
* Whether to make the cookies available to subdomains.
* When true, equivalent to configuring the `;domain=${window.location.host}` directive.
* When false, equivalent to leaving the directive unset.
* Defaults to false.
*/
"availableToSubdomains": "boolean"
}
}
```
To override default values, set the desired values in the `cookieOptions` configuration and pass it as part of the optional second parameter when initializing the Stytch SDK client:
```javascript lines theme={null}
import { StytchUIClient } from "@stytch/vanilla-js"
const STYTCH_PUBLIC_TOKEN = 'YOUR_PUBLIC_TOKEN';
const stytchOptions = {
cookieOptions: {
opaqueTokenCookieName: "my_stytch_session",
jwtCookieName: "my_stytch_session_jwt",
path: "/",
availableToSubdomains: true,
domain: "app.example.com",
}
}
const stytchClient = new StytchUIClient(STYTCH_PUBLIC_TOKEN, stytchOptions)
```
`cookieOptions` do not apply to HttpOnly cookies.
***
## HttpOnly cookies
For added security, you can enable the use of HttpOnly cookies for your project. This moves cookie management from the client to Stytch's backend.
When HttpOnly cookies are used, Stytch's backend sets the `Domain` of the cookie to the parent of your custom domain. Cookies are accessible to the `Domain` specified and all its subdomains, for example:
* If your custom domain is `login.example.com`, the `Domain` will be set to `example.com`, making the cookies accessible to `example.com`, `app.example.com`, `login.example.com`, etc.
* If your custom domain is `login.app.example.com`, the `Domain` will be set to `app.example.com`, making the cookies accessible to `app.example.com` and its subdomains, but not `example.com`.
By default, cookies managed by the SDK running in the client are not marked as `HttpOnly`.
***
### Enable HttpOnly cookies
Important things to consider before enabling HttpOnly cookies:
#### Requirements
1. Using HttpOnly cookies **requires** a custom domain to be set up for your Stytch project to allow Stytch's backend to set cookies in a first-party context for your domain.
2. HttpOnly Cookies are not supported in localhost environments.
#### Considerations
1. Using HttpOnly cookies means the cookies are no longer accessible via JavaScript in the browser. This includes the `session_token` and `session_jwt`. These tokens are omitted from any response bodies to the client.
2. `session.getTokens()` will no longer return session tokens when an active session exists.
3. `session.updateSession()` will only work if there's **not** an existing session token set using an HttpOnly cookie. Make sure `cookieOptions` are configured to match the domain and cookie names used by Stytch's backend if you use this method.
4. If the HttpOnly cookies setting is set to `enforced`, Stytch will only accept browser requests for your project that use your custom domain.
5. `cookieOptions` does not apply to HttpOnly cookies.
***
Configure a [custom domain](https://stytch.com/dashboard/custom-domains) for your project.
Set the HttpOnly cookies setting to `Enabled` under the [Frontend SDK settings](https://stytch.com/dashboard/sdk-configuration?env=live).
In your application, set the `customBaseUrl` option in your Stytch SDK client to your custom domain to tell the SDK to use your custom domain for requests.
We recommend initially changing this setting in a staging environment before enabling it in production.
Verify that your application behaves as expected. The `stytch_session` and `stytch_session_jwt` cookies should now be marked as `HttpOnly`.
After confirming your implementation works as expected, you can set the HttpOnly cookies setting to `Enforced` to require all browser requests to use your custom domain.
This will block all browser SDK requests that do not use your custom domain.
***
### Disable HttpOnly cookies
Stop using HttpOnly cookies by removing the `customBaseUrl` setting from your Stytch client configuration.
This will not remove existing HttpOnly cookies from the user's browser, which the SDK will be unable to access.
To avoid disruptions to existing sessions, have your backend replace HttpOnly cookies with non-HttpOnly cookies.
# Hydrating sessions
Source: https://stytch.com/docs/consumer-auth/manage-sessions/hydrate-session
Hydrate the frontend session state from the backend.
## Use case
* **Backend-initiated session changes:** Ensure your frontend session state stays in sync when sessions are created or updated on the backend.
***
## Session hydration
Provide both the active `session_token` and `session_jwt` to the `session.updateSession()` method to prime the frontend with a valid set of tokens.
Authenticate the session using the `session.authenticate()` method to validate the updated session tokens.
```javascript lines theme={null}
export const hydrateSession = () => {
stytch.session.updateSession({
session_token: 'ACTIVE_SESSION_TOKEN',
session_jwt: 'ACTIVE_SESSION_JWT',
});
stytch.session.authenticate();
};
```
When `HttpOnly` cookies are enabled for your Stytch project, the Sessions Update method only works if there isn't an existing session token set in an `HttpOnly` cookie.
* If `HttpOnly` cookies are enabled, make sure your SDK client's `cookieOptions` are configured to match the domain and cookie names used by Stytch's backend.
* Alternatively, you can override the session cookies via response headers from your backend. Be sure to still make a frontend authenticate session call.
# JWTs vs. session tokens
Source: https://stytch.com/docs/consumer-auth/manage-sessions/jwts-and-tokens
Both the `session_jwt` and `session_token` represent the underlying Stytch Session. Your application can use either one or both to balance performance and security.
Stytch allows you to switch between using JWTs and session tokens as needed.
***
## Using session JWTs
A session JWT is a signed token that can be locally verified.
* Contains standard claims and information about the underlying session.
* Expires after 5 minutes, but can be refreshed via API for the duration of the underlying session.
* Will locally validate during the 5-minute period, even if the underlying session is revoked.
### Reasons to use JWTs
* **Reduce latency with JWTs** Speed up authorization requests without a call to the database.
* **Add custom claims** Encode additional information into your Stytch session JWTs.
* **Authorize external applications** Leverage identity information and authorization granted through the JWT.
***
## Using session tokens
A session token is an opaque token that must be verified via an API call.
* Does **not** contain information about the user or the underlying session.
* Invalid once the underlying Session object is revoked or expires.
### Reasons to use session tokens
* **Added security layer** Ensure every call is authorized at that exact moment.
* **Storage limitations** Access to user-side storage is limited and can only store small values.
* **Limit session exposure** If you don't want to expose session data or metadata in user storage (e.g. authentication factors or timestamps).
# Using Stytch's session layer
Source: https://stytch.com/docs/consumer-auth/manage-sessions/overview
Sessions provide a way to uniquely identify users on your application and maintain useful state information, such as detecting whether a user is logged in or not, determining access privileges, personalizing experiences, and building user insights.
When a user session starts.
## What is a session?
A session is a unique identifier for a user's authenticated state within your application. It ties together the user's identity, authentication factors, and session metadata.
Once a user successfully authenticates, Stytch mints a Session object for the user—represented by two tokens:
### Session represented by tokens
Represents an authenticated session for a specific user.
An opaque token that's verified via API call.
A signed token that can be locally verified.
Session tokens should be stored client-side (usually a browser cookie) and authenticated on each request.
***
## Session management
Stytch's SDKs provide a session management layer that abstracts token rotation, session validation, and more:
Control your application behavior based on whether a user has an active session.
Validate session tokens or JWTs before any action that requires authorization.
Hydrate the frontend session state from the backend.
Encode additional information into your session JWTs using custom claims.
Options for cookie configuration and HttpOnly cookies.
### Session lifecycle
Session duration and extending the lifetime of a session.
Immediately invalidate the session token, effectively logging the user out.
# Authenticate a session
Source: https://stytch.com/docs/consumer-auth/manage-sessions/validate-session
How to validate session tokens and JWTs on your backend.
## Use case
* **Protect server-side endpoints:** Validate either the `session_token` or `session_jwt` on requests to your backend before proceeding with the request.
* **Backend authorization checks:** Enforce RBAC permissions and perform server-side user authorization checks before proceeding with the request.
***
## JWT vs. session token validation
Since `session_jwt` and `session_token` are stored in the site's cookies, the browser will include these cookies in the request headers to your backend if they share a domain.
Select between validating a JWT or session token:
Using JSON Web Tokens (JWT) over opaque session tokens **allows you to validate the token locally entirely within your backend service**, which is significantly faster than communicating with Stytch's API on every user session validation check.
### Reasons to use JWTs
* **Minimize latency:** JWTs can be locally validated to authenticate sessions without an API call (until it expires).
* **Authorize actions outside your app:** JWTs contain standard claims and information about the User Session.
***
### Using the Stytch SDK
Upon initialization, the Stytch SDK client will automatically retrieve and cache your JSON Web Key Set (JWKS) for use in local JWT validation.
Persist the SDK client instead of reinitializing it between requests so your JWKS is only fetched once.
Handles the local JWT authentication and remote session authentication fallback.
**Recommended**
Always communicates with the Stytch API to authenticate a session.
```javascript Node.js icon="node-js" lines theme={null}
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.use(authenticateStytchSession);
const client = new stytch.Client({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
const authenticateStytchSession = (req, res, next) => {
return client.sessions.authenticateJwt({ session_jwt: req.cookies['stytch_session_jwt'] })
.then(session => {
req.stytchSession = session;
return next();
})
.catch(next)
};
```
`authenticateJwt()` does not return the full [User object](/docs/api-reference/consumer/api/users/user-object) when validated locally. If you need data from the User object to complete additional authorization checks or any other additional action, use [custom claims](/docs/consumer-auth/manage-sessions/custom-claims) to add it to the JWT itself.
***
### Using custom backend JWT validation
Stytch JWTs are compatible with other standard JWT validation libraries. You'll need to:
* Implement logic to fall back to Stytch's [Authenticate Session](/docs/api-reference/consumer/api/sessions/authenticate-session) endpoint when the JWT is expired. You can refer to the [Authenticate JWT](/docs/api-reference/consumer/api/sessions/authenticate-jwt) method for what that logic might look like.
* Supply your validation library the Stytch [Get JWKS](/docs/api-reference/consumer/api/sessions/get-jwks) endpoint for [JWKS rotation](#json-web-key-set-jwks-rotation). Otherwise, your application should decide which JWKS to use for validation by inspecting the JWT and JWKS `kid` value.
- [jose](https://www.npmjs.com/package/jose) (Node.js)
- [jwt](https://pkg.go.dev/github.com/golang-jwt/jwt) (Go)
- [Hasura](https://hasura.io/docs/2.0/auth/authentication/jwt/)
- [NGINX](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-jwt-authentication/)
- [Apollo](https://www.apollographql.com/docs/router/configuration/authn-jwt/)
- [php-jwt](https://packagist.org/packages/firebase/php-jwt)
We strongly recommend using our [backend SDKs](/docs/api-reference/consumer/api/sdks) (available in Python, Node, Ruby, Go, and Java) for JWT validation. Our libraries handle the logic to authenticate JWTs locally and automatically fall back to the Stytch API when the JWT is expired.
***
### JSON Web Key Set (JWKS) rotation
Stytch automatically rotates your JWKS every 6 months for security purposes. Newly minted JWTs will be signed using the new key set. **Both key sets** will be returned by the [Get JWKS](/docs/api-reference/consumer/api/sessions/get-jwks) endpoint for a 1 month grace period.
* \~1 month before your Project's old JWKS expires, a new JWKS will be automatically generated.
* When this happens, there will be a 5 minute period where some JWTs will be signed by the old JWKS and some by the new, due to the JWT set lifetime.
* Match the `kid` value of the JWT and JWKS to determine the correct JWKS to use for validation.
* Most JWT libraries have a mechanism to handle this automatically; if not, you may see errors like `"kid" invalid, unable to lookup correct key` (depending on the library and version you use).
Stytch's [backend SDKs](/docs/api-reference/consumer/api/sdks) handle JWKS rotation for you automatically.
Session tokens are considered opaque because they don't contain any information about the underlying User Session.
### Reasons to use session tokens
* **Strict security requirements:** Session tokens cannot be locally validated. You must always make a Stytch API call to authenticate the token, which guarantees that the token is valid at that point in time. This provides stricter guarantees than JWTs, which have a 5-minute window where the underlying User Session may have been revoked before the JWT was refreshed.
***
### Using the Stytch SDK
Always communicates with the Stytch API to authenticate a session.
```python lines theme={null}
def upgrade_user_to_coordinator():
try:
resp = client.sessions.authenticate(
session_token=session['stytch_session'],
custom_claims={
"role": "coordinator",
"read-scope": ["appointments", "deliveries"],
"write-scope": ["calendar"]
}
)
if resp.status != 200:
return handle_error(resp.status)
session['stytch_session'] = resp.session_token
return
except Exception as e:
return handle_exception(e)
```
# Consumer authentication
Source: https://stytch.com/docs/consumer-auth/overview
Use Stytch Consumer API & SDKs to build auth tailored for consumer-based apps.
Quickstart to add login or signup to your application.
Sample apps showcasing how to authenticate users and manage sessions.
See reference documentation for the Stytch Consumer API and SDKs.
## Flexible, secure platform
Add provider-based logins like Google, Apple, and Github.
Send passcodes via SMS, email, or WhatsApp.
One-click, passwordless login through a magic link.
Add passwordless login using biometrics or PINs.
Native biometrics support for mobile apps.
Add a traditional password-based login or signup.
Add 2-factor authentication for more security.
Prevent fraud and abuse with device intelligence.
Alert users when a new device login is detected.
## AI agent authentication
Enable AI agents to securely connect to your application.
Enable your application to become an OAuth and OIDC Authorization server.
# Modeling your app
Source: https://stytch.com/docs/get-started/data-models
Multi-tenancy vs. consumer (single-tenancy) data models.
## Overview
You must select between **multi-tenant** or **consumer** data models when creating your Stytch project. This both defines who "owns" end user data and sets which SDKs and APIs your project has access to.
Common for B2B SaaS where users exist as members to one or more organization.
*Uses B2B API & SDKs.*
Single-tenant model where users exist as individual users **without** organizations.
*Uses Consumer API & SDKs.*
## Model diagram
## Why this matters
Different application use cases have different auth needs. For example, the Consumer API is designed for simplicity and works well for apps that don't require complex identity management features like SSO or SCIM—which are more commonly needed for B2B SaaS apps. Unique identifiers for end users are also slightly differentiated:
* In multi-tenant auth, a member is identified by their email.
* In consumer auth, a user is identified by either email or phone number.
## Need a hybrid model?
If you have a mix of business customers and individual end users, **we recommend using multi-tenant auth** and creating an organization behind the scene for each individual user “tenant” in order to allow these single-player accounts to seamlessly upgrade and add potential collaborators in the future.
# Example apps
Source: https://stytch.com/docs/get-started/example-apps
Working sample apps demonstrating how to implement Stytch Auth.
## Repository
Our [GitHub monorepo](https://github.com/stytchauth/stytch-all-examples) contains various example apps that demonstrate how to use our frontend and backend SDKs. You can use these as a starting point for your own app or as a proof-of-concept:
View the README on how to get started.
***
## What's included
The monorepo contains both frontend and backend examples for Stytch's B2B and Consumer APIs.
### Frontend examples
Examples for using the pre-built Stytch UI or headless SDKs:
### Backend examples
Backend examples for using Stytch's Consumer and B2B APIs:
# API reference
Source: https://stytch.com/docs/get-started/for-developers/api-reference-link
# LLM instructions
Source: https://stytch.com/docs/get-started/for-developers/llm-instructions
Instructions for using LLMs and AI coding tools with Stytch documentation.
Use these docs with LLMs and AI coding tools to get faster answers, generate code, and stay aligned with Stytch best practices. This page covers the built-in options on each doc page, plus the shared `llms.txt` files and the on-site AI bot.
## Context menu
Every page has a dropdown in the top right. Use it to:
* **Copy page**: Copy a Markdown version of the current page to your clipboard (ideal for pasting into ChatGPT, Claude, or other LLMs).
* **View as Markdown**: Open the page as plain text so you can copy sections or save them locally.
* **Open in ChatGPT / Claude**: Send the page to your chosen LLM with one click.
This is the fastest way to share the exact page you are reading with an LLM, without manual copying or formatting.
## Assistant
Use the built-in assistant to ask questions in-context while you browse:
* Ask for explanations of concepts on the current page.
* Request examples (e.g., “show a minimal redirect URL example”).
* Clarify how multiple docs pages fit together.
## Use `llms.txt` and `llms-full.txt`
If you want a broader context for LLMs and coding tools, use the site-wide LLM exports:
* [`llms.txt`](https://stytch.com/docs/llms.txt): A concise summary to provide high-level context.
* [`llms-full.txt`](https://stytch.com/docs/llms-full.txt): A larger, more comprehensive reference.
You can load either file directly into an editor or attach it to your AI tool of choice. For example:
* Paste into ChatGPT or Claude to prime the conversation.
* Add to Cursor as documentation context.
* Use as a reference in your own prompts or internal documentation.
# AI agents & Connected Apps
Source: https://stytch.com/docs/get-started/guides/ai-agents-and-apps
Make your app agent-ready with OAuth 2.0, MCP server integration, and enterprise-grade security for AI interactions.
## Agent-ready authentication
As AI agents gain the ability to access applications, Stytch Connected Apps provides the infrastructure to securely authenticate and authorize agent interactions—without rebuilding your auth system.
* **OAuth 2.0 & OIDC compliant**: Turn your app into an authorization server that AI agents and third-party apps can securely connect to
* **MCP server ready**: Native support for the Model Context Protocol, enabling Claude, ChatGPT, and other AI agents to access your app's resources
* **Works with existing auth**: Add agent authentication to your current auth stack with no migration required
* **Granular consent management**: Users control exactly what data and actions agents can access, with org-level visibility for IT admins
* **Token lifecycle management**: Issue, validate, refresh, and revoke access tokens with full control over expiration and scopes
* **Enterprise controls**: Org-wide allowlists, audit logs, and one-click revocation for compliance and security teams
## Core capabilities
Use Stytch's pre-built UI components or headless SDK to create an OAuth-compliant authorization endpoint.
Connect AI agents to your app via the Model Context Protocol with dynamic registration and scoped access.
Define granular permissions based on real actions and resources in your app.
Configure first-party, third-party, confidential, and public clients with appropriate security controls.
Issue access tokens, refresh tokens, and ID tokens with built-in validation and revocation.
View connected apps, authorized scopes, and revoke access at the user or organization level.
## Use cases
Deploy MCP servers on Cloudflare Workers with Stytch handling authentication and authorization.
Build MCP servers on Vercel's edge runtime with secure token management.
Authenticate command-line tools using the Authorization Code Flow with PKCE for public clients.
Allow external developers to build apps that integrate with your platform through OAuth.
## Start building
Learn the fundamentals of OAuth, OIDC, and how Connected Apps works.
Complete API documentation for token exchange, consent management, and client configuration.
# Authentication and authorization
Source: https://stytch.com/docs/get-started/guides/authentication
Build complete authentication and authorization flows with Stytch's developer-friendly APIs and SDKs.
## Built with developers in mind
Unlike building auth in-house or using basic libraries, Stytch offers a complete solution that prioritizes the developer experience.
* **Flexible integration**: Choose [pre-built UI components](/docs/multi-tenant-auth/build-auth/overview), [headless SDKs](/docs/multi-tenant-auth/build-auth/custom/via-sdk), or [direct API integration](/docs/multi-tenant-auth/build-auth/custom/via-api)
* **No infrastructure to maintain**: We handle scaling, security updates, and uptime
* **Faster time to launch**: Weeks or months of engineering time saved on implementation
* **Enterprise sales ready**: SSO and SCIM integrations that enterprise customers require, with security and compliance built-in
* **Secure by default**: Passwordless options, breach detection, and fraud prevention built-in
* **99.999% uptime SLA**: Enterprise customers get guaranteed uptime with redundancy across multiple regions
* **Built-in observability**: Real-time analytics, audit logs, and webhooks for monitoring auth events
* **Future-proof**: New auth methods and security features added continuously, including [Agent-ready auth](/docs/get-started/guides/ai-agents-and-apps)
* **Expert support**: Direct access to our team for implementation help and troubleshooting
## Authentication methods
Stytch offers both [Consumer Authentication](/docs/consumer-auth/overview) for consumer-facing apps and [Multi-Tenant Authentication](/docs/multi-tenant-auth/overview) for B2B SaaS platforms that need Enterprise-ready features like SSO, SCIM, RBAC and organization management. Support any login method your users expect, with SDKs and APIs that make integration straightforward:
Pre-configured integrations with major identity providers like Google, Apple, GitHub, Microsoft, and more.
Passwordless email authentication with one-click login. Includes smart link detection for enterprise email security.
Send verification codes via SMS, email, or WhatsApp. Built-in retry logic and rate limiting.
Modern passwordless auth using biometrics or security keys. Full passkey lifecycle management.
Traditional password authentication with breach detection, strength requirements, and secure storage.
Native mobile biometric authentication for iOS and Android with seamless session management (B2C API only).
### Enterprise methods
Pre-built SAML integrations with Okta, Azure AD, Google Workspace, and custom IdPs.
OpenID Connect authentication with automatic discovery and JIT provisioning.
Automatic user and group sync from workforce identity providers.
## Authorization & access control
Authentication is just the beginning. Stytch provides the tools to manage what users can do:
Define custom roles and permissions with flexible RBAC. Assign roles automatically via SCIM or SSO, or let admins manage them.
Multi-tenant architecture with organization isolation, member management, and org-specific auth settings.
Flexible session controls with customizable timeouts, JWT issuance, and cross-device sync.
Pre-built UI components for organization admins to manage members, roles, and auth settings.
## Start building
Get your first auth flow working in under 10 minutes with our guided quickstart.
Comprehensive API and SDK documentation with interactive examples and code snippets.
# Enterprise ready
Source: https://stytch.com/docs/get-started/guides/enterprise-ready
Ship enterprise features your customers expect—SSO, SCIM, RBAC, and org management—without months of development.
## Ready to go enterprise features
Building enterprise-grade authentication can take months. Stytch gives you production-ready SSO, SCIM, and organization management out of the box.
* **SSO**: Pre-built SAML and OIDC integrations with Okta, Azure AD, Google Workspace, and custom IdPs
* **SCIM**: Out-of-the-box user provisioning and deprovisioning from workforce IdPs
* **RBAC**: Flexible role-based access control with automatic role assignment via SSO and SCIM
* **JIT provisioning**: Automatically provision users on first login based on email domain, SSO connection, or SCIM group
* **Embeddable admin portal**: Pre-built UI components let your customers manage their own SSO, SCIM, and member settings
* **Multi-tenant by design**: Organizations and Members data model built for B2B SaaS from the ground up
* **Built-in redundancy**: SMS and email provider failover ensures reliable delivery of authentication messages
Enterprise single sign-on with built-in integrations for Okta, Azure AD, Google Workspace, and custom IdPs.
Automatic user and group sync from workforce identity providers with out-of-the-box SCIM 2.0 support.
Define custom roles and permissions. Assign roles automatically via SSO, SCIM, or email domain.
Drop-in UI components for SSO, SCIM, and member management that your customers can self-service.
Automatically provision members on first login based on email domain, SSO connection, or SCIM group.
SMS and email provider redundancy ensures reliable delivery of OTPs and magic links.
## Organization management
Stytch's multi-tenant architecture is built around Organizations and Members—giving you complete control over how your customers authenticate:
Organizations represent your customers, Members represent their users—built for B2B SaaS from day one.
Let each organization choose their own authentication methods—SSO-only, passwordless, or a mix.
Invite members via email or let them self-provision via email domain or SSO connection.
Control auth settings, provisioning rules, and member management at the organization level.
## Start building
Talk to our team about enterprise features and pricing for your organization.
Complete API documentation for SSO, SCIM, RBAC, and organization management.
# Fraud & risk prevention
Source: https://stytch.com/docs/get-started/guides/fraud-and-risk
Stop bots, prevent fraud, and protect your app with Device Fingerprinting and Intelligent Rate Limiting.
## Intelligent fraud prevention
Unlike basic WAF or CAPTCHA solutions, Stytch Device Fingerprinting provides intelligent fraud prevention that protects your app while maintaining a frictionless end-user experience.
* **Integrate in minutes**: Add a simple JavaScript snippet to immediately start monitoring vulnerabilities
* **99.99% accurate bot detection**: The world's most accurate device fingerprinting, leveraging standard and proprietary signals
* **Invisible to real users**: No clicking on stop signs or solving puzzles—only add friction to suspicious traffic
* **\<100ms latency**: Frictionless UX with more than 3x lower latency than competing solutions
* **Intelligent rate limiting**: Granular device, browser, and network fingerprints go beyond IP address detection
* **Tamper-resistant**: Client-side encryption prevents reverse engineering and signal spoofing
* **AI agent detection**: Monitor, allow, or restrict AI-driven activity like OpenAI's Operator and Anthropic's Computer Use
## Core capabilities
Stytch Fraud & Risk provides comprehensive fraud prevention through device intelligence, decisioning, and enforcement:
Stable device identifiers that remain consistent across incognito browsing, VPNs, and IP changes.
Clear action recommendations (ALLOW, BLOCK, CHALLENGE) with explainable warning flags.
Rate limiting by device, browser, and network—not just IP addresses.
Automatic fraud checks on every authentication request with configurable actions.
Tailor decisioning logic for your application's specific needs.
Detect disposable emails, typos, and suspicious email patterns.
Coming soon
## Fraud-proof auth flows
Seamlessly integrate fraud prevention into your authentication flows:
Automatically protect sign-ups and logins from account takeover attacks.
Recognize devices so users only need MFA when logging in from a new device.
Ensure delivery of email magic links even with enterprise security services.
Alert users when a new device login is detected for added security.
## Start building
Integrate device fingerprinting directly into your app with our API.
Common fraud prevention patterns and implementation guides.
# Migration & deployment
Source: https://stytch.com/docs/get-started/guides/migration-and-deployment
Zero-downtime migration paths, hands-on developer support, and tools to help you seamlessly switch to Stytch.
## Migrate with confidence
Switching auth providers doesn't have to be painful. Stytch provides tested migration paths, comprehensive tooling, and hands-on support to help you move from any authentication setup—whether you're coming from Auth0, Cognito, Firebase, or a homegrown solution.
* **Zero-downtime migration**: Keep users logged in during the transition with session migration from OIDC-compliant providers
* **Flexible migration paths**: Choose between dual-write with backfill, export and import, or gradual rollout strategies for your user data
* **Password hash support**: Migrate existing password hashes without forcing users to reset—supports bcrypt, scrypt, Argon2, and more
* **SSO connection migration**: Proxy existing SAML connections to Stytch without requiring customers to reconfigure their IdPs
* **Preserve user IDs**: Use external IDs or trusted metadata to maintain your existing user identifiers
* **Hands-on support**: Our team helps plan and execute migrations—from architecture review to go-live
## Migration strategies
Mint Stytch sessions from Auth0, Cognito, or other OIDC JWTs to keep users logged in during cutover.
Set up dual-writes to Stytch before running a backfill for seamless, zero-downtime data migration.
Map your existing users and tenants to Stytch's Organizations and Members model.
Import password hashes from your existing system so users can log in without resetting.
Proxy existing SAML connections or create new ones without customer-side IdP changes.
Preserve your existing user and organization identifiers for seamless backend integration.
## Coming from another provider?
## Start your migration
Step-by-step guide covering planning, data migration, testing, and go-live.
Talk to our team for hands-on migration support and architecture review.
# Stytch documentation
Source: https://stytch.com/docs/get-started/overview
Quickstart tutorial on implementing Stytch, supporting different frameworks and
application stacks.
Sample apps showcasing how to authenticate users and manage sessions.
Reference documentation for the Stytch API and SDKs.
## Featured
SAML and OIDC-compliant authentication for organizations.
Be an OAuth-compliant provider for AI agents or other apps.
Customizable UI components for faster deployment.
Prevent attacks like credential stuffing and free trial abuse.
Enable services to communicate with no human involvement.
Components for managing access and roles for users.
## Learn the platform
Select multi-tenant or consumer authentication for your application.
The full suite of supported authentication methods and authorization tools.
How to migrate to Stytch from your current authentication stack.
***
#### Need help?
#### Product updates
#### Policies & legal
# Quickstart
Source: https://stytch.com/docs/get-started/quickstart
Get up and running with Stytch in minutes.
# API reference
Source: https://stytch.com/docs/multi-tenant-auth/api-reference-link
# Fraud & risk protections
Source: https://stytch.com/docs/multi-tenant-auth/authentication/fraud-and-risk
Ways to protect your application using Device Fingerprinting and Protected Auth.
## Protected Auth
Ensure that only legitimate users can access your authentication flows.
Using Protected Auth to defend your signup and login flows.
### Modes
* **Enforcement:** React to Device Fingerprinting verdicts for your traffic.
* **Observation:** Only observe Device Fingerprinting verdicts.
### Protected methods
These authentication methods collect fingerprints and prevent actions when a fingerprint receives a `BLOCK` verdict:
* `impersonation.authenticate`
* `magicLinks.authenticate`
* `magicLinks.discovery.authenticate`
* `magicLinks.email.discovery.send`
* `oauth.authenticate`
* `oauth.discovery.authenticate`
* `otps.email.authenticate`
* `otps.email.discovery.authenticate`
* `otps.email.discovery.send`
* `otps.email.loginOrSignup`
* `otps.sms.authenticate`
* `otps.sms.send`
* `passwords.authenticate`
* `passwords.discovery.authenticate`
* `passwords.discovery.resetByEmail`
* `passwords.discovery.resetByEmailStart`
* `passwords.resetByEmail`
* `passwords.resetByEmailStart`
* `passwords.resetByExistingPassword`
* `passwords.resetBySession`
* `recoveryCodes.recover`
* `recoveryCodes.rotate`
* `sso.authenticate`
* `totp.authenticate`
* `totp.create`
Protected Auth is a ready-made feature provided in the [frontend](/docs/api-reference/b2b/frontend-sdks/react/overview) and [mobile](/docs/api-reference/b2b/mobile-sdks/react-native/overview) SDKs.
***
## New device notifications
Alert your users if a login is detected from a new device, IP address, or location so they can take immediate action if needed:
Detect new devices to trigger user notifications.
***
## Additional ways to use Device Fingerprinting
Both features integrate Stytch's Device Fingerprinting, which offers a device intelligence layer that can be used to prevent fraud and risk or provide device information as an additional user identifier.
More examples on how to use Device Fingerprinting:
Add an invisible bot protection for a smooth user experience.
Stop fake account creation abuse.
Use IP-based geographic filters for access control.
Add a remembered device flow.
# Authenticate an M2M client
Source: https://stytch.com/docs/multi-tenant-auth/authentication/m2m/authenticate-client
How to implement M2M authentication.
Stytch's M2M Authentication enables your services to communicate and authenticate without any human involvement. Use our M2M Auth API to create clients, retrieve access tokens, and define permissions with scopes to securely communicate between machines.
In this guide, you'll learn how to create and authenticate an M2M Client. By the end, you'll have:
* Created an M2M Client.
* Issued an `access_token`.
* Authenticated the M2M Client by validating the `access_token`.
* Deactivated or deleted an M2M Client.
## Before you start
In order to complete this guide, you'll need the following:
* A Stytch project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select **B2B Authentication**.
* The `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the **Project ID & API keys** section of the Stytch Dashboard. You'll need to pass these values into the `Authorization` request header for most Stytch API calls.
## Authenticating an M2M client
An M2M (machine-to-machine) client represents a machine identity. To create one, call the [Create M2M Client endpoint](/docs/api-reference/b2b/api/m2m/m2m-client/create-m2m-client) and provide optional attributes like `client_name`, `client_description`, `scopes`, and `trusted_metadata` that describe the client use case.
If you prefer, you also have a UI option for creating M2M Clients in the [Dashboard](https://stytch.com/dashboard/m2m-clients). For the purposes of this guide, we'll be using the API.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
-d '{
"client_name": "Example client",
"client_description": "Following the Stytch guide.",
"scopes": ["read:settings", "write:settings"],
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
}'
```
After a successful API call, the response will look like this.
```javascript theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret": "FXvejQZKicBl7Lq...QqNU",
"client_secret_last_four": "QqNU",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
This is the only time you will be able to view the generated `client_secret` in the API response. Stytch stores a hash of the `client_secret` and cannot recover the value if lost. Be sure to persist the `client_secret` in a secure location. If the `client_secret` is lost, you will need to trigger a [secret rotation flow](/docs/multi-tenant-auth/authentication/m2m/rotate-secrets) to receive another one.
After creating an M2M client, you'll need to issue and retrieve an access token.
Call the [Get Access Token endpoint](/docs/api-reference/b2b/api/m2m/token/get-access-token) and provide the following parameters in the request body and URL path:
* `project_id` from the Basic Auth header in **Step 1**
* `client_secret` from the response in **Step 1**
* `client_id` from the response in **Step 1**
* `grant_type` set to the value `"client_credentials"`
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/public/${projectId}/oauth2/token \
-H 'Content-Type: application/json' \
-d '{
"client_id": "m2m-client-test-a50053..",
"client_secret": "FXvejQZKicBl7Lq...QqNU",
"grant_type": "client_credentials"
}'
```
The response will contain the `access_token`, a JWT signed with your project's [JWKS](/docs/api-reference/b2b/api/sessions/get-jwks). The lifespan of the `access_token` is one hour.
```javascript theme={null}
{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600
}
```
At this point, the M2M Client you've created from **Step 1** will communicate with your services with the issued `access_token` from **Step 2**.
Your services will need to authenticate the `access_token` locally with one of Stytch's backend SDKs (available in Node, Python, Go, and Ruby). You can also alternatively use a library that supports JWT signature validation.
Using the backend Node SDK as an example, your services will call the [Authenticate Access Token method](/docs/api-reference/b2b/api/m2m/token/authenticate-access-token) with the provided `access_token`.
As an option for enforcing permissions, you can also specify `required_scopes` the `access_token` must contain in order to be valid: any missing scope will return an error.
```javascript theme={null}
const stytch = require('stytch');
const client = new stytch.B2BClient({
project_id: '${projectId}',
secret: '${secret}',
});
const params = {
access_token: 'eyJ...',
required_scopes: ['write:settings'],
};
client.m2m
.authenticateToken(params)
.then((resp) => {
console.log(resp);
})
.catch((err) => {
console.log(err);
});
```
A successful response will contain the M2M Client's `client_id`, `scopes`, and `custom_claims` (Check out our [custom claims guide](/docs/multi-tenant-auth/authentication/m2m/custom-claims) to learn how to pass customized claims to every issued JWT for M2M Clients.)
```javascript theme={null}
{
"client_id": "m2m-client-test-a50053....",
"scopes": ["read:settings","write:settings"],
"custom_claims": {}
}
```
At this point, the M2M Client has successfully been authenticated.
If the M2M Client needs to be deactivated, call the [Update M2M Client endpoint](/docs/api-reference/b2b/api/m2m/m2m-client/update-m2m-client) and set the `status` to `inactive`, which will prevent the M2M Client from receiving new access tokens.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/m2m-client-test-d73... \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
-d '{
"status": "inactive"
}'
```
M2M Clients can be reactivated by setting the `status` to `active`.
If the M2M Client needs to be deleted, call the [Delete M2M Client endpoint](/docs/api-reference/b2b/api/m2m/m2m-client/delete-m2m-client).
```sh theme={null}
curl --request DELETE \
--url https://test.stytch.com/v1/m2m/clients/m2m-client-test-d73... \
-u '${projectId}:${secret}'
```
Deactivating or deleting an M2M Client will not invalidate any existing JWTs issued to the client, only prevent it from receiving new ones.
## What's next
Keep your M2M Clients' identities secure and learn how to [rotate client secrets](/docs/multi-tenant-auth/authentication/m2m/rotate-secrets).
Also, take a look at our comprehensive [M2M Authentication example app tutorial](https://stytch.com/blog/how-to-implement-m2m-authentication-and-authorization-in-node-js-using-stytch/), which includes an open-source [GitHub repo](https://github.com/stytchauth/stytch-m2m-node-example) utilizing the Node.js SDK.
# Adding custom claims to M2M tokens
Source: https://stytch.com/docs/multi-tenant-auth/authentication/m2m/custom-claims
Add custom claims to M2M access tokens.
Custom claims allow you to encode additional information into your Stytch M2M access tokens, similar to session custom claims.
## How to use
First, pass in extra parameters to the [Get Access Token](/docs/api-reference/b2b/api/m2m/token/get-access-token) API endpoint. For example, to add a custom `user_id` claim:
```json theme={null}
{
"client_id": "m2m-client-test-cxxxxxx",
"client_secret": "xxxxxx",
"grant_type": "client_credentials",
"user_id": "123"
}
```
Most backend SDKs also support this via the `extra` param in the corresponding method.
Next, create an M2M custom claims template, either via [the Dashboard](https://stytch.com/dashboard/custom-claim-templates) or [the API](/docs/api-reference/pwa/api/v3/jwt-templates/set-jwt-template) and include this claim using the variable syntax `request.[claim]`:
```json theme={null}
{
"user_id": {{ request.user_id }}
}
```
You should see your custom `user_id` claim in newly minted M2M tokens.
# Import M2M clients from Auth0
Source: https://stytch.com/docs/multi-tenant-auth/authentication/m2m/import-clients
Migrating M2M clients from other platforms like Auth0 to Stytch.
Stytch's M2M Authentication supports importing clients from other platforms like Auth0. Use Stytch to import pre-existing `client_id`s, `client_secret`s, and assigned `scopes` without any data transformations or downtime.
## Before you start
In order to complete this guide, you'll need the following:
* A Stytch project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select **B2B Authentication**.
* The `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the **API keys** section of the Stytch Dashboard. You'll need to pass these values into the `Authorization` request header for most Stytch API calls.
## Importing M2M clients
First, access your existing client wherever it is stored. For this guide, we'll use Auth0 as an example.
In Auth0, under **Applications**, select an already created M2M client.
You'll need to find and copy the client's values that correspond to the following Stytch M2M Client fields:
* `client_id`
* `client_secret`
In this guide, you'll use the Dashboard UI to do the import.
In the [Dashboard](https://stytch.com/dashboard), navigate to **M2M Clients** and then click the **Import** button. A popup modal will appear prompting you to enter the client ID and secret.
Enter the client's values you copied from **Step 1** and click **Import**.
After the import, you can update the M2M Client with additional metadata like `client_name` and `client_description`.
More importantly, you can transfer any previously defined scopes during this step. In Auth0, navigate to your client's permissions and record the scopes you want to transfer.
Then in Stytch, scroll down to the **Scopes** section of the newly imported M2M Client and click **+ Add scopes**. A popup modal will appear prompting you to type in the scopes and separate them by a single space.
Click **Save** when you've finished editing the M2M Client.
To confirm a successful import, issue an access token for the M2M Client and decode the JWT.
```json theme={null}
{
"aud": [
"${projectId}"
],
"exp": 1693492107,
"iat": 1693488507,
"iss": "stytch.com/${projectId}",
"nbf": 1693488507,
"scope": "read:users update:users",
"sub": "lJJsWpAmd42q0EEJHJoSAxTPzdgpzF0i"
}
```
The access token's payload and standard claims should show the following data:
* The `iss` and `sub` match or contain your `project_id`.
* The `scope` matches the scopes you entered in **Step 3**.
* The `sub` matches the `client_id` you imported from **Steps 1 and 2**.
If the criteria are met, your M2M Client has successfully been imported to Stytch.
# Rotate client secrets
Source: https://stytch.com/docs/multi-tenant-auth/authentication/m2m/rotate-secrets
Managing M2M client secret rotation.
Stytch's M2M Authentication supports secret rotation to ensure security best practices with zero downtime. Regular rotation can prevent the prolonged misuse of any compromised credentials by updating the secrets for an M2M Client.
In this guide, you'll learn how to rotate secrets for an M2M Client. By the end, you'll have:
* Started a secret rotation.
* Completed a secret rotation.
* Canceled a secret rotation.
## Before you start
In order to complete this guide, you'll need the following:
* A Stytch project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select **B2B Authentication**.
* The `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the **Project ID & API keys** section of the Stytch Dashboard. You'll need to pass these values into the `Authorization` request header for most Stytch API calls.
* An active M2M Client. If you haven't created one yet, you can follow the [Authenticate an M2M Client guide](/docs/multi-tenant-auth/authentication/m2m/authenticate-client).
## Rotating client secrets
If you completed the [Authenticate an M2M Client guide](/docs/multi-tenant-auth/authentication/m2m/authenticate-client), you should have an active M2M Client. To confirm, call the [Get M2M Client endpoint](/docs/api-reference/b2b/api/m2m/m2m-client/get-m2m-client) with its `client_id`.
```sh theme={null}
curl --request GET \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID} \
-u '${projectId}:${secret}'
```
The response should look similar to this:
```javascript theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "QqNU",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
The important field to evaluate is `next_client_secret_last_four`. If the value is `null`, proceed to the next step. If the field is already populated, the client is already in a secret rotation flow. Create a new M2M Client instead.
Take the `client_id` of your active M2M Client and call the [Start Secret Rotation endpoint](/docs/api-reference/b2b/api/m2m/rotate-secret/start-secret-rotation).
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID}/secrets/rotate/start \
-u '${PROJECT_ID}:${SECRET}'
```
```json theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "QqNU",
"next_client_secret":"NHQhc7ZqsXJ...DzJj",
"next_client_secret_last_four": "DzJj",
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
After a successful API call, the response will return the M2M Client with two newly populated fields: `next_client_secret` and `next_client_secret_last_four`.
This is the only time you will be able to view the generated `next_client_secret` in the API response. Stytch stores a hash of the `next_client_secret` and cannot recover the value if lost. Be sure to persist the `next_client_secret` in a secure location. If the `next_client_secret` is lost, you will need to cancel the secret rotation (Step 4) and start the rotation all over.
At this point, the active M2M Client has **two** valid secrets associated with it. Both the `client_secret` and `next_client_secret` can be used to issue access tokens.
This is an opportune time to update your secret store to use the `next_client_secret` and replace the `client_secret` for your existing services.
Once updated with `next_client_secret`, your services can now retire the `client_secret` by completing the secret rotation.
Call the [Rotate Secret endpoint](/docs/api-reference/b2b/api/m2m/rotate-secret/rotate-secret) with the `client_id`.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID}/secrets/rotate \
-u '{PROJECT_ID}:{SECRET}'
```
```json theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "DzJj",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
A successful API call completes the secret rotation and results in the following updates:
* The M2M Client's `client_secret` from **Steps 1 and 2** will be invalidated.
* The M2M Client's `client_secret` from **Steps 1 and 2** will be replaced by the `next_client_secret`. To confirm, check the M2M Client's `client_secret_last_four` updated value.
* The M2M Client's `next_client_secret_last_four` will be set back to `null`.
If for any reason after **Step 2** you need to stop or revert the secret rotation process, call the [Cancel Secret Rotation endpoint](/docs/api-reference/b2b/api/m2m/rotate-secret/cancel-secret-rotation) with the `client_id`.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID}/secrets/rotate/cancel \
-u '{PROJECT_ID}:{SECRET}'
```
```json theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "DzJj",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
A successful API call cancels the secret rotation and results in the following updates:
* The M2M Client's `client_secret` from **Steps 1 and 2** will not be updated and will remain valid.
* The M2M Client's `next_client_secret` and `next_client_secret_last_four` will be invalidated and set back to `null`.
## What's next
Learn how to [import M2M Clients from other platforms like Auth0](/docs/multi-tenant-auth/authentication/m2m/import-clients).
Also, take a look at our comprehensive [M2M Authentication example app tutorial](https://stytch.com/blog/how-to-implement-m2m-authentication-and-authorization-in-node-js-using-stytch/), which includes an open-source [GitHub repo](https://github.com/stytchauth/stytch-m2m-node-example) utilizing the Node.js SDK.
# Adding magic links to a custom auth flow
Source: https://stytch.com/docs/multi-tenant-auth/authentication/magic-links/adding-magic-links-to-custom-auth-flow
Integrate with Stytch's headless or backend SDKs for complete control over your authentication flow.
## Overview
This guide walks through integrating email magic links using Stytch's headless frontend SDKs. This approach gives you complete control over your UI while handling authentication logic on the frontend.
Both Discovery and Organization-specific authentication flows are supported.
The Discovery flow is designed for situations where end users are signing up or logging in from a central landing page, and have not specified which Organization they are trying to access.
### Prerequisites
1. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)
2. Enable Create Organizations under "Enabled Methods"
### Implementation
In your application's UI, introduce a way for end users to input their email and trigger a `magicLinks.email.discovery.send()` method call with the provided email.
```jsx React icon=react theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Login = () => {
const stytch = useStytchB2BClient();
const sendDiscoveryEmail = () => {
stytch.magicLinks.email.discovery.send({
email_address: 'sandbox@stytch.com',
});
};
return ;
};
```
```jsx Next.js icon=triangle theme={null}
import { useStytchB2BClient } from '@stytch/nextjs/b2b';
export const Login = () => {
const stytch = useStytchB2BClient();
const sendDiscoveryEmail = () => {
stytch.magicLinks.email.discovery.send({
email_address: 'sandbox@stytch.com',
});
};
return ;
};
```
```javascript Vanilla JS icon=square-js theme={null}
```
Stytch will redirect the user to the Discovery RedirectURL you specified on the [Redirect URLs](https://stytch.com/dashboard/redirect-urls) page earlier. Exchange the token for a list of Discovered Organizations that the user can choose to log into.
```jsx React icon=react theme={null}
export const DiscoveryAuthenticate = () => {
const stytch = useStytchB2BClient();
useEffect(() => {
const authenticate = async () => {
const result = await stytch.authenticateByUrl();
if (result?.handled) {
const { email_address, discovered_organizations } = result.data;
// surface discovered organizations to user to select from
console.log({ email_address, discovered_organizations });
}
};
authenticate();
}, [stytch]);
return
;
};
```
```javascript Vanilla JS icon=square-js theme={null}
import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
const stytch = new StytchB2BClient('STYTCH_PUBLIC_TOKEN');
const result = await stytch.authenticateByUrl({
session_duration_minutes: 60,
});
if (result?.handled) {
const { email_address, discovered_organizations } = result.data;
// surface discovered organizations to user to select from
console.log({ email_address, discovered_organizations });
}
```
An intermediate\_session\_token will be returned. The headless SDK will automatically set this in cookies and include it in follow-up calls.
When the end user selects an Organization to log into, call the exchange intermediate session method with the selected Organization ID.
```jsx React icon=react theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const ExchangeIntermediateSession = () => {
const stytch = useStytchB2BClient();
useEffect(() => {
stytch.discovery.intermediateSessions.exchange({
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931'
});
});
return
;
};
```
```javascript Vanilla JS icon=square-js theme={null}
import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
const stytch = new StytchB2BClient('STYTCH_PUBLIC_TOKEN');
const createOrganization = () => {
stytch.discovery.organizations.create();
};
```
You can optionally prompt the user for the name and slug of their new Organization. If not provided, Stytch will auto-generate them based on the end user's email address.
If end users login via a page that indicates which Organization they're trying to access (e.g., `.your-app.com` or `your-app.com/team/`), you can offer Organization-specific authentication.
### Prerequisites
1. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)
### Implementation
On your login page for the organization, take in a user's email address and call the `magicLinks.email.loginOrSignup()` method.
```jsx React icon=react theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Login = () => {
const stytch = useStytchB2BClient();
const sendEmailMagicLink = () => {
stytch.magicLinks.email.loginOrSignup({
email_address: 'sandbox@stytch.com',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
});
};
return ;
};
```
```jsx Next.js icon=triangle theme={null}
import { useStytchB2BClient } from '@stytch/nextjs/b2b';
export const Login = () => {
const stytch = useStytchB2BClient();
const sendEmailMagicLink = () => {
stytch.magicLinks.email.loginOrSignup({
email_address: 'sandbox@stytch.com',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
});
};
return ;
};
```
```html Vanilla JS icon=square-js theme={null}
```
Stytch will redirect the user to the Login or Signup RedirectURL that you specified in the `magicLinks.email.loginOrSignup()` call, or the relevant default in the [Redirect URLs](https://stytch.com/dashboard/redirect-urls) page of the Stytch Dashboard. The URL's query parameters will contain `stytch_token_type=multi_tenant_magic_links` and an authentication token. Your application should extract the token from the URL and call the appropriate authentication method to finish the login process.
```jsx React icon=react theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient, useStytchMemberSession } from '@stytch/react/b2b';
export const Authenticate = () => {
const stytch = useStytchB2BClient();
const { session } = useStytchMemberSession();
useEffect(() => {
if (session) {
window.location.href = 'https://example.com/profile';
} else {
stytch.authenticateByUrl({
session_duration_minutes: 60,
});
}
}, [stytch, session]);
return
;
};
```
```html Vanilla JS icon=square-js theme={null}
import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
const stytch = new StytchB2BClient('STYTCH_PUBLIC_TOKEN');
stytch.authenticateByUrl({
session_duration_minutes: 60,
});
```
This guide walks through integrating magic links using Stytch's backend SDKs. This approach handles all authentication logic on your backend, giving you complete control over the user experience.
Both Discovery and Organization-specific authentication flows are supported.
The Discovery flow is designed for situations where end users are signing up or logging in from a central landing page, and have not specified which Organization they are trying to access.
### Implementation
Prompt the user to input their email and then call the [SendDiscoveryEmail API](/docs/api-reference/b2b/api/email-magic-links/discovery/send-discovery-email) to initiate the magic link flow.
```python Python icon=python theme={null}
@app.route("/send-discovery-eml", methods=["POST"])
def send_discovery_eml() -> str:
resp = stytch_client.magic_links.email.discovery.send(
email_address=email
)
if resp.status_code != 200:
return resp.error
return "Success", 200
```
Stytch will make a callback to the `discovery` Redirect URL that you specified on the [Redirect URLs](https://stytch.com/dashboard/redirect-urls) page of the Stytch Dashboard. Since the same Redirect URL can be used for multiple authentication methods, the `stytch_token_type` in the callback parameters indicates which authentication flow the token belongs to. Your application should handle checking the `stytch_token_type` and calling the appropriate authentication method with the `token`.
If your RedirectURL was `http://localhost:3000/discovery` you would add the following route to your application:
```python theme={null}
@app.route("/discovery", methods=["GET"])
def discovery() -> str:
token_type = request.args["stytch_token_type"]
token = request.args["token"]
if token_type != "discovery":
# add handling for other discovery token types like discovery_oauth in the future
return "Unsupported auth method", 400
resp = stytch_client.magic_links.discovery.authenticate(
discovery_magic_links_token=token
)
if resp.status_code != 200:
return resp.error
# store IST as cookie to include in org selection or creation request
session['ist'] = resp.intermediate_session_token
return resp.discovered_organizations
```
When the end user selects an Organization to log into, call the [ExchangeIntermediateSession API](api-reference/b2b/api/discovery/exchange-intermediate-session) with the selected `organization_id` and the `intermediate_session_token` returned from the discovery authenticate call.
```python theme={null}
@app.route("/login/", methods=["GET"])
def login_to_org(organization_id):
ist = session.get('ist')
if not ist:
return 500
resp = stytch_client.discovery.intermediate_sessions.exchange(
intermediate_session_token=ist,
organization_id=organization_id
)
if resp.status_code != 200:
return resp.error
# Clear IST and set stytch session
session.pop('ist', None)
session['stytch_session'] = resp.session_token
return resp.member
```
Allow users to create a new Organization instead of logging into an existing one.
You can optionally prompt the user for the name and slug of their new Organization. If not provided, Stytch will auto-generate them based on the end user's email address.
```python theme={null}
@app.route("/create_org", methods=["GET"])
def create_org() -> str:
ist = session.get('ist')
if not ist:
return "No IST found", 400
resp = stytch_client.discovery.organizations.create(
intermediate_session_token=ist
)
if resp.status_code != 200:
return resp.error
# Clear IST and set stytch session
session.pop('ist', None)
session['stytch_session'] = resp.session_token
return resp.member
```
If end users login via a page that indicates which Organization they're trying to access (e.g., `.your-app.com` or `your-app.com/team/`), you can offer Organization-specific authentication.
### Implementation
After the user inputs their email on your Organization-specific login page, make a call to Stytch to initiate the magic link login flow with the provided email and the `organization_id` they are logging into.
```python Python icon=python theme={null}
@app.route("/send_magic_link", methods=["POST"])
def send_magic_link_login() -> str:
email = request.form.get('email', None)
organization_id = request.form.get('organization_id', None)
if email is None or organization_id is None:
return "Email and OrgID are required", 400
resp = stytch_client.magic_links.email.login_or_signup(
email_address=email,
organization_id=organization_id
)
if resp.status_code != 200:
return "Error sending EML", 500
return "Success"
```
Stytch will make a callback to the `login` or `signup` Redirect URL that you specified on the [Redirect URLs](https://stytch.com/dashboard/redirect-urls) page of the Stytch Dashboard, depending on if this is a returning user or a new user JIT Provisioning. Since the same Redirect URL can be used for multiple authentication methods, the `stytch_token_type` in the callback parameters indicates which authentication flow the token belongs to. Your application should handle checking the `stytch_token_type` and calling the appropriate authentication method with the `token`.
If your RedirectURL was `http://localhost:3000/authenticate` you would add the following route to your application:
```python Python icon=python theme={null}
def authenticate() -> str:
token_type = request.args["stytch_token_type"]
if token_type != "multi_tenant_magic_links":
return "unsupported authentication method", 400
resp = stytch_client.magic_links.authenticate(magic_links_token=request.arg["token"])
if resp.status_code != 200:
return "something went wrong authenticating token", 500
session["stytch_session"] = resp.session_jwt
return resp.member
```
# Adding magic links to Stytch Login
Source: https://stytch.com/docs/multi-tenant-auth/authentication/magic-links/adding-magic-links-to-stytch-login
Integrate magic links with Stytch's pre-built UI components.
This guide walks through adding Magic Links to Stytch's pre-built UI components. The pre-built UI automatically handles both Discovery and Organization-specific authentication flows.
## Prerequisites
If you haven't done so already:
1. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)
2. Enable Create Organizations under "Enabled Methods."
## Add magic links to your UI config
To surface email magic links as an authentication option in your login UI, simply add it to the UI config products array.
```jsx React icon=react theme={null}
import { AuthFlowType, B2BProducts, StytchB2B } from '@stytch/react/b2b';
const config = {
authFlowType: AuthFlowType.Discovery,
products: [B2BProducts.emailMagicLinks],
emailMagicLinksOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
},
sessionOptions: { sessionDurationMinutes: 60 },
};
const LoginOrSignup = () => {
return ;
};
```
```jsx Next.js icon=triangle theme={null}
import { AuthFlowType, B2BProducts, StytchB2B } from '@stytch/nextjs/b2b';
const config = {
authFlowType: AuthFlowType.Discovery,
products: [B2BProducts.emailMagicLinks],
emailMagicLinksOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
},
sessionOptions: {
sessionDurationMinutes: 60,
},
};
export const LoginOrSignupDiscoveryForm = () => {
return ;
};
```
```javascript Vanilla JS icon=square-js theme={null}
```
## Render the UI component
Render the Stytch UI component on the Redirect URL(s) that you specified in the [Stytch Dashboard](https://stytch.com/dashboard/redirect-urls) during the configuration steps.
When rendered, the UI component will automatically:
* Send an email to the user containing a magic link
* On click, handle calling Stytch's servers to authenticate the token
* For Discovery flows, prompt the user with their discovered organizations and handle their selection
* For Organization-specific flows, log the user in directly
## Test your integration
Start your application and test the magic link flow:
1. Navigate to your login page
2. Input your email and click Continue
3. Click on the login link sent to your email
4. Verify that you're redirected back to your application and authenticated
## Next steps
Learn how to customize the appearance and behavior of the pre-built UI
Learn how to add server-side session authentication and authorization with Stytch sessions
# Email magic links
Source: https://stytch.com/docs/multi-tenant-auth/authentication/magic-links/overview
Learn about email magic links in Multi-tenant authentication.
## API resources
Use the Send Discovery Email API to start a magic link login flow.
Use Stytch's Frontend SDKs to start a magic link login flow.
Use Stytch's pre-built UI components to seamlessly add email magic links to your login flow.
## How it works
Email magic links are a secure, seamless, passwordless authentication option.
When a user logs in via Stytch's Email Magic Link (EML) product, Stytch generates a unique, one-time-use token embedded in a URL and sends it to the user's email address. The user authenticates their identity by successfully receiving and clicking on this link before the link expires, at which point Stytch will either issue a Session or prompt the user to perform MFA if they are enrolled.
Stytch also uses magic links in our flow for inviting new Members to an existing Organization, allowing the end user to accept the invite and authenticate into the target Organization in one step.
Magic links require [redirect URLs](/docs/resources/workspace-management/redirect-urls) to be configured in your Dashboard.
## Discovery vs. Organization-specific login
Stytch's Multi-Tenant Authentication product supports two different magic link authentication flows:
Used when you already know the Organization that the end user is trying to log into.
Used for self-serve Organization creation or login prior to knowing the Organization context.
Both flows support email magic links, allowing end users to accept pending invites or JIT Provisioning by email domain, and finish with the end user authenticated in a specific Organization. However, Discovery involves one additional step to surface the end user's "discovered organizations" that they are eligible to login to and also allows self-serve organization creation.
## Email template customization
Email templates control the subject line and body of the email a Member receives. For Magic Link endpoints that send emails, there are three possible email templates a Member can receive: **login**, **signup** or **invite**. Which email template a Member receives is based on a combination of the Member's state (e.g. `active`, `pending`) in the Stytch backend and whether the magic link is initiated via a discovery authentication, organization-specific authentication or invite method.
Styling customizations like color and font are available by default, while full HTML customization is available to customers paying for our branding add-on. Learn more about customizing email templates [here](/docs/resources/branding/custom-email-templates).
## Security
Stytch's email magic links product has built-in protections that ensure seamless delivery of magic links in corporate environments without compromising on security.
### One-time use restrictions
Each Magic Link that is sent to the user is one-time use. Once the user clicks on the Magic Link and logs in, the embedded token that uniquely identifies the authentication request is “consumed” and cannot be used for subsequent logins. This is critical for security as it significantly reduces the chances of unauthorized access. Once the user has completed the authentication flow the token cannot be stolen or replayed by an attacker to gain access.
However, in corporate environments, where email security scanners are very prevalent, this security measure can sometimes make Email Magic Links unusable.
### Email security scanners
Email security scanners are tools that scan incoming emails for malicious content, such as suspicious links or attachments, in an effort to protect the user from phishing attacks or malware. Most security scanners will inspect and click on any links present in emails, in order to identify suspicious redirects, attempts to execute malicious scripts or download malware, or signs of a phishing attempt.
When the link in question is actually an email magic link, the scanner will end up consuming the token before the email actually makes its way into the user's inbox, preventing them from being able to use the link to login.
### Stytch's built-in solution
In order to not compromise on the security of our EML product, while accounting for the prevalence of email security scanners, particularly in corporate inboxes, Stytch leverages our device intelligence product to handle “clicks” differently depending on if it is a real human user, or an email security scanner.
When we identify that the Magic Link has been clicked by an email security scanner, we do not treat the click as an authentication attempt - meaning the token is not consumed, and a session is not granted. The security scanner is able to inspect the link and identify that it is not malicious, without interfering with the one-time-use restriction. Once the scan is complete and the email is passed through to the user's inbox, they are able to click on the link and successfully log in.
This protection comes out of the box for all Stytch customers, but if you are interested in learning more about other use cases of Stytch's device intelligence product (such as bot-protection on your login page, or identifying and blocking fraudulent real users) [contact sales](https://stytch.com/contact/sales?vertical=FRAUD).
# Adding MFA to a custom auth flow
Source: https://stytch.com/docs/multi-tenant-auth/authentication/mfa/adding-mfa-to-custom-auth-flow
Integrate MFA with Stytch's headless or backend SDKs for complete control over your authentication flow.
## Prerequisites
Before you can add MFA to a custom auth flow, you'll need to complete the following steps:
Before integrating MFA, you need to already have a **primary authentication flow** built out. You can do so by following one of the integration guides:
To configure MFA, you'll need to first toggle on "Require MFA" for at least one Organization in the [Stytch Dashboard](https://stytch.com/dashboard/organizations), or call the [Update Organization API](/docs/api-reference/b2b/api/organizations/update-organization) with `mfa_policy` set to `REQUIRED_FOR_ALL`.
Each Organization can specify which `mfa_methods` are allowed for Members in their Organization: either `ALL_ALLOWED` or `RESTRICTED`. If `RESTRICTED`, Members can only use MFA methods specified in the `allowed_mfa_methods` array.
For example, if an Organization requires Members to use TOTP MFA:
```json theme={null}
{
"mfa_policy": "REQUIRED_FOR_ALL",
"mfa_methods": "RESTRICTED",
// Optional, not enforced if mfa_methods is ALL_ALLOWED
"allowed_mfa_methods": ["totp"]
}
```
## Integrating MFA
Stytch's headless and backend SDKs provide a flexible way to integrate MFA into your authentication flow.
### Required MFA enrollment
When an Organization's MFA policy is `REQUIRED_FOR_ALL` but the Member is not currently enrolled in MFA, you'll need to handle MFA enrollment after primary authentication.
The following authentication methods will return a Member Session if the Member is not enrolled in MFA OR an `intermediate_session_token` and an `mfa_required` object that indicates MFA is required in order to be granted a Session for the Organization:
* [sso.authenticate()](/docs/api-reference/b2b/frontend-sdks/react/methods/sso/authenticate)
* [oauth.authenticate()](/docs/api-reference/b2b/frontend-sdks/react/methods/oauth/discovery-authenticate)
* [magicLinks.authenticate()](/docs/api-reference/b2b/frontend-sdks/react/methods/email-magic-links/authenticate-discovery-magic-link)
* [passwords.authenticate()](/docs/api-reference/b2b/frontend-sdks/react/methods/passwords/authenticate)
* [discovery.intermediateSessions.exchange()](/docs/api-reference/b2b/frontend-sdks/react/methods/discovery/exchange-intermediate-session)
* [passwords.resetByEmail()](/docs/api-reference/b2b/frontend-sdks/react/methods/passwords/reset-by-email)
* [passwords.resetByExistingPassword()](/docs/api-reference/b2b/frontend-sdks/react/methods/passwords/reset-by-existing-password)
If the Member is not yet enrolled in MFA, the response will look as follows:
```json theme={null}
{
"intermediate_session_token": "oNJB3foIA79dn_uNVMNghG_MGkKSLHnR65NsKXv0gZzY",
"mfa_required": {
"member_options": null,
"secondary_auth_initiated": null
},
"member_authenticated": false,
"organization": {
"mfa_methods": "REQUIRED_FOR_ALL",
"allowed_mfa_methods": ["sms_otp", "totp"]
}
...
}
```
Indicating that `member_authenticated: false` due to `mfa_required` -- but no `member_options` are present. The Stytch SDK will automatically store the `intermediate_session_token` and handle passing that with subsequent calls to tie the user's primary and secondary authentication together.
For the primary authentication endpoint(s) you've already integrated add handling to detect when MFA enrollment is required by checking to see if the `organization.mfa_methods` is `ALL_ALLOWED` or `RESTRICTED`. If `RESTRICTED` use the `organization.allowed_mfa_methods` to surface only allowed MFA methods to the user.
Check the `organization.allowed_mfa_methods` and based on the available options, prompt the user to enroll in MFA.
For SMS MFA, prompt the user for their phone number and call the SMS Send method with the number. Make sure the `mfa_phone_number` is in the E.164 format, e.g. "+14155551234".
```jsx React icon=react theme={null}
import { useCallback, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const SendOtp = () => {
const stytchClient = useStytchB2BClient();
const [phoneNumber, setPhoneNumber] = useState('');
const trigger = useCallback(() => {
stytchClient.otps.sms.send({
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c2cfb6e1bd7d',
mfa_phone_number: phoneNumber,
});
}, [stytchClient, phoneNumber]);
return (
setPhoneNumber(e.target.value)}
/>
);
};
```
```html Vanilla JS icon=js theme={null}
```
For TOTP MFA, you'll first trigger the TOTP Create method, which will return a `qr_code` that you will surface to the end user to register their authenticator app.
```jsx React icon=react theme={null}
import { useCallback } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const CreateTOTP = () => {
const stytchClient = useStytchB2BClient();
const trigger = useCallback(() => {
stytchClient.totp.create({ expiration_minutes: 60 });
}, [stytchClient]);
return ;
};
```
```html Vanilla JS icon=js theme={null}
```
Prompt the user to input their OTP or TOTP code in order to complete the MFA enrollment and authenticate.
```jsx React icon=react theme={null}
import { useCallback, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Authenticate = () => {
const stytch = useStytchB2BClient();
const [code, setCode] = useState('');
const authenticate = useCallback(
(e) => {
e.preventDefault();
stytch.otps.sms.authenticate({
member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
code: code,
session_duration_minutes: 60,
});
},
[stytch, code],
);
const handleChange = useCallback((e) => {
setCode(e.target.value);
}, []);
return (
);
};
```
```html Vanilla JS icon=js theme={null}
```
```jsx React icon=react theme={null}
import { useCallback, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Authenticate = () => {
const stytchClient = useStytchB2BClient();
const [totpCode, setTotpCode] = useState('');
const authenticate = useCallback(
(e) => {
e.preventDefault();
stytchClient.totp.authenticate({ code: totpCode, session_duration_minutes: 60 });
},
[stytchClient, totpCode],
);
const handleChange = useCallback((e) => {
setTotpCode(e.target.value);
}, []);
return (
);
};
```
```html Vanilla JS icon=js theme={null}
```
Run your application and attempt to login to your Organization with the `REQUIRED_FOR_ALL` MFA policy to test out required enrollment in MFA.
### Returning MFA authentication
The flow for returning MFA authentication with a headless frontend integration is quite similar to the last steps of enrolling a Member in MFA for the first time.
When a Member is already enrolled in MFA, the response from the various `authenticate()` methods discussed in the "Required MFA Enrollment" section will include details on the MFA options they are enrolled in:
```json theme={null}
{
"intermediate_session_token": "oNJB3foIA79dn_uNVMNghG_MGkKSLHnR65NsKXv0gZzY",
"mfa_required": {
"member_options": {
"mfa_phone": "+14151112222",
"totp_registration_id": "member-totp-test-41920359-8bbb-4fe8-8fa3-aaa83f35f02c"
},
"secondary_auth_initiated": "sms_otp"
},
"member_authenticated": false,
...
}
```
If a Member's `default_mfa_method` is SMS OTP, Stytch will automatically trigger the SMS Send when the user performs primary authentication for that Organization and will indicate this in the `secondary_auth_initiated` field.
Check the `member_options` and surface the appropriate input to the Member to complete MFA.
Similar to the last step of MFA enrollment, handle submission of the TOTP or OTP code in order to finish the MFA authentication and log the member in based on their default MFA method.
```jsx React icon=react theme={null}
import { useCallback, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Authenticate = () => {
const stytch = useStytchB2BClient();
const [code, setCode] = useState('');
const authenticate = useCallback(
(e) => {
e.preventDefault();
stytch.otps.sms.authenticate({
member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
code: code,
session_duration_minutes: 60,
});
},
[stytch, code],
);
const handleChange = useCallback((e) => {
setCode(e.target.value);
}, []);
return (
);
};
```
```html Vanilla JS icon=js theme={null}
```
```jsx React icon=react theme={null}
import { useCallback, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Authenticate = () => {
const stytchClient = useStytchB2BClient();
const [totpCode, setTotpCode] = useState('');
const authenticate = useCallback(
(e) => {
e.preventDefault();
stytchClient.totp.authenticate({ code: totpCode, session_duration_minutes: 60 });
},
[stytchClient, totpCode],
);
const handleChange = useCallback((e) => {
setTotpCode(e.target.value);
}, []);
return (
);
};
```
```html Vanilla JS icon=js theme={null}
```
Run your application and go through an authentication flow with a user who already has an MFA method enabled.
### Optional MFA enrollment
If an Organization's MFA policy is `OPTIONAL`, members can still opt to secure their account with MFA. At a high level, the flow for this is very similar to required enrollment, but can occur anytime after the Member has logged in and has been granted a Session.
The headless SDK will take care of including the `session_jwt` / `session_token` in the `authenticate()` calls, so the only additional piece you'd need to do is to pass in the `set_mfa_enrollment` flag:
```jsx React icon=react theme={null}
stytch.otps.sms.authenticate({
member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
code: otpCode,
session_duration_minutes: 60,
set_mfa_enrollment: 'enroll'
});
```
```javascript Vanilla JS icon=js theme={null}
stytch.otps.sms.authenticate({
member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
code: document.getElementById('otp-input').value,
session_duration_minutes: 60,
set_mfa_enrollment: 'enroll'
});
```
```jsx React icon=react theme={null}
stytch.totp.authenticate({
member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
code: totpCode,
session_duration_minutes: 60,
set_mfa_enrollment: 'enroll'
});
```
```javascript Vanilla JS icon=js theme={null}
stytch.totp.authenticate({
member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
code: document.getElementById('totp-input').value,
session_duration_minutes: 60,
set_mfa_enrollment: 'enroll'
});
```
If the Member is enrolling in an additional form of MFA (e.g. has SMS MFA already, and is adding TOTP) you can also specify that the newly enrolled MFA method becomes the default through the `set_default_mfa` boolean.
If you want to enroll the user in MFA for occasional step-up authentication on particularly sensitive actions, rather than MFA that is required on each login you can omit the `set_mfa_enrollment` field in the `authenticate()` call.
### Required MFA enrollment
When an Organization's MFA policy is `REQUIRED_FOR_ALL` but the Member is not currently enrolled in MFA, you'll need to handle MFA enrollment after primary authentication.
The following APIs will return a Member Session if the Member is not enrolled in MFA OR an `intermediate_session_token` and an `mfa_required` object that indicates MFA is required in order to be granted a Session for the Organization:
* [Authenticate SSO](/docs/api-reference/b2b/api/sso/shared/complete-sso-authenticate)
* [Authenticate OAuth](/docs/api-reference/b2b/api/oauth/discovery/authenticate)
* [Authenticate Magic Link](/docs/api-reference/b2b/api/email-magic-links/organization/authenticate-magic-link)
* [Authenticate Password](/docs/api-reference/b2b/api/passwords/authenticate)
* [Exchange Session](/docs/api-reference/b2b/api/sessions/exchange-session)
* [Password Reset by Email](/docs/api-reference/b2b/api/passwords/create-or-reset-options/password-reset-by-email)
* [Password Reset by Existing Password](/docs/api-reference/b2b/api/passwords/create-or-reset-options/password-reset-by-existing-password)
If the Member is not yet enrolled in MFA, the response will look as follows:
```json theme={null}
{
"intermediate_session_token": "oNJB3foIA79dn_uNVMNghG_MGkKSLHnR65NsKXv0gZzY",
"mfa_required": {
"member_options": null,
"secondary_auth_initiated": null
},
"member_authenticated": false,
"organization": {
"mfa_methods": "REQUIRED_FOR_ALL",
"allowed_mfa_methods": ["sms_otp", "totp"]
}
}
```
Indicating that `member_authenticated: false` due to `mfa_required` -- but no `member_options` are present.
For the primary authentication endpoint(s) you've already integrated add handling to detect when MFA enrollment is required by checking to see if the `organization.mfa_methods` is `ALL_ALLOWED` or `RESTRICTED`. If `RESTRICTED` use the `organization.allowed_mfa_methods` to surface only allowed MFA methods to the user.
```python Python icon=python theme={null}
@app.route("/authenticate", methods=["POST"])
def authenticate():
# After primary authentication (e.g., magic link, password, OAuth)
resp = stytch_client.magic_links.authenticate(
magic_links_token=request.form.get("token")
)
if resp.member_authenticated:
# No MFA required, create session
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
# MFA is required - store intermediate session token
session["intermediate_session_token"] = resp.intermediate_session_token
# Check allowed MFA methods
allowed_methods = resp.organization.allowed_mfa_methods
if resp.organization.mfa_methods == "RESTRICTED":
# Only show allowed methods to user
return render_template("mfa_enrollment.html", allowed_methods=allowed_methods)
return render_template("mfa_enrollment.html", allowed_methods=["sms_otp", "totp"])
```
```javascript Node.js icon=node theme={null}
app.post("/authenticate", async (req, res) => {
// After primary authentication (e.g., magic link, password, OAuth)
const resp = await stytchClient.magicLinks.authenticate({
magic_links_token: req.body.token,
});
if (resp.member_authenticated) {
// No MFA required, create session
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
}
// MFA is required - store intermediate session token
req.session.intermediate_session_token = resp.intermediate_session_token;
// Check allowed MFA methods
const allowedMethods = resp.organization.allowed_mfa_methods;
if (resp.organization.mfa_methods === "RESTRICTED") {
// Only show allowed methods to user
return res.render("mfa_enrollment", { allowedMethods });
}
return res.render("mfa_enrollment", { allowedMethods: ["sms_otp", "totp"] });
});
```
Your frontend should prompt the user to enroll in MFA via the allowed methods.
For TOTP, no user input is required to start registration. For SMS, your frontend will need to collect the user's phone number in E.164 format (e.g., "+14155551234").
Add routes to handle starting the MFA enrollment process for SMS OTP or TOTP, depending on the users' selection.
```python Python icon=python theme={null}
@app.route("/mfa/sms/send", methods=["POST"])
def mfa_sms_send():
intermediate_session_token = session.get("intermediate_session_token")
if not intermediate_session_token:
return "No intermediate session", 400
phone_number = request.form.get("phone_number")
resp = stytch_client.otps.sms.send(
organization_id=session.get("organization_id"),
member_id=session.get("member_id"),
mfa_phone_number=phone_number,
intermediate_session_token=intermediate_session_token,
)
if resp.status_code != 200:
return "Error sending OTP", 500
return render_template("mfa_verify.html", mfa_type="sms")
```
```javascript Node.js icon=node theme={null}
app.post("/mfa/sms/send", async (req, res) => {
const intermediateSessionToken = req.session.intermediate_session_token;
if (!intermediateSessionToken) {
return res.status(400).send("No intermediate session");
}
const phoneNumber = req.body.phone_number;
const resp = await stytchClient.otps.sms.send({
organization_id: req.session.organization_id,
member_id: req.session.member_id,
mfa_phone_number: phoneNumber,
intermediate_session_token: intermediateSessionToken,
});
return res.render("mfa_verify", { mfaType: "sms" });
});
```
```python Python icon=python theme={null}
@app.route("/mfa/totp/create", methods=["POST"])
def mfa_totp_create():
intermediate_session_token = session.get("intermediate_session_token")
if not intermediate_session_token:
return "No intermediate session", 400
resp = stytch_client.totps.create(
organization_id=session.get("organization_id"),
member_id=session.get("member_id"),
intermediate_session_token=intermediate_session_token,
expiration_minutes=60,
)
if resp.status_code != 200:
return "Error creating TOTP", 500
# Return the QR code for the user to scan
return render_template(
"mfa_totp_setup.html",
qr_code=resp.qr_code,
secret=resp.secret,
)
```
```javascript Node.js icon=node theme={null}
app.post("/mfa/totp/create", async (req, res) => {
const intermediateSessionToken = req.session.intermediate_session_token;
if (!intermediateSessionToken) {
return res.status(400).send("No intermediate session");
}
const resp = await stytchClient.totps.create({
organization_id: req.session.organization_id,
member_id: req.session.member_id,
intermediate_session_token: intermediateSessionToken,
expiration_minutes: 60,
});
// Return the QR code for the user to scan
return res.render("mfa_totp_setup", {
qrCode: resp.qr_code,
secret: resp.secret,
});
});
```
You'll want to add a route that allows the user to finish the enrollment process by authenticating with their chosen factor.
Your frontend should prompt the user for the 6-digit code they received via SMS and submit it along with:
* `organization_id`: The Organization's ID
* `member_id`: The Member's ID
* `code`: The OTP code entered by the user
* `intermediate_session_token`: The token from primary authentication
When the user submits their OTP code, you'll call Stytch to authenticate the code. If successful, this will complete the Member's enrollment in MFA and grant them a Member Session.
```python Python icon=python theme={null}
@app.route("/mfa/sms/authenticate", methods=["POST"])
def mfa_sms_authenticate():
intermediate_session_token = session.get("intermediate_session_token")
if not intermediate_session_token:
return "No intermediate session", 400
code = request.form.get("code")
resp = stytch_client.otps.sms.authenticate(
organization_id=session.get("organization_id"),
member_id=session.get("member_id"),
code=code,
intermediate_session_token=intermediate_session_token,
session_duration_minutes=60,
)
if resp.status_code != 200:
return "Error authenticating OTP", 500
# Clear intermediate session and set authenticated session
session.pop("intermediate_session_token", None)
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
```
```javascript Node.js icon=node theme={null}
app.post("/mfa/sms/authenticate", async (req, res) => {
const intermediateSessionToken = req.session.intermediate_session_token;
if (!intermediateSessionToken) {
return res.status(400).send("No intermediate session");
}
const code = req.body.code;
const resp = await stytchClient.otps.sms.authenticate({
organization_id: req.session.organization_id,
member_id: req.session.member_id,
code: code,
intermediate_session_token: intermediateSessionToken,
session_duration_minutes: 60,
});
// Clear intermediate session and set authenticated session
delete req.session.intermediate_session_token;
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
});
```
For TOTP, you'll need to first surface the QR Code so the user can create a TOTP registration in their authenticator app. Once registered, your UI should prompt them to input the TOTP code from their authenticator app. Your frontend should submit:
* `organization_id`: The Organization's ID
* `member_id`: The Member's ID
* `code`: The 6-digit TOTP code from their authenticator app
* `intermediate_session_token`: The token from primary authentication
When the user submits their TOTP code, you'll call Stytch to authenticate the code. If successful, this will complete the Member's enrollment in MFA and grant them a Member Session.
```python Python icon=python theme={null}
@app.route("/mfa/totp/authenticate", methods=["POST"])
def mfa_totp_authenticate():
intermediate_session_token = session.get("intermediate_session_token")
if not intermediate_session_token:
return "No intermediate session", 400
code = request.form.get("code")
resp = stytch_client.totps.authenticate(
organization_id=session.get("organization_id"),
member_id=session.get("member_id"),
code=code,
intermediate_session_token=intermediate_session_token,
session_duration_minutes=60,
)
if resp.status_code != 200:
return "Error authenticating TOTP", 500
# Clear intermediate session and set authenticated session
session.pop("intermediate_session_token", None)
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
```
```javascript Node.js icon=node theme={null}
app.post("/mfa/totp/authenticate", async (req, res) => {
const intermediateSessionToken = req.session.intermediate_session_token;
if (!intermediateSessionToken) {
return res.status(400).send("No intermediate session");
}
const code = req.body.code;
const resp = await stytchClient.totps.authenticate({
organization_id: req.session.organization_id,
member_id: req.session.member_id,
code: code,
intermediate_session_token: intermediateSessionToken,
session_duration_minutes: 60,
});
// Clear intermediate session and set authenticated session
delete req.session.intermediate_session_token;
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
});
```
The member is now enrolled in MFA, and will be prompted to perform MFA through their selected method on subsequent authentication.
Run your application and attempt to login to your Organization with the `REQUIRED_FOR_ALL` MFA policy to test out required enrollment in MFA.
### Returning MFA authentication
The flow for returning MFA authentication with a backend integration is quite similar to the last step of enrolling a Member in MFA for the first time.
When a Member is already enrolled in MFA, the response from the various `authenticate()` APIs discussed in the "Required MFA Enrollment" section will indicate this in the `mfa_required.member_options` object:
```json theme={null}
{
"intermediate_session_token": "oNJB3foIA79dn_uNVMNghG_MGkKSLHnR65NsKXv0gZzY",
"mfa_required": {
"member_options": {
"mfa_phone": "+14151112222",
"totp_registration_id": "member-totp-test-41920359-8bbb-4fe8-8fa3-aaa83f35f02c"
},
"secondary_auth_initiated": "sms_otp"
},
"member_authenticated": false
}
```
If a Member's `default_mfa_method` is SMS OTP, Stytch will automatically trigger the SMS Send when the user performs primary authentication for that Organization and will indicate this in the `secondary_auth_initiated` field.
Check the `member_options` and surface the appropriate input to the Member to complete MFA:
```python Python icon=python theme={null}
@app.route("/authenticate", methods=["POST"])
def authenticate():
# After primary authentication
resp = stytch_client.magic_links.authenticate(
magic_links_token=request.form.get("token")
)
if resp.member_authenticated:
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
# MFA required - check member's enrolled methods
session["intermediate_session_token"] = resp.intermediate_session_token
member_options = resp.mfa_required.member_options
if member_options:
# Member is already enrolled in MFA
mfa_methods = []
if member_options.mfa_phone:
mfa_methods.append("sms_otp")
if member_options.totp_registration_id:
mfa_methods.append("totp")
# Check if SMS was auto-triggered
sms_auto_sent = resp.mfa_required.secondary_auth_initiated == "sms_otp"
return render_template(
"mfa_verify.html",
mfa_methods=mfa_methods,
sms_auto_sent=sms_auto_sent,
masked_phone=member_options.mfa_phone,
)
# Member needs to enroll in MFA
return render_template("mfa_enrollment.html")
```
```javascript Node.js icon=node theme={null}
app.post("/authenticate", async (req, res) => {
// After primary authentication
const resp = await stytchClient.magicLinks.authenticate({
magic_links_token: req.body.token,
});
if (resp.member_authenticated) {
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
}
// MFA required - check member's enrolled methods
req.session.intermediate_session_token = resp.intermediate_session_token;
const memberOptions = resp.mfa_required.member_options;
if (memberOptions) {
// Member is already enrolled in MFA
const mfaMethods = [];
if (memberOptions.mfa_phone) {
mfaMethods.push("sms_otp");
}
if (memberOptions.totp_registration_id) {
mfaMethods.push("totp");
}
// Check if SMS was auto-triggered
const smsAutoSent = resp.mfa_required.secondary_auth_initiated === "sms_otp";
return res.render("mfa_verify", {
mfaMethods,
smsAutoSent,
maskedPhone: memberOptions.mfa_phone,
});
}
// Member needs to enroll in MFA
return res.render("mfa_enrollment");
});
```
Your frontend should prompt the user to input the OTP they received and submit it with:
* `organization_id`: The Organization's ID
* `member_id`: The Member's ID
* `code`: The 6-digit OTP code
* `intermediate_session_token`: The token from primary authentication
Your frontend should prompt the user to input their TOTP code from their authenticator app and submit it with:
* `organization_id`: The Organization's ID
* `member_id`: The Member's ID
* `code`: The 6-digit TOTP code
* `intermediate_session_token`: The token from primary authentication
Similar to the last step of MFA enrollment, handle submission of the TOTP or OTP code in order to finish the MFA authentication and log the member in based on their default MFA method.
```python Python icon=python theme={null}
@app.route("/mfa/sms/authenticate", methods=["POST"])
def mfa_sms_authenticate():
intermediate_session_token = session.get("intermediate_session_token")
if not intermediate_session_token:
return "No intermediate session", 400
code = request.form.get("code")
resp = stytch_client.otps.sms.authenticate(
organization_id=session.get("organization_id"),
member_id=session.get("member_id"),
code=code,
intermediate_session_token=intermediate_session_token,
session_duration_minutes=60,
)
if resp.status_code != 200:
return "Error authenticating OTP", 500
session.pop("intermediate_session_token", None)
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
```
```javascript Node.js icon=node theme={null}
app.post("/mfa/sms/authenticate", async (req, res) => {
const intermediateSessionToken = req.session.intermediate_session_token;
if (!intermediateSessionToken) {
return res.status(400).send("No intermediate session");
}
const code = req.body.code;
const resp = await stytchClient.otps.sms.authenticate({
organization_id: req.session.organization_id,
member_id: req.session.member_id,
code: code,
intermediate_session_token: intermediateSessionToken,
session_duration_minutes: 60,
});
delete req.session.intermediate_session_token;
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
});
```
```python Python icon=python theme={null}
@app.route("/mfa/totp/authenticate", methods=["POST"])
def mfa_totp_authenticate():
intermediate_session_token = session.get("intermediate_session_token")
if not intermediate_session_token:
return "No intermediate session", 400
code = request.form.get("code")
resp = stytch_client.totps.authenticate(
organization_id=session.get("organization_id"),
member_id=session.get("member_id"),
code=code,
intermediate_session_token=intermediate_session_token,
session_duration_minutes=60,
)
if resp.status_code != 200:
return "Error authenticating TOTP", 500
session.pop("intermediate_session_token", None)
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
```
```javascript Node.js icon=node theme={null}
app.post("/mfa/totp/authenticate", async (req, res) => {
const intermediateSessionToken = req.session.intermediate_session_token;
if (!intermediateSessionToken) {
return res.status(400).send("No intermediate session");
}
const code = req.body.code;
const resp = await stytchClient.totps.authenticate({
organization_id: req.session.organization_id,
member_id: req.session.member_id,
code: code,
intermediate_session_token: intermediateSessionToken,
session_duration_minutes: 60,
});
delete req.session.intermediate_session_token;
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
});
```
Run your application and go through an authentication flow with a user who already has an MFA method enabled.
### Optional MFA enrollment
If an Organization's MFA policy is `OPTIONAL`, members can still opt to secure their account with MFA. At a high level, the flow for this is very similar to required enrollment, but can occur anytime after the Member has logged in and has been granted a Session.
Add a route for optional enrollment that expects a `session_jwt` or `session_token` before calling the MFA Authentication endpoint that finalizes the enrollment. This can be used to add optional MFA to users or additional MFA methods to users who want to use both TOTP and SMS.
```python Python icon=python theme={null}
@app.route("/optional-mfa-enrollment", methods=["POST"])
def optional_mfa_enrollment():
jwt = session.get("stytch_session_jwt")
if not jwt:
return "No session", 400
resp = stytch_client.sessions.authenticate_jwt(session_jwt=jwt)
if resp.status_code != 200:
return "Invalid session", 401
organization_id = resp.member_session.organization_id
member_id = resp.member_session.member_id
code = request.form.get("code")
resp = stytch_client.otps.sms.authenticate(
session_jwt=jwt,
code=code,
organization_id=organization_id,
member_id=member_id,
set_mfa_enrollment="enroll",
)
if resp.status_code != 200:
return "Error authenticating MFA", 500
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
```
```javascript Node.js icon=node theme={null}
app.post("/optional-mfa-enrollment", async (req, res) => {
const jwt = req.session.stytch_session_jwt;
if (!jwt) {
return res.status(400).send("No session");
}
const authResp = await stytchClient.sessions.authenticateJwt({
session_jwt: jwt,
});
const organizationId = authResp.member_session.organization_id;
const memberId = authResp.member_session.member_id;
const code = req.body.code;
const resp = await stytchClient.otps.sms.authenticate({
session_jwt: jwt,
code: code,
organization_id: organizationId,
member_id: memberId,
set_mfa_enrollment: "enroll",
});
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
});
```
```python Python icon=python theme={null}
@app.route("/optional-mfa-enrollment", methods=["POST"])
def optional_mfa_enrollment():
jwt = session.get("stytch_session_jwt")
if not jwt:
return "No session", 400
resp = stytch_client.sessions.authenticate_jwt(session_jwt=jwt)
if resp.status_code != 200:
return "Invalid session", 401
organization_id = resp.member_session.organization_id
member_id = resp.member_session.member_id
code = request.form.get("code")
resp = stytch_client.totps.authenticate(
session_jwt=jwt,
code=code,
organization_id=organization_id,
member_id=member_id,
set_mfa_enrollment="enroll",
)
if resp.status_code != 200:
return "Error authenticating MFA", 500
session["stytch_session_jwt"] = resp.session_jwt
return redirect(url_for("dashboard"))
```
```javascript Node.js icon=node theme={null}
app.post("/optional-mfa-enrollment", async (req, res) => {
const jwt = req.session.stytch_session_jwt;
if (!jwt) {
return res.status(400).send("No session");
}
const authResp = await stytchClient.sessions.authenticateJwt({
session_jwt: jwt,
});
const organizationId = authResp.member_session.organization_id;
const memberId = authResp.member_session.member_id;
const code = req.body.code;
const resp = await stytchClient.totps.authenticate({
session_jwt: jwt,
code: code,
organization_id: organizationId,
member_id: memberId,
set_mfa_enrollment: "enroll",
});
req.session.stytch_session_jwt = resp.session_jwt;
return res.redirect("/dashboard");
});
```
If the Member is enrolling in an additional form of MFA (e.g. has SMS MFA already, and is adding TOTP) you can also specify that the newly enrolled MFA method becomes the default through the `set_default_mfa` boolean.
If you want to enroll the user in MFA for occasional step-up authentication on particularly sensitive actions, rather than MFA that is required on each login you can omit the `set_mfa_enrollment` field in the `authenticate()` call.
# Adding MFA to Stytch Login
Source: https://stytch.com/docs/multi-tenant-auth/authentication/mfa/adding-mfa-to-stytch-login
Integrate MFA with Stytch's pre-built UI components.
## Prerequisites
Before you can add MFA to Stytch's pre-built UI, you'll need to complete the following steps:
Before integrating MFA, you need to already have a **primary authentication flow** built out. You can do so by following one of the integration guides:
To configure MFA, you'll need to first toggle on "Require MFA" for at least one Organization in the [Stytch Dashboard](https://stytch.com/dashboard/organizations), or call the [Update Organization API](/docs/api-reference/b2b/api/organizations/update-organization) with `mfa_policy` set to `REQUIRED_FOR_ALL`.
Each Organization is can specify which `mfa_methods` are allowed for Members in their Organization: either `ALL_ALLOWED` or `RESTRICTED`. If `RESTRICTED`, Members can only use MFA methods specified in the `allowed_mfa_methods` array.
For example, if an Organization requires Members to use TOTP MFA:
```json theme={null}
{
"mfa_policy": "REQUIRED_FOR_ALL",
"mfa_methods": "RESTRICTED",
// Optional, not enforced if mfa_methods is ALL_ALLOWED
"allowed_mfa_methods": ["totp"]
}
```
## Integrating MFA
The UI component handles MFA enrollment and authentication automatically, honoring both the Organization's MFA Policy and any optional Member enrollment.
You can adjust the defaults for which MFA options are shown and the order they are presented to the end user through the `mfaProductInclude` and the `mfaProductOrder` arrays in the UI config. However, the Organization's `allowed_mfa_methods` and the Member's current MFA enrollment options will take precedence.
```javascript theme={null}
var organizationConfig = {
authFlowType: "Organization",
mfaProductInclude: ['totp', 'smsOtp'],
mfaProductOrder: ['totp', 'smsOtp'],
...,
};
```
### Required MFA enrollment
When an Organization's MFA Policy is set to `REQUIRED_FOR_ALL` but the Member is not currently enrolled in MFA, the UI will automatically prompt the user to enroll after primary authentication, via the flow shown below.
* If neither the Organization nor UI config restrict the MFA methods available, the user will be presented with options ordered according to the `mfaProductOrder` array.
* If the Organization restricts MFA methods, the UI will surface that option — even if it is not explicitly included in the UI config.
* If the Organization does not restrict MFA methods and `mfaProductInclude` is explicitly passed in the UI config, the user will only be shown the options allowed by the config.
### Returning MFA authentication
For returning logins, Stytch will enforce MFA for Members who are enrolled in MFA, either as a requirement for the Organization or optionally, via the flow shown below.
* When a Member is already enrolled in MFA, Stytch will render the component for the Member's default MFA method.
* If the Member has multiple MFA options, they will see an option to switch to their non-default method instead.
# Overview
Source: https://stytch.com/docs/multi-tenant-auth/authentication/mfa/overview
Implement Stytch's MFA using SMS OTP or TOTP as secondary factors.
# Multi-Factor Authentication
**Multi-factor authentication (MFA)** enhances security by requiring users to provide two or more verification factors prior to accessing their account, which reduces the likelihood of account compromise.
Use the MFA API to manage multi-factor authentication on your backend.
Use the MFA SDKs to implement multi-factor authentication in your frontend.
## How it works
Stytch supports two different methods of secondary authentication:
1. **SMS one-time passcodes (OTPs)**. OTPs ask users to enter a unique numeric or alphanumeric code sent via SMS to a recognized mobile phone number.
2. **Authenticator app time-based one-time passcodes (TOTPs)**. TOTPs ask users to confirm control of their device within a certain time frame using a passcode generated by a smartphone app like Authy or Google Authenticator.
Stytch handles:
* Enforced enrollment in MFA based on the Organization's MFA policy, which determines whether MFA is optional or required and which methods are allowed.
* Optional enrollment in MFA, even if Organization does not require it.
* Enforcing that MFA requirements for the Member and Organization have been met prior to a Stytch session being issued.
## OTP considerations
### Cost
When using Stytch's SMS OTP product, Stytch will passthrough SMS costs from our upstream providers. You can see the pricing for each country by visiting [Country Code Allowlists](https://stytch.com/dashboard/country-code-allowlists) in the Stytch Dashboard. This is important to keep in mind as you plan costs and which countries you want to support as pricing can vary significantly by country.
### Country code allowlist
By default, a Stytch project will have only the United States (US) and Canada (CA) enabled for SMS OTP sends. To enable SMS OTP sends to other allowed countries, you can add the country to your country code allowlist in the [Stytch Dashboard](https://stytch.com/dashboard/country-code-allowlists) or via the [`country_code_allowlist endpoint`](/docs/api-reference/pwa/api/v3/country-code-allowlist/set-country-code-allowlist-sms).
Stytch does **not support** sending SMS passcodes to the following countries. If you attempt to add a country listed below to your country code allowlist, the API will return an `country_code_allowlist_invalid_country_codes` error.
| Country | Alpha-2 | Prefix |
| - | - | - |
| Algeria | DZ | +213 |
| Afghanistan | AF | +93 |
| Albania | AL | +355 |
| Andorra | AD | +376 |
| Angola | AO | +244 |
| Antarctica | AQ | +672 |
| Armenia | AM | +374 |
| Aruba | AW | +297 |
| Azerbaijan | AZ | +994 |
| Bahamas | BS | +1242 |
| Bahrain | BH | +973 |
| Bangladesh | BD | +880 |
| Barbados | BB | +1246 |
| Belarus | BY | +375 |
| Belize | BZ | +501 |
| Bermuda | BM | +1441 |
| Bhutan | BT | +975 |
| Bosnia and Herzegovina | BA | +387 |
| Botswana | BW | +267 |
| Bouvet Island | BV | +47 |
| British Virgin Islands | VG | +1284 |
| Burma (Myanmar) | MM | +95 |
| Burundi | BI | +257 |
| Cambodia | KH | +855 |
| Cape Verde | CV | +238 |
| Cayman Islands | KY | +1345 |
| Chad | TD | +235 |
| China – including Hong Kong | CN | +86 |
| Comoros | KM | +269 |
| Congo, Dem Rep | CD | +243 |
| Cote D'Ivoire (Ivory Coast) | CI | +225 |
| Cuba | CU | +53 |
| Curaçao and Caribbean Netherlands | CW | +599 |
| Cyprus | CY | +357 |
| Democratic Republic of Congo | CD | +243 |
| Djibouti | DJ | +253 |
| Dominica | DM | +1767 |
| East Timor | TL | +670 |
| Egypt | EG | +20 |
| Equatorial Guinea | GQ | +240 |
| Ethiopia | ET | +251 |
| Eritrea | ER | +291 |
| Estonia | EE | +372 |
| Faroe Islands | FO | +298 |
| Fiji | FJ | +679 |
| French Polynesia | PF | +689 |
| French Southern Territories (the) | TF | +262 |
| Gabon | GA | +241 |
| Gambia | GM | +220 |
| Georgia | GE | +995 |
| Gibraltar | GI | +350 |
| Greenland | GL | +299 |
| Guadeloupe | GP | +590 |
| Guam | GU | +1671 |
| Guinea | GN | +224 |
| Haiti | HT | +509 |
| Heard Island and McDonald Islands | HM | +672 |
| Honduras | HN | +504 |
| Indonesia | ID | +62 |
| Israel | IL | +972 |
| Iran | IR | +98 |
| Iraq | IQ | +964 |
| Ivory Coast | CI | +225 |
| Jersey | JE | +44 |
| Jordan | JO | +962 |
| Kazakhstan | KZ | +7 |
| Korea Dem People's Rep | KP | +850 |
| Kosovo | XK | +383 |
| Kuwait | KW | +965 |
| Kyrgyzstan | KG | +996 |
| Laos PDR | LA | +856 |
| Lebanon | LB | +961 |
| Lesotho | LS | +266 |
| Liberia | LR | +231 |
| Libya | LY | +218 |
| Macau | MO | +853 |
| Macedonia | MK | +389 |
| Madagascar | MG | +261 |
| Malawi | MW | +265 |
| Malaysia | MY | +60 |
| Maldives | MV | +960 |
| Mali | ML | +223 |
| Martinque | MQ | +596 |
| Micronesia | FM | +691 |
| Moldova | MD | +373 |
| Mongolia | MN | +976 |
| Montserrat | MS | +1664 |
| Morocco | MA | +212 |
| Mozambique | MZ | +258 |
| Namibia | NA | +264 |
| Niue | NU | +683 |
| Nepal | NP | +977 |
| New Caledonia | NC | +687 |
| Niger | NE | +227 |
| Nigeria | NG | +234 |
| North Korea | KP | +850 |
| Northern Mariana Islands | MP | +1670 |
| Oman | OM | +968 |
| Pakistan | PK | +92 |
| Palestine | PS | +970 |
| Papua New Guinea | PG | +675 |
| Philippines | PH | +63 |
| Pitcairn | PN | +870 |
| Qatar | QA | +974 |
| Republic of North Macedonia | MK | +389 |
| Reunion/Mayotte | RE | +262 |
| Russia | RU | +7 |
| Rwanda | RW | +250 |
| Samoa | WS | +685 |
| Saudi Arabia | SA | +966 |
| Senegal | SN | +221 |
| Serbia | RS | +381 |
| Sierra Leone | SL | +232 |
| Singapore | SG | +65 |
| Solomon Islands | SB | +677 |
| Somalia | SO | +252 |
| South Georgia and the South Sandwich Islands | GS | +500 |
| Sri Lanka | LK | +94 |
| St Vincent Grenadines | VC | +1784 |
| Sudan | SD | +249 |
| Syria | SY | +963 |
| Tajikistan | TJ | +992 |
| Thailand | TH | +66 |
| Togo | TG | +228 |
| Tonga | TO | +676 |
| Tunisia | TN | +216 |
| Tuvalu | TV | +688 |
| Turkmenistan | TM | +993 |
| Turks and Caicos Islands | TC | +1649 |
| U.S. Virgin Islands | VI | +1340 |
| United Arab Emirates | AE | +971 |
| Uganda | UG | +256 |
| Uzbekistan | UZ | +998 |
| Vanuatu | VU | +678 |
| Venezuela | VE | +58 |
| Vietnam | VN | +84 |
| Wallis and Futuna | WF | +681 |
| Yemen | YE | +967 |
| Zambia | ZM | +260 |
| Zimbabwe | ZW | +263 |
| United States Minor Outlying Islands (the) | UM | |
For more information about SMS deliverability and best practices, see our [SMS and WhatsApp deliverability guide](/docs/resources/policies/messaging/deliverability/sms-whatsapp-deliverability).
***
Learn how to use Stytch's Device Fingerprinting (DFP) product to implement a remembered device flow, a form of adaptive MFA, to trigger MFA only on unrecognized logins.
# Adding OAuth to Stytch Login
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-oauth-to-stytch-login
Integrate OAuth with Stytch's pre-built UI components.
## Prerequisites
If you haven't done so already:
1. Complete the steps in the [OAuth configuration guide](/docs/multi-tenant-auth/authentication/oauth/configuring-oauth-for-your-project)
2. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration) and enable Create Organizations under "Enabled Methods."
## Add OAuth providers to your UI config
To surface the OAuth providers you wish to support in the UI, add them to the UI config for your authentication flow.
```jsx React icon="react" theme={null}
import { B2BProducts, StytchB2B } from '@stytch/react/b2b';
const Login = () => {
const config = {
authFlowType: "Discovery", // or "Organization"
products: [B2BProducts.oauth],
oauthOptions: {
providers: [{ type: 'google' }, { type: 'microsoft' }]
},
};
return ;
};
export default Login;
```
```jsx Next.js icon="triangle" theme={null}
import { B2BProducts, StytchB2B } from '@stytch/nextjs/b2b';
const Login = () => {
const config = {
authFlowType: "Discovery", // or "Organization"
products: [B2BProducts.oauth],
oauthOptions: {
providers: [{ type: 'google' }, { type: 'microsoft' }]
},
};
return ;
};
export default Login;
```
```html Vanilla JS icon="square-js" theme={null}
```
You can add multiple OAuth providers by including additional provider objects in the `providers` array. See the [OAuth providers guide](/docs/multi-tenant-auth/authentication/oauth/adding-providers/google-oauth) for more information on configuring specific providers.
## Render the UI component
Render the Stytch UI component on the Redirect URL(s) that you specified in the [Stytch Dashboard](https://stytch.com/dashboard/redirect-urls) during the configuration steps.
When rendered, the UI component will automatically:
* Handle the OAuth redirect from Stytch based on the `stytch_token_type`
* For Discovery flows, prompt the user with their discovered organizations and handle their selection
* For Organization-specific flows, log the user in directly
## Test your integration
Start your application and test the OAuth flow:
1. Navigate to your login page
2. Click on an OAuth provider button (e.g., "Continue with Google")
3. Complete the OAuth flow with the provider
4. Verify that you're redirected back to your application and authenticated
Use test accounts for your OAuth providers during development to avoid affecting production data.
## Next steps
Learn how to customize the appearance and behavior of the pre-built UI
Configure additional OAuth providers like GitHub, Slack, or HubSpot
# Adding PKCE to your OAuth flow
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-pkce
Secure your OAuth login flow with PKCE
[Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636) is a specification that builds on OAuth 2.0 to create a more secure login flow.
In the original OAuth authorization code flow, an IdP issues a one-time use code to the app, which is exchanged along with a `client_id` and `client_secret` for an auth token. For certain types of apps (i.e. SPAs, mobile apps, or native apps) there was no mechanism to hide the `client_secret`, which must be included in the app's source code and reused on every request. This meant that an attacker would be able to impersonate a user if the attacker gained access to the one-time use code. Codes in a URL can be leaked in many different ways - either through improper logging, HTTP Referrer headers, or even through another exploit like XSS.
PKCE introduces a one-time secret for each authorization flow. This secret, called a `code_verifier`, is generated and stored by the app on the user's device. Before the user is redirected to the IdP, the verifier is hashed to produce a `code_challenge`. This challenge is passed as a query parameter during the authentication flow, e.g. `/oauth/authenticate?code_challenge=E9Mel....`
After the IdP has validated the user's credentials, the user is redirected back to the app with a secure code. Now, the app must submit the code along with the original `code_verifier`. The IdP hashes the submitted `code_verifier` and validates that it matches the `code_challenge` passed as a query parameter earlier. This check ensures that the token has not been leaked, and that it is being handled by the same user that was seen at the start of the flow.
This guide will show you how to add PKCE support to your Stytch OAuth integration for added security. A PKCE integration is required for applications that use native callback URLs, for example `appname://some/callback` on mobile. Stytch's support for PKCE differs slightly from the original specification - for example we do not permit plain verifiers to be used - but the original idea of strengthening app security with a unique secret on every request remains the same.
If you get stuck while working through this guide, feel free to ask questions by [contacting us](/docs/resources/get-support).
Enable PKCE for all OAuth logins performed by the JavaScript SDK. By flipping this setting, you tell the Javascript SDK to use PKCE for all UI components, and to reject any OAuth logins that do not use PKCE. In order to do so, toggle the option in the [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration) of the Stytch Dashboard. This makes your application resistant to attacks like [Login CSRF](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#login-csrf) in which an attacker can trick a victim into logging in to the wrong account.
The legacy `oauth.$provider.getURL()` method is not compatible with PKCE. If you are using this method already in your codebase, complete Step 2 before enabling PKCE.
Call one of the `oauth.$provider.start()` methods to start the OAuth flow. The SDK will generate a `code_verifier` and store it in local storage before redirecting the user to Stytch's OAuth start endpoint. If you use the SDK's built-in OAuth UI, this step is already handled for you.
```jsx theme={null}
function OAuthStartButton() {
const stytch = useStytch();
const startOAuthFlow = useCallback(() => stytch.oauth.google.start({
login_redirect_url: 'https://example.com/callback',
signup_redirect_url: 'https://example.com/callback'
}), [stytch])
return (
);
}
```
At the route that the user is redirected to after the OAuth flow, pull the token out of the URL params and call `oauth.authenticate(token)` to exchange the token for a Stytch session. The SDK will automatically retrieve the `code_verifier` created earlier from local storage and pass it on to the Stytch servers.
```jsx theme={null}
function OAuthCallback() {
const stytch = useStytch();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
if(!token) {
// TODO: Handle redirect back to logged-out view
}
stytch.oauth.authenticate(token, {
session_duration_minutes: 60,
}).then(() => {
// TODO: Redirect to final logged-in view
})
}, [stytch]);
return (
Authenticating....
);
}
```
Create a `code_verifier` and `code_challenge` pair. To create the `code_verifier`, generate at least 256 random bits of entropy. This can be done by having a suitable random number generator create a 32-octet sequence. To create the `code_challenge`, hash the `code_verifier` using SHA256. Finally, base64url encode both to produce ASCII strings.
```javascript theme={null}
const pkceChallenge = require("pkce-challenge")
const { code_verifier, code_challenge } = pkceChallenge();
console.log('code_verifier', code_verifier)
console.log('code_challenge', code_challenge)
```
You'll need to associate the `code_verifier` created in the previous step with the user's device before starting the OAuth flow. One common way to do this is to set the `code_verifier` as an HTTP Only cookie. The exact mechanism to set cookies and redirect the user will depend on the web framework you are using. Be sure to include the `code_challenge` as a URL query param sent to Stytch!
```text theme={null}
< HTTP/2 302
< content-type: application/json
< location: https://api.stytch.com/v1/public/oauth/google/start?public_token=PUBLIC_TOKEN&code_challenge=$CODE_CHALLENGE
< set-cookie: code_verifier=$CODE_VERIFIER; Path=/; HttpOnly; Secure; SameSite=None
```
After the user is redirected back from Stytch with a valid token, you'll need to exchange the token along with the original `code_verifier` to complete the flow and be issued a valid Stytch session. Retrieve the `code_verifier` from the cookie where it was stored in the previous step, and send it to Stytch's OAuth authenticate endpoint.
```javascript theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
});
client.oauth.authenticate("SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4=", {
code_verifier: code_verifier
})
.then(resp => { console.log(resp) })
.catch(err => { console.log(err) });
```
You just finished all the critical components to authenticate your users via OAuth with PKCE! Have any feedback after integrating? Get in touch with us at [support@stytch.com](mailto:support@stytch.com).
# GitHub
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-providers/github
Configure GitHub OAuth for Multi-Tenant Authentication.
## Create a new GitHub OAuth client
Go to [GitHub OAuth Applications](https://github.com/settings/developers). For more information, check out GitHub's [Creating an OAuth App](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) guide.
Click **New OAuth App** or **Register a new application** depending on whether you have other applications or not.
Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it into the **Authorization callback URL** field. Fill in the remaining fields as needed.
## Configure your GitHub OAuth client in Stytch
Go to [GitHub OAuth Applications](https://github.com/settings/developers). If you're using an organization OAuth Application, navigate to the organization's **Developer Settings** tab.
This requires admin privileges for organization applications.
Click on the application you'd like to use with Stytch.
Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it into the **Authorization callback URL** field.
# Google OAuth
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-providers/google-oauth
Configure Google OAuth for Multi-Tenant Authentication.
## Create a new Google OAuth client
Navigate to the [Google Cloud Console](https://console.cloud.google.com/) and create a Google Cloud account and a new project if you have not already done so. For more information, see [Create a Google Cloud account and project](https://support.google.com/googleapi/answer/6251787#zippy=%2Ccreate-a-project).
Navigate to the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) and configure your OAuth consent screen.
[Go to the Google Credentials page](https://console.cloud.google.com/apis/credentials) for your application. Click on **CREATE CREDENTIALS** and then select **OAuth Client ID**.
Select **Web Application** as the application type (regardless of if it is a mobile or non-web application) and enter a name for your OAuth client.
Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it into the **Authorized redirect URIs** section.
Click **create** to save the configuration.
If you want the Stytch SDK to support Google One Tap, add your application's URI as an **Authorized JavaScript Origin**.
## Configure your Google OAuth client in Stytch
Go to the [Google Credentials page](https://console.cloud.google.com/apis/credentials) for your application. You should see your OAuth client under the **OAuth 2.0 Client IDs** section.
Click on the OAuth client to edit its configuration.
Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it into the **Authorized redirect URIs** section.
Click to save the configuration.
If you want the Stytch SDK to support Google One Tap, add your application's URI as an **Authorized JavaScript Origin**.
# Google One Tap
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-providers/google-one-tap
Configure Google One Tap as an OAuth provider for Multi-Tenant Authentication.
Google One Tap is a popular, low-friction authentication method that allows end users to select the Google account they wish to use to sign in with a single tap.
The Google One Tap prompt will appear in the top right of the end user's browser, and will look something like this:
## Summary of Key Differences
For a quick overview of the differences between Google OAuth and Google One Tap, see the table below:
| | **Standard Google OAuth** | **Google One Tap** |
| - | - | - |
| **Issued OAuth Tokens** | `access_token` and `refresh_token` | None (use standard OAuth if tokens are needed) |
| **Available Stytch Integration Methods** | Backend, Headless Frontend, Pre-built UI | Headless Frontend, Pre-built UI |
| **Supported Browsers** | All | Not supported in Arc or Brave; blocked by certain browser extensions |
| **Headless SDK Reference Docs** | [Discovery sign-up and login](/docs/api-reference/b2b/frontend-sdks/react/methods/oauth/discovery-start.mdx) and [Organization login](/docs/api-reference/b2b/frontend-sdks/react/methods/oauth/start.mdx) | [Discovery sign-up and login](/docs/api-reference/b2b/frontend-sdks/react/methods/oauth/discovery-start.mdx) and [Organization login](/docs/api-reference/b2b/frontend-sdks/react/methods/oauth/google-one-tap.mdx) |
## Adding One Tap to your Google OAuth integration
You'll first need to configure Google OAuth in the Stytch Dashboard, by following the steps [here](/docs/multi-tenant-auth/authentication/oauth/adding-providers/google-oauth).
Ensure you add your application's URI as an Authorized Javascript Origin in your Google OAuth credential. For example, if you plan on showing One Tap at `https://example.com/login`, enter `https://example.com`. If you're using localhost for testing, we recommend adding both `http://localhost` and `http://locahost:[port]`.
### Headless frontend integration
If you don't already have Google OAuth set up in the headless SDK, follow the steps [here](/docs/multi-tenant-auth/authentication/oauth/adding-to-custom-auth-flow#headless-frontend-sdk).
To use Google One Tap for the Discovery flow, call the `oauth.googleOneTap.discovery.start()` method to display the Google One Tap prompt in the upper right of the user's browser tab.
The user is redirected to the Discovery Redirect URL you configured in the [Stytch Dashboard](https://stytch.com/dashboard/redirect-urls) after a successful One Tap selection.
The examples below show how to display One Tap in response to a user action (button click). You can also show it on page load.
```tsx React icon="react" theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const GoogleOneTapDiscovery = () => {
const stytchClient = useStytchB2BClient();
const showGoogleOneTapDiscovery = () =>
stytchClient.oauth.googleOneTap.discovery.start();
return ;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
To prompt with One Tap for an Organization, call `oauth.googleOneTap.start()`.
After a successful One Tap selection, Stytch redirects to the Login or Signup Redirect URL configured in the Dashboard.
```jsx React SDK theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const GoogleOneTap = () => {
const stytchClient = useStytchB2BClient();
const showGoogleOneTap = () =>
stytchClient.oauth.googleOneTap.start({
organization_id: '${organizationId}',
});
return ;
};
```
```javascript Vanilla JS SDK theme={null}
```
### Pre-built UI frontend integration
You'll first need to have Google OAuth set up in the pre-built UI components, following the steps [here](/docs/multi-tenant-auth/authentication/oauth/overview).
To enable One Tap, include `one_tap` in your `providers` list in the OAuth UI configuration.
If the only product is `oauth` and the only provider is `google` with `one_tap: true`, the login form is not displayed and only the One Tap prompt will appear in the upper right of the user's browser.
Google One Tap is not supported on all browsers and may be blocked by certain extensions. If Google OAuth is your only login method, we recommend using One Tap in targeted locations rather than on your primary login page.
```tsx React icon="react" theme={null}
import { B2BProducts, StytchB2B } from '@stytch/react/b2b';
const Login = () => {
const config = {
authFlowType: 'Discovery', // or "Organization"
products: [B2BProducts.oauth],
oauthOptions: {
providers: [{ type: 'google', one_tap: true }],
},
};
return ;
};
export default Login;
```
```html Vanilla JS icon="square-js" theme={null}
```
# HubSpot
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-providers/hubspot
Configure HubSpot OAuth for Multi-Tenant Authentication.
## Create a new HubSpot OAuth client
Create or navigate to your [HubSpot developer account](https://developers.hubspot.com/).
Go to the **Apps** tab and click **Create app**.
In the **Auth** tab of the App creation screen, scroll down to **Redirect URLs**. Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it to populate a Redirect URL in the App configuration.
Complete the creation of your App by clicking **Create App** at the bottom of the page.
## Configure your HubSpot OAuth client in Stytch
Go to your HubSpot account, navigate to the **Apps** tab, and click on your app.
Click on the **Auth** tab and scroll down to **Redirect URLs**.
Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it to populate a Redirect URL in the App configuration.
# Microsoft
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-providers/microsoft
Configure Microsoft OAuth for Multi-Tenant Authentication.
The Stytch B2B Microsoft OAuth product limits access to only **verified Azure Active Directory accounts**. Personal accounts, including personal Xbox Live, Skype, or Outlook accounts, are not allowed. Accounts managed by Azure Active Directory B2C are not allowed.
By default, Stytch will deduplicate users by treating their `UserPrincipalName` as an email address.
## Create a new Microsoft OAuth client
If you don't have one already, create a [Microsoft Azure account](https://login.microsoftonline.com/).
Go to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) to create a Microsoft application.
Click **New registration**.
Enter a name for your OAuth client.
Select **Accounts in any organizational directory (Any Azure AD directory - Multitenant)**.
Under the **Redirect URI** section, select **Web** and then copy the Stytch redirect URI from the [OAuth page](https://stytch.com/dashboard/oauth) and paste it into the URI field.
Click **Register** to save the configuration.
## Configure your Microsoft OAuth client in Stytch
Go to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) and select your Microsoft application.
In your OAuth client configuration, select the link next to the **Redirect URIs** section. Then select **Add a platform** and finally select **Web**.
Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it into the URI field.
Click **Configure** to save the URI.
# Slack
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-providers/slack
Configure Slack OAuth for Multi-Tenant Authentication.
## Create a new Slack OAuth client
Go to [Slack OAuth Applications](https://api.slack.com/apps). For more information, check out Slack's [Creating an OAuth App](https://api.slack.com/authentication/oauth-v2) guide.
Select **Create New App** and choose to create it **From scratch**. Enter your desired App Name and Slack Workspace.
You will be redirected to the configuration page for your new App. On the left-hand sidebar, navigate to **OAuth & Permissions**.
Scroll down to **Redirect URLs** and click **Add New Redirect URL**. Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it, then click **Save URLs**.
In the **Scopes** section under **User Token Scopes**, add at least the following scopes: `users:read`, `users:read.email`.
## Configure your Slack OAuth client in Stytch
Go to [Slack OAuth Applications](https://api.slack.com/apps) and select the OAuth App you will be using.
On the left-hand sidebar, navigate to **OAuth & Permissions**.
Scroll down to **Redirect URLs** and click **Add New Redirect URL**. Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it, then click **Save URLs**.
In the **Scopes** section under **User Token Scopes**, ensure at least the following scopes are added: `users:read`, `users:read.email`.
# Adding OAuth to a custom auth flow
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/adding-to-custom-auth-flow
Integrate OAuth with Stytch's headless or backend SDKs for complete control over your authentication flow.
Before you begin, make sure you've completed the [OAuth configuration steps](/docs/multi-tenant-auth/authentication/oauth/configuring-oauth-for-your-project).
## Overview
This guide walks through integrating OAuth using Stytch's headless frontend SDKs. This approach gives you complete control over your UI while handling authentication logic on the frontend.
Both Discovery and Organization-specific authentication flows are supported.
The Discovery flow is designed for situations where end users are signing up or logging in from a central landing page, and have not specified which Organization they are trying to access.
### Prerequisites
1. Complete the steps in the [OAuth configuration guide](/docs/multi-tenant-auth/authentication/oauth/configuring-oauth-for-your-project)
2. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)
3. Enable Create Organizations under "Enabled Methods"
### Implementation
In your application's UI, use `oauth.discovery.$provider.start()` to allow users to begin the OAuth Discovery flow.
```jsx React icon="react" theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Login = () => {
const stytchClient = useStytchB2BClient();
const startOAuth = () =>
stytchClient.oauth.google.discovery.start();
return ;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
Stytch will send a callback to the Discovery Redirect URL you specified in the [Stytch Dashboard](https://stytch.com/dashboard/redirect-urls). Exchange the token for a list of Discovered Organizations that the user can choose to log into.
```jsx React icon="react" theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient, useStytchMemberSession } from '@stytch/react/b2b';
import { useNavigate } from 'react-router';
export const Authenticate = () => {
const stytchClient = useStytchB2BClient();
const { session } = useStytchMemberSession();
const navigate = useNavigate();
useEffect(() => {
if (session) {
navigate('/profile');
} else {
stytchClient.authenticateByUrl({
session_duration_minutes: 60,
});
}
}, [stytchClient, session, navigate]);
return
Loading...
;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
An intermediate\_session\_token will be returned. The headless SDK will automatically set this in cookies and include it in follow-up calls.
When the end user selects an Organization to log into, call the exchange intermediate session method with the selected Organization ID.
```jsx React icon="react" theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const ExchangeIntermediateSession = () => {
const stytch = useStytchB2BClient();
useEffect(() => {
stytch.discovery.intermediateSessions.exchange({
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931'
});
});
return
Log In
;
};
```
```javascript Vanilla JS icon="square-js" theme={null}
import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
const stytch = new StytchB2BClient('STYTCH_PUBLIC_TOKEN');
const exchangeIntermediateSession = () => {
stytch.discovery.intermediateSessions.exchange({
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931'
});
};
```
Allow users to create a new Organization instead of logging into an existing one.
```jsx React icon="react" theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const CreateOrganization = () => {
const stytch = useStytchB2BClient();
useEffect(() => {
stytch.discovery.organizations.create();
});
return
Create Organization
;
};
```
```javascript Vanilla JS icon="square-js" theme={null}
import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
const stytch = new StytchB2BClient('STYTCH_PUBLIC_TOKEN');
const createOrganization = () => {
stytch.discovery.organizations.create();
};
```
You can optionally prompt the user for the name and slug of their new Organization. If not provided, Stytch will auto-generate them based on the end user's email address.
If end users login via a page that indicates which Organization they're trying to access (e.g., `.your-app.com` or `your-app.com/team/`), you can offer Organization-specific authentication.
### Prerequisites
1. Complete the steps in the [OAuth configuration guide](/docs/multi-tenant-auth/authentication/oauth/configuring-oauth-for-your-project)
2. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)
### Implementation
Use the `oauth.$provider.start()` method to initiate the OAuth flow for a specific Organization.
```jsx React icon="react" theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Login = () => {
const stytchClient = useStytchB2BClient();
const startOAuth = () =>
stytchClient.oauth.google.start({
slug: 'your-org-slug'
});
return ;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
Stytch will redirect the user to the Login or Signup Redirect URL. Extract the token from the URL and call the authentication method to finish the login process.
```jsx React icon="react" theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient, useStytchMemberSession } from '@stytch/react/b2b';
import { useNavigate } from 'react-router';
export const Authenticate = () => {
const stytchClient = useStytchB2BClient();
const { session } = useStytchMemberSession();
const navigate = useNavigate();
useEffect(() => {
if (session) {
navigate('/profile');
} else {
stytchClient.authenticateByUrl({
session_duration_minutes: 60,
});
}
}, [stytchClient, session]);
return
Loading
;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
This guide walks through integrating OAuth using Stytch's backend SDKs. This approach handles all authentication logic on your backend, giving you complete control over the user experience.
Both Discovery and Organization-specific authentication flows are supported.
The Discovery flow is designed for situations where end users are signing up or logging in from a central landing page, and have not specified which Organization they are trying to access.
### Prerequisites
Complete the steps in the [OAuth configuration guide](/docs/multi-tenant-auth/authentication/oauth/configuring-oauth-for-your-project)
### Implementation
Stytch will make a callback to the Discovery Redirect URL that you specified in the [Stytch Dashboard](https://stytch.com/dashboard/redirect-urls). Handle the callback and authenticate the token.
```python Python icon="python" theme={null}
@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 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
)
```
On your frontend, render the list of discovered organizations and allow users to select one. Call the endpoint we'll setup in the next step with the selected `organization_id`.
Create routes to handle logging into an existing Organization or creating a new Organization.
```python theme={null}
@app.route("/login/", 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()
```
Test the OAuth flow by navigating to the following URL in your browser. Replace `{provider}` with "google" or "microsoft" and `{public_token}` with your Public Token from the [Stytch Dashboard](https://stytch.com/dashboard).
```bash theme={null}
https://test.stytch.com/v1/public/oauth/{provider}/discovery/start?public_token={public_token}
```
This will automatically redirect your browser to the OAuth provider to start the flow.
On your frontend, give users the option to start an OAuth flow with a specific provider. For example, if you want to support Google and Microsoft OAuth:
* Add a "Login with Google" button that links to `${apiBase}/v1/public/oauth/google/discovery/start?public_token=${public_token}`
* Add a "Login with Microsoft" button that links to `${apiBase}/v1/public/oauth/microsoft/discovery/start?public_token=${public_token}`
If end users login via a page that indicates which Organization they're trying to access (e.g., `.your-app.com` or `your-app.com/team/`), you can offer Organization-specific authentication.
### Prerequisites
Complete the steps in the [OAuth configuration guide](/docs/multi-tenant-auth/authentication/oauth/configuring-oauth-for-your-project), including creating an Organization.
### Implementation
Stytch will make a callback to the Login or Signup Redirect URL. Handle the callback and authenticate the token.
```python Python icon="python" theme={null}
@app.route("/authenticate", methods=["GET"])
def authenticate() -> str:
token_type = request.args["stytch_token_type"]
if token_type == "oauth":
resp = stytch_client.oauth.authenticate(oauth_token=request.args["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()
```
Test the OAuth flow by navigating to the following URL. Replace `{provider}` with "google" or "microsoft", and use your Public Token and Organization slug.
```bash theme={null}
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.
Create a UI that allows end users to identify the Organization they wish to log into and surface all allowed authentication methods.
```python theme={null}
@app.route("/org/", 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
)
```
On your frontend, give users the option to start an OAuth flow with a specific provider. For example, if you want to support Google and Microsoft OAuth:
* Add a "Login with Google" button that links to `${apiBase}/v1/public/oauth/google/discovery/start?slug=${organization_slug}&public_token=${public_token}`
* Add a "Login with Microsoft" button that links to `${apiBase}/v1/public/oauth/microsoft/discovery/start?slug=${organization_slug}&public_token=${public_token}`
# Configuring OAuth for your project
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/configuring-oauth-for-your-project
Get started by configuring your OAuth settings in the Stytch Dashboard.
To use Stytch's OAuth product, you'll first need to complete the following configuration steps in the [Stytch Dashboard](https://stytch.com/dashboard/):
Note the Project Domain from the Project Overview page. You will need this later when setting up your OAuth provider and adjusting your DNS entries.
Navigate to the [OAuth section](https://stytch.com/dashboard/oauth), select the OAuth identity provider of your choice and click **Configure**.
Once in the provider configuration, we recommend clicking the **Setting up OAuth with \[provider]** button to streamline the configuration of your OAuth client.
Be sure to add the **Stytch redirect URI** (`https://{test|live}.stytch.com/v1/b2b/oauth/callback/...`) as an authorized redirect URI in your OAuth provider's settings. This is where end users will be initially redirected after they authenticate with the provider.
On the [Redirect URLs page](https://stytch.com/dashboard/redirect-urls), you'll need to configure the URL where the user will be redirected after a successful OAuth authentication.
By default, all new projects have redirect URLs set to `http://localhost:3000/authenticate` for Test environments.
If you want to use a different port, update the Dashboard to specify new default Redirect URLs for:
* **Sign-up and Login Redirect URL** (for organization-specific login)
* **Discovery Redirect URL** (for discovery authentication)
You can get started with the Discovery sign-up or login flow without this step, so feel free to skip to Step 5!
If you want to start with an example of logging into an existing Organization, you can do so by creating an Organization through the [Stytch Dashboard](https://stytch.com/dashboard) or [Create Organization API](/docs/api-reference/b2b/api/organizations/create-organization).
###### JIT Provisioning by Email Domain
If you are using **Google** or **Microsoft** as OAuth providers, and will be testing using a corporate email, you can allow Just-In-Time (JIT) Provisioning to the Organization via email domain by including your domain in the `email_allowed_domains` array and setting `email_jit_provisioning` to `RESTRICTED`.
```json theme={null}
{
"organization_id": "${organizationId}",
"email_allowed_domains": [
"stytch.com"
],
"email_jit_provisioning": "RESTRICTED"
}
```
Just-in-Time (JIT) Provisioning by email domain is restricted for common, free email providers (e.g. gmail.com). If you don't have a corporate email, you can alternatively create a Member in your new Organization with the email you will use to log in.
You can create a Member in the Dashboard by navigating to the Organization and clicking "Create new member" or by calling the [Create Member API](/docs/api-reference/b2b/api/members/create-member).
###### JIT Provisioning by Tenant ID
If you are using **Slack**, **HubSpot**, or **GitHub** as OAuth providers, you can allow JIT Provisioning to the Organization by tenant ID. See our [JIT Provisioning guide](/docs/multi-tenant-auth/enterprise-ready/org-management/jit-provision-members#by-oauth-tenant) for additional information.
Learn more about [step-up flows for Slack/HubSpot/GitHub OAuth](/docs/multi-tenant-auth/authentication/oauth/overview#understanding-step-up-requirements).
Follow one of the guides below to get an end-to-end OAuth flow up and running:
If you want to get up and running with as little code as possible, choose the **pre-built UI** option. If you want fine-grained control over your authentication flow, choose the **custom auth flow** option.
Integrate with Stytch's frontend UI components
Build a custom OAuth flow with full control
# Overview
Source: https://stytch.com/docs/multi-tenant-auth/authentication/oauth/overview
Use OAuth to allow Members to login to your application through their authenticated identity on common OAuth providers.
## API resources
Use the OAuth API to start an OAuth flow.
Use the OAuth SDKs to start an OAuth flow.
Use Stytch's pre-built UI components to seamlessly add OAuth to your login flow.
## Authentication flows
OAuth, otherwise known as Social Login or Social SSO, is an easy way for end users to sign-up and login to your application through their authenticated identity on common OAuth providers, such as Google or Microsoft.
Visually, the OAuth flow looks like this:
```mermaid theme={null}
sequenceDiagram
participant Frontend as YOUR FRONTEND
participant Backend as YOUR BACKEND
participant Stytch as STYTCH
participant IDP as IDP (e.g., Google)
Frontend->>Stytch: User initiates login
Stytch->>IDP: Redirect to IdP
IDP->>IDP: User completes OAuth flow
IDP->>Stytch: Redirect to Stytch OAuth callback
Stytch->>Backend: Redirect to your redirect URL
Backend->>Backend: Receive token
Backend->>Stytch: Send OAuth token via /oauth/authenticate
Stytch->>Backend: Return Stytch session
Backend->>Frontend: Return session
```
### Discovery vs. Organization-specific login
Stytch's Multi-Tenant Authentication product supports two different OAuth authentication flows:
Used when you already know the Organization that the end user is trying to log into.
Used for self-serve Organization creation or login prior to knowing the Organization context.
Both flows support OAuth, allowing end users to accept pending invites or JIT Provisioning by email domain, and finish with the end user authenticated in a specific Organization. However, Discovery involves one additional step to surface the end user's "discovered organizations" that they are eligible to login to and also allows self-serve organization creation.
#### Key differences
| | **Discovery** | **Organization-Specific** |
| - | - | - |
| **OAuth Start Initiated With** | `public_token` | `public_token` and `organization_id` OR `slug` |
| **Redirect URL Used for Callback** | Discovery | Login or Sign-up |
| **Stytch Token Type** | `discovery_oauth` | `oauth` |
| **Authenticate Method** | `oauth.discovery.authenticate()` | `oauth.authenticate()` |
| **Access and Refresh Token** | Retrieve via [Get Access Token APIs](/docs/api-reference/b2b/api/oauth/token/google) after organization selection | Returned in `oauth.authenticate()` response, or can retrieve via [Get Access Token APIs](/docs/api-reference/b2b/api/oauth/token/google) |
### Just-in-Time provisioning
#### Enabling JIT provisioning for OAuth tenants
If your project is configured to use Slack, HubSpot, or GitHub as identity providers for OAuth, organizations can enable Just-in-Time (JIT) Provisioning of end users based on **tenant ID**. A tenant ID is a unique identifier for a subgroup within one of these OAuth providers:
* Slack's **Workspace ID**
* HubSpot's **Hub ID**
* GitHub's **organization ID**
An end user who completes a Discovery OAuth flow where the tenant ID of the provider they authenticated with matches a tenant ID in an organization's Allowed Tenants list will have that organization surfaced in their Discovery response and will subsequently be allowed to authenticate into that organization.
See instructions for finding your provider's tenant ID in our [JIT Provisioning guide](/docs/multi-tenant-auth/enterprise-ready/org-management/jit-provision-members#determining-tenant-ids).
If the tenant ID does not match any of the organization's Allowed Tenants, the end user will have to undergo further authentication of their email (i.e. a **step-up**) before being permitted to perform certain actions in Stytch, such as JIT provisioning by email domain or creating a new organization.
#### Understanding step-up requirements
If the tenant ID of the provider matches a tenant ID in the organization's Allowed Tenants, the end user will be fully authenticated after completing an OAuth flow.
If the tenant ID of the provider does not match any of the Allowed Tenants, but an end user is still eligible to join the organization via their email domain, an end user wishing to authenticate using Slack, HubSpot, or GitHub as identity providers are required to do a "step-up" form of primary authentication (such as [Email Magic Links](/docs/api-reference/b2b/api/email-magic-links/organization/send-login-or-signup-email), [Email OTP](/docs/api-reference/b2b/api/email-otp/organization/send-login-or-signup-otp), [Google OAuth](/docs/api-reference/b2b/api/oauth/organization/start-google), or [Microsoft OAuth](/docs/api-reference/b2b/api/oauth/organization/start-microsoft)) before being able to receive a full session and fully authenticate as a Member.
Additionally, Google and Microsoft are non-authoritative about the user's active ownership of their email address for some account types, and in these cases the user must also complete step-up authentication (such as [Email Magic Links](/docs/api-reference/b2b/api/email-magic-links/organization/send-login-or-signup-email) or [Email OTP](/docs/api-reference/b2b/api/email-otp/organization/send-login-or-signup-otp)) before being granted a full session.
Active Members (Members that have been pre-created in an Organization or have previously completed step-up authentication) and Members whose tenant ID match the Organization's Allowed Tenants are allowed to sign-in via these providers without undergoing any step-up requirements.
When an end user authenticates via OAuth and opts to JIT Provision into an organization they are eligible to join based on the email domain returned, fields are provided in the [OAuth authenticate response](/docs/api-reference/b2b/api/oauth/organization/authenticate) that inform the developer whether an end user needs to step-up and how to satisfy the step-up requirement:
* `member_authenticated`: will be `false` to indicate that the end user requires step-up authentication.
* `primary_required`: details actions that the Member must take in order to meet the step-up requirement for the Organization in question.
* `intermediate_session_token`: this will be populated with the same token the end user received when completing OAuth authentication.
# Cross-Organization Passwords
Source: https://stytch.com/docs/multi-tenant-auth/authentication/passwords/cross-org
Share Passwords across Organizations.
## Cross-Organization vs Organization-scoped Passwords
Stytch offers two different approaches to passwords within our B2B product, depending on how passwords are treated across Organizations:
1. **Cross-Organization:** An email has a single password associated with it, and the end user can use that password to log into any of their Organizations that allow passwords as an authentication method.
2. **Organization-scoped:** A password is scoped to a specific MemberID, and can only be used to log into that specific Organization.
If you have a single, centralized login page for all Organizations, we recommend you use Cross-Organization Passwords by enabling **Allow Passwords to be used between Organizations** in the [Passwords Policy](https://stytch.com/dashboard/password-strength-config) page of the Dashboard.
If you have tenanted login pages for each Organization, and want to enforce strict data isolation between your Organizations we recommend you use Organization-scoped Passwords and disable this setting.
This is a Project-level setting, and cannot be changed if you have active Passwords. Make sure you have selected the password type you want in the [Dashboard](https://stytch.com/dashboard/password-strength-config) prior to integrating.
# Overview
Source: https://stytch.com/docs/multi-tenant-auth/authentication/passwords/overview
Stytch's Password product allows you to offer a familiar authentication option to your end users, with built-in protection against common password pitfalls like credential stuffing attacks and insecure account deduplication.
Before implementing Passwords, it's important to decide if you want to use Cross-Organization Passwords or Organization-scoped Passwords. Learn more in the [Cross-Organization Passwords page](/docs/multi-tenant-auth/authentication/passwords/cross-org).
## Default Password Policy
By default, Stytch uses [zxcvbn](https://github.com/dropbox/zxcvbn) for our password strength assessment, which is designed with modern password cracking techniques in mind and rewards easy-to-type but difficult to crack passwords like `EntropyIsInformation` over annoying and ineffective LUDS (lower, upper, digit, symbol) requirements that still allow users to set easily crackable passwords like `P@ssword123`. You can play around with zxcvbn [here](https://lowe.github.io/tryzxcvbn/).
Stytch integrates with [HaveIBeenPwned](https://haveibeenpwned.com/) to detect breached passwords, and by default verifies the user's password has not been breached on both initial password creation and on subsequent authentication. If HaveIBeenPwned indicates that a user's current password has been breached, Stytch will force the end user to reset their password in order to prevent a credential stuffing attack.
However, Stytch also offers the ability to customize your password strength assessment and password breach detection policies to fit whatever makes the most sense for your application. You can read more about the full list of configurations in the [Strength Policies guide](/docs/multi-tenant-auth/authentication/passwords/strength-policy).
# Password setting flows
Source: https://stytch.com/docs/multi-tenant-auth/authentication/passwords/password-setting-flows
Create a Member's password via password reset flows.
## Creating passwords for Members
A Member must exist already before you call any of these endpoints. You cannot simultaneously create a Member and a password. Once you've created a Member, you can set their password a few ways:
### Using email
1. Call [Reset password via email start](/docs/api-reference/b2b/api/passwords/create-or-reset-options/password-reset-by-email-start) to send a magic link to create a new password
2. Call [Reset password via email](/docs/api-reference/b2b/api/passwords/create-or-reset-options/password-reset-by-email) to accept the password and token
A successful password reset by email will revoke all active sessions for the Member.
### Using a session
1. Call [Reset password via session](/docs/api-reference/b2b/api/passwords/create-or-reset-options/password-reset-by-session) using an existing session
The Member must have an active session to use this endpoint.
### Using discovery email with Cross-Organization passwords
If you are using [Cross-Organization passwords](/docs/multi-tenant-auth/authentication/passwords/cross-org), you can create a password via discovery email
1. Call [Discovery reset password via email start](/docs/api-reference/b2b/api/passwords/create-or-reset-options/discovery-password-reset-by-email-start) to send a magic link to create a new password
2. Call [Discovery reset password via email](/docs/api-reference/b2b/api/passwords/create-or-reset-options/discovery-password-reset-by-email) to accept the password and token
A successful password reset by email will revoke all active sessions for the Member.
## Password reset email behavior
Password reset emails differ slightly depending on whether a password already exists for a Member:
* If a password does not exist, the "Verify Email" email template will be used by default.
* For [custom email templates](/docs/resources/branding/custom-email-templates), you can specify the custom template to use with `verify_email_template_id` when calling the [Password Reset APIs](/docs/api-reference/b2b/api/passwords/create-or-reset-options/password-reset-by-email-start).
* If you're managing custom email templates via the [PWA API](/docs/api-reference/pwa/api/v3/email-templates/email-template-object), use the template type `VERIFY_EMAIL_PASSWORD_RESET`.
* If a password does exist, the "Password reset" email template will be used by default.
* For [custom email templates](/docs/resources/branding/custom-email-templates), you can specify the custom template to use with `reset_password_template_id` when calling the [Password Reset APIs](/docs/api-reference/b2b/api/passwords/create-or-reset-options/password-reset-by-email-start).
* If you're managing custom email templates via the [PWA API](/docs/api-reference/pwa/api/v3/email-templates/email-template-object), use the template type `RESET_PASSWORD`.
# Password strength policy
Source: https://stytch.com/docs/multi-tenant-auth/authentication/passwords/strength-policy
Configure password strength rules with zxcvbn, LUDS, and breach detection.
Stytch supports two password strength strategies, [zxcvbn](https://github.com/dropbox/zxcvbn) and Lowercase Uppercase Digits Special Characters (LUDS). On top of these two strategies, you can also enable breach detection, powered by [HaveIBeenPwned](https://haveibeenpwned.com/) to protect your users from password breaches.
You may choose any mix of these strategies for your Stytch Project. For example, you may choose to use LUDS for password complexity plus breach detection, or zxcvbn without breach detection.
By default, our Passwords product uses zxcvbn for password strength and automatically protects your users against password breaches. If that doesn't fit your needs, you may configure your desired strategy by creating a custom policy [in the Dashboard](https://stytch.com/dashboard/password-strength-config) or [via the API](/docs/api-reference/pwa/api/v3/password-strength-config/set-password-strength-config).
Passwords at Stytch may contain any UTF8 character, e.g. spaces, emojis, non-English characters, etc. Any of these types of characters may contribute to fulfilling the complexity requirements you choose.
## zxcvbn
[zxcvbn](https://github.com/dropbox/zxcvbn) is a password strength estimation library developed originally by Dropbox. It is a complex password strength strategy that checks for the presence of dictionary words, common passwords, and common patterns. It does not explicitly check for lowercase letters, uppercase letters, digits, or special characters.
Some end users find zxcvbn to be too strict, and may be unable to create a password that meets the requirements. If you find that your users are unable to create a password that meets the requirements, you may want to consider using LUDS instead.
You can find examples of zxcvbn passwords and an interactive tool to test passwords at this hosted [zxcvbn checker](https://lowe.github.io/tryzxcvbn/).
### Example
If a user provides a commonly used password like, "p\@ssword1", the response from our [Strength check endpoint](/docs/api-reference/b2b/api/passwords/strength-check) will look like the following:
```json theme={null}
{
"breach_detection_on_create": true,
"breached_password": false,
"feedback": {
"luds_requirements": null,
"suggestions": [
"Add another word or two. Uncommon words are better.",
"Predictable substitutions like '@' instead of 'a' don't help very much."
],
"warning": "This is a top-100 common password."
},
"request_id": "request-id-test-86779128-d4e7-4dd4-a588-88faa570f638",
"score": 0,
"status_code": 200,
"strength_policy": "zxcvbn",
"valid_password": false
}
```
You may use the `warning` and `suggestions` fields to provide feedback directly to your users to help them craft a stronger password that will pass your requirements.
## LUDS
LUDS stands for Lowercase Uppercase Digits Special Characters. It is a simple password strength strategy that checks for the presence of lowercase letters, uppercase letters, digits, and special characters. It does not check for dictionary words or common passwords.
### Example
For this example, we will use the following password strength policy:
* Strength policy: LUDS
* LUDS minimum length: 8
* LUDS complexity: 4
If a user provides a password of, "Lorem1!", the password will be considered invalid. The password contains the necessary complexity, symbols, numbers, and upper and lower case letters, but is not of sufficient length. The response from our [Strength check endpoint](/docs/api-reference/b2b/api/passwords/strength-check) would look like the following:
```json theme={null}
{
"breach_detection_on_create": true,
"breached_password": false,
"feedback": {
"luds_requirements": {
"has_digit": true,
"has_lower_case": true,
"has_symbol": true,
"has_upper_case": true,
"missing_characters": 2,
"missing_complexity": 0
},
"suggestions": null,
"warning": null
},
"request_id": "request-id-test-86779128-d4e7-4dd4-a588-88faa570f638",
"score": 0,
"status_code": 200,
"strength_policy": "luds",
"valid_password": false
}
```
You may use the `missing_characters` and `missing_complexity` fields to provide feedback to your users to help them craft a stronger password that will pass your requirements.
## How to set a password strength policy
You may set or update the password strength policy for your Stytch Project via [the Passwords Policy](https://stytch.com/dashboard/password-strength-config) page in the Stytch Dashboard.
### Password strength policy options
* Strength policy type: The type of password validation policy to use – either one of our default zxcvbn policies or a custom policy using LUDS.
* Minimum password length: When using a custom LUDS policy, the minimum number of characters required for a password. We require a minimum of at least 8 characters.
* Number of character types: When using a custom LUDS policy, the minimum number of character types required for a password. Allowable values are 1-4. This value determines how many LUDS checks must be passed – for example, 2 means that a password with a symbol and a number is valid because two of the four LUDS requirements are met. You cannot choose *which* LUDS requirements are required, only how many.
* Password strength enforcement during authentication: Determines whether or not password strength checks are applied during password authentication. During user migrations from another service with a password policy that is incompatible with Stytch's password strength policy options, you may choose to disable password strength checks during authentication.
* Breach detection: Determines whether passwords are checked against the HaveIBeenPwned dataset during password creation and authentication, during password creation only, or never.
## Breach detection
Stytch’s Passwords product automatically protects your users against password breaches. We do this by checking the password against a list of known passwords that have been leaked in data breaches from [HaveIBeenPwned](https://haveibeenpwned.com/). If the password is found in this list, we will reject the password and ask the user to choose a different one or reset their password if they have already used it previously.
Protecting your users against password breaches is a critical part of your security strategy. Password breaches are a common attack vector for hackers, and can be used to gain access to your users’ accounts and compromise your app.
### Enabling breach detection
Breach detection can be configured on the [Password and user policies](https://stytch.com/dashboard/password-strength-config) page in the Stytch Dashboard, and can be enabled during password creation, logins, or both.
If configured to detect breaches on logins, you must also enable **Require password reset for non-compliant passwords** under the **Enforce password policy** section in order for breaches to be detected during password authentication.
# Adding to Stytch Login
Source: https://stytch.com/docs/multi-tenant-auth/authentication/sso/adding-sso-to-stytch-login
Integrate SSO with Stytch's pre-built UI components.
## Authentication flow
```mermaid theme={null}
sequenceDiagram
participant Frontend as Stytch Login Component
participant App as Your Application Logic
participant Stytch as Stytch
participant IDP as Workflow Idp
Frontend->>Stytch: User initiates SSO login
Stytch-->>Frontend: Returns redirect URL
Frontend->>IDP: Automatically redirects to IdP
IDP<<-->>Frontend: User authenticates with IdP
IDP<<-->>Stytch: Stytch performs SSO handshake with IdP
Stytch-->>Frontend: Redirects to Login or Signup Redirect URL with token
Frontend-->>Stytch: SDK automatically authenticates token
Stytch->>Frontend: Member logged in Session cookie stored in browser
```
## Prerequisites
If you haven't done so already:
1. If you'd like to have a provider to test with, complete the steps in the [SSO Provider Setup](/docs/multi-tenant-auth/authentication/sso/provider-setup) guide for an Organization that you're a Member of.
2. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration).
## Add SSO to your UI config
To surface SSO in the UI, include `B2BProducts.sso` in the `products` array of your configuration.
```jsx React icon="react" theme={null}
import { B2BProducts, StytchB2B } from '@stytch/react/b2b';
const Login = () => {
const config = {
authFlowType: "Discovery", // or "Organization"
products: [B2BProducts.sso],
ssoOptions: {},
};
return ;
};
export default Login;
```
```jsx Next.js icon="triangle" theme={null}
import { B2BProducts, StytchB2B } from '@stytch/nextjs/b2b';
const Login = () => {
const config = {
authFlowType: "Discovery", // or "Organization"
products: [B2BProducts.sso],
ssoOptions: {},
};
return ;
};
export default Login;
```
```html Vanilla JS icon="square-js" theme={null}
```
SSO can be used in both Discovery and Organization-specific flows. Use Discovery if you need to determine the user's Organization before completing authentication.
## Render the UI component
Render the Stytch UI component on the Redirect URL(s) that you specified in the [Stytch Dashboard](https://stytch.com/dashboard/redirect-urls) during setup.
When rendered, the UI component will automatically:
* Handle the SSO redirect based on the `stytch_token_type`
* For Discovery flows, prompt the user to select an Organization and finish authentication
* For Organization-specific flows, complete authentication directly
## Test your integration
First, ensure the Organization you will log into has an [sso provider configured](/docs/multi-tenant-auth/authentication/sso/provider-setup). Then, start your application and test the SSO flow:
1. Navigate to your login page
2. Click the SSO button
3. Complete the SSO flow with your IdP
4. Verify that you're redirected back to your application and authenticated
## Next steps
Learn how to customize the appearance and behavior of the pre-built UI
Add or update SSO Connections for your Identity Provider
# Adding SSO to a custom auth flow
Source: https://stytch.com/docs/multi-tenant-auth/authentication/sso/adding-to-custom-auth-flow
Integrate SSO with Stytch's headless or backend SDKs for complete control over your authentication flow.
## Authentication flow
```mermaid theme={null}
sequenceDiagram
participant Browser
participant App as Your Application Logic
participant Stytch
participant IDP as Workflow Idp
Browser->>App: User initiates SSO login
App->>Stytch: Call sso.start()
Stytch-->>App: Returns redirect URL
App-->>Browser: Follow redirect URL
Browser->>IDP: Redirects to IdP
IDP<<-->>Browser: User authenticates with IdP
IDP<<-->>Stytch: Stytch performs SSO handshake with IdP
Stytch-->>Browser: Redirect to Login or Signup Redirect URL with token
Browser-->>App: Extract sso_token from URL
App->>Stytch: Call sso.authenticate() with sso_token
Stytch->>App: Member logged in Returns Member info & Session
```
***
Before you begin, make sure you've completed the [SSO provider setup steps](/docs/multi-tenant-auth/authentication/sso/provider-setup).
## Overview
This guide walks through integrating SSO using Stytch's headless frontend SDKs. This approach gives you complete control over your UI while handling authentication logic on the frontend.
Both Discovery and Organization-specific authentication flows are supported.
The Discovery flow is designed for situations where end users are signing up or logging in from a central landing page, and have not specified which Organization they are trying to access.
### Prerequisites
1. Complete the steps in the [SSO provider setup guide](/docs/multi-tenant-auth/authentication/sso/provider-setup)
2. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)
### Implementation
If you have a centralized login page, prompt the user for their email and call `sso.discoverConnections`.
```jsx React icon="react" theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const SSOConnectionsDiscovery = () => {
const stytch = useStytchB2BClient();
const discoverSSOConnections = (email) => {
stytch.sso.discoverConnections(email);
};
return ;
};
```
The `sso.discoverConnections()` method returns an array of SSO Connections. Use the `display_name` and `idp_type` to render the available options, then pass the selected `connection_id` into the next step.
Use the selected `connection_id` to initiate the SSO flow.
```jsx React icon="react" theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Login = ({ connectionId }) => {
const stytchClient = useStytchB2BClient();
const startSSO = () =>
stytchClient.sso.start({
connection_id: connectionId,
});
return ;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
The user will be redirected to their IdP to authenticate.
After the SSO handshake, Stytch redirects to your Login or Signup Redirect URL with a token in the URL. Call `sso.authenticate` to finish the login process.
```jsx React icon="react" theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient, useStytchMemberSession } from '@stytch/react/b2b';
export const Authenticate = () => {
const stytchClient = useStytchB2BClient();
const { session } = useStytchMemberSession();
useEffect(() => {
if (session) {
window.location.href = 'https://example.com/profile';
} else {
const token = new URLSearchParams(window.location.search).get('token');
stytchClient.sso.authenticate({
sso_token: token,
});
}
}, [stytchClient, session]);
return
Loading
;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
The Stytch frontend SDK will store `stytch_session_token` and `stytch_session_jwt` in cookies for you. See [session cookie management](/docs/api-reference/b2b/frontend-sdks/vanilla-js/resources/cookies-and-session-management).
If end users login via a page that indicates which Organization they're trying to access (e.g., `.your-app.com` or `your-app.com/team/`), you can offer Organization-specific authentication.
### Prerequisites
1. Complete the steps in the [SSO provider setup guide](/docs/multi-tenant-auth/authentication/sso/provider-setup), including creating an Organization.
2. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)
### Implementation
Use the Organization slug to fetch SSO Connections and render them in your UI.
```jsx React icon="react" theme={null}
import { useEffect, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';
export const OrganizationLoginPage = ({ slug }) => {
const stytch = useStytchB2BClient();
const [organization, setOrganization] = useState();
useEffect(() => {
stytch.organization
.getBySlug({ organization_slug: slug })
.then((response) => setOrganization(response.organization));
}, [stytch, slug]);
if (organization === undefined) {
return
Loading...
;
}
if (!organization) {
return
No organization found for {slug}
;
}
const ssoConnections = organization.sso_active_connections;
// render UI
};
```
Call `sso.start()` using the selected `connection_id`.
```jsx React icon="react" theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const Login = ({ organization }) => {
const stytchClient = useStytchB2BClient();
const startSSO = (connectionId) =>
stytchClient.sso.start({
connection_id: connectionId,
});
return ;
};
```
Stytch will redirect the user to the Login or Signup Redirect URL. Extract the token from the URL and call the authentication method to finish the login process.
```jsx React icon="react" theme={null}
import { useEffect } from 'react';
import { useStytchB2BClient, useStytchMemberSession } from '@stytch/react/b2b';
import { Navigate } from "react-router-dom";
export const Authenticate = () => {
const stytchClient = useStytchB2BClient();
const { session } = useStytchMemberSession();
useEffect(() => {
if (session) {
;
} else {
const token = new URLSearchParams(window.location.search).get('token');
stytchClient.sso.authenticate({
sso_token: token,
});
}
}, [stytchClient, session]);
return
Loading...
;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
This guide walks through integrating SSO using Stytch's backend SDKs or Direct API. This approach handles all authentication logic on your backend.
### Prerequisites
Complete the steps in the [SSO provider setup guide](/docs/multi-tenant-auth/authentication/sso/provider-setup).
### Implementation
Stytch will make a callback to your Redirect URL. Handle `stytch_token_type=sso` and authenticate the token.
```python Python icon="python" theme={null}
@app.route("/authenticate", methods=["GET"])
def authenticate() -> str:
token_type = request.args["stytch_token_type"]
if token_type == "sso":
resp = stytch_client.sso.authenticate(sso_token=request.args["token"])
if resp.status_code != 200:
return "Authentication error"
else:
return "unsupported auth method"
member = resp.member
session["stytch_session"] = resp.session_jwt
return member.json()
```
Test the SSO flow by navigating to:
```text theme={null}
https://test.stytch.com/v1/public/sso/start?connection_id={connection_id}&public_token={public_token}
```
If multiple SSO Connections exist, show users a list based on their Organization.
```python theme={null}
@app.route("/org/", methods=["GET"])
def org_index(slug: str):
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]
return render_template(
"organizationLogin.html",
public_token=STYTCH_PUBLIC_TOKEN,
org_name=organization.organization_name,
sso_connections=organization.sso_active_connections
)
```
# Multi-Organization SSO
Source: https://stytch.com/docs/multi-tenant-auth/authentication/sso/multi-organization
Implement External SSO connections to support multi-Organization access.
## Security considerations with SSO
SSO Connections in Stytch are scoped to a specific Organization and SSO authentication is non-transferable between Organizations. This means that an end user who authenticates via one Organization's SSO Connection will need to perform another authentication flow in order to Session Exchange into another Organization they belong to under the same email.
The reason for this restriction is that the workforce IdP can **attest to whatever user identity it wants** in SSO authentication, without any proof of verification. This is expected and desired when the Organization is also the owner of the workforce IdP instance, as it is the ultimate source of truth on their employees' identities and access.
However, allowing authentication into *other* Organizations via that SSO Connection introduces a security vulnerability; if that workforce IdP is compromised, SSO could then be used by a malicious attacker to access **any other Organization's instance** by having the workforce IdP attest to emails of Members of those Organizations.
### Supporting multi-Organization access
While the vast majority of SSO Connections are typically only used to log into a single Organization, there are a few scenarios where you may want end users to be able to authenticate into **multiple Organizations via a single SSO Connection**:
1. **Single company has multiple Organization instances**, due to M\&A activity or wanting to keep international subsidiaries or distinct departments separate
2. **Managed Service Providers (MSPs)** who help maintain multiple companies’ Organization instances
In order to address these use cases in a secure and controlled way, Stytch offers the concept of **External SSO Connections**, which allows an Organization to explicitly specify another Organization's SSO Connection as a trusted and valid authentication option for their Members.
This eliminates the need to re-configure the SSO Connection for every Organization that wants to allow authentication through a given IdP instance, while still maintaining clear boundaries and control between Organizations. The underlying SAML or OIDC Connection is still "owned" by the Organization that originally configured it, but each Organization that has allowed the SSO Connection as an External Connection has its own controls over Just-in-Time (JIT) Provisioning and role assignments and can choose to sever the Connection at any point.
## Implementing External SSO Connections
### Configuration
Organization admins can create External SSO Connections via the Admin Portal SSO UI Component by:
* Clicking **New Connection**
* Selecting **Add an external connection**
* Inputting the **ConnectionID** they wish to allow and the owning **OrganizationID**
They can then configure JIT Provisioning settings or automatic role assignments for users authenticating through this shared connection.
You can also configure this via Direct API by calling [CreateExternalConnection](/docs/api-reference/b2b/api/sso/external/create-external-connection) or in the Stytch Dashboard within the Organization page that wishes to add the External SSO Connection
### Initiating SSO
You can initiate SSO with an External SSO Connection in the same way that you would with any other SSO Connection:
```js theme={null}
stytch.sso.start({
connection_id: 'external-connection-test-5c44cc6a-8af7-48d6-8da7-ea821342f5a6'
});
```
### SSO Discovery
If you are using SSO Discovery via our pre-built UI components or headless FE SDK, External SSO Connections will be deduped down to a single Connection in order to display to the end user and initiate the SSO Start.
Upon successful SSO authentication, we will automatically redirect the user to an Organization in a logged in state. If the user is part of the "owning" Organization, we will redirect them there. Otherwise, we will redirect them to the oldest Organization that they belong to and can access via this External SSO Connection.
Once the user is logged in, they can Session Exchange into any other Organization that shares this External SSO Connection.
# Overview
Source: https://stytch.com/docs/multi-tenant-auth/authentication/sso/overview
Use SSO to authenticate users with their enterprise identity provider credentials.
## API resources
Use the SSO API to start an SSO flow.
Use the SSO SDKs to start an SSO flow.
Use Stytch's pre-built UI components to seamlessly add SSO to your login flow.
## About SSO
**Single Sign On** (SSO) allows end users to authenticate to your application using their existing enterprise identity provider credentials. With Stytch, you can implement SSO as part of a fully-featured authentication stack or easily add enterprise-grade SSO capabilities to your existing infrastructure.
SSO involves two parties:
1. **Service Provider (SP):** the application the end user is trying to access (your application)
2. **Identity Provider (IdP):** the application that is verifying the end user's identity
For B2B applications like yours, the Identity Provider in the SSO exchange refers to the workforce IdP that your customers use to centrally manage their employees' access and identity information. When an end user authenticates through an Organization's SSO Connection this verifies both their identity as well as their authorization to access the Organization's instance on your application.
Implementing SSO on your own requires extensive work with various identity protocols and provider-specific implementations, but Stytch abstracts away those details for you, and the flow between you and Stytch will be the same regardless of the protocol used. Stytch handles the backend auth exchange with each identity provider, wrapping complex OIDC and SAML protocol flows into two simple API calls. Stytch supports both SAML 2.0 and OpenID Connect (OIDC), the two main protocols used for Enterprise SSO:
* **SAML 2.0** (Security Assertion Markup Language): A mature standard used by many enterprise identity providers
* **OIDC** (OpenID Connect): A more modern protocol built on OAuth 2.0, used by providers like Google and Microsoft
## Integrate SSO
Use Stytch's pre-built UI components to seamlessly add SSO to your login flow.
Use Stytch's headless or backend SDKs to add SSO to your custom auth flow.
# SSO Provider Setup
Source: https://stytch.com/docs/multi-tenant-auth/authentication/sso/provider-setup
Instructions for setting up SSO connections with various Identity Providers (IdPs) in Stytch.
This guide provides step-by-step instructions for setting up SSO connections with various Identity Providers (IdPs) in Stytch. Use this to set up a test connection or collect required information from your customers to enable their SSO connections.
Manually create an SSO connection for an Organization in your Stytch Dashboard.
Create an SSO Connection programmatically via API.
Enable your customers to add their own SSO connections with our pre-built UI.
## Generic SAML (most IdPs)
If you're configuring a SAML connection, you'll need to perform the following steps:
* Ensure you have your `organization_id` and a SAML connection created in Stytch.
* Ensure you have admin access to your IdP.
* `acs_url` and `audience_uri` from your SAML connection.
Create an application in your IdP.
* Enter the `acs_url` and `audience_uri` in their respective fields. Some IdPs call these SP SSO URL and SP Entity ID.
* Set up attribute mapping so the IdP returns at least email and name fields. We recommend passing a unique identifier as well.
* Metadata URL, **or**...
* IdP SSO URL, IdP Entity ID, and X.509 Certificate.
Configure your IdP metadata with Stytch using one of two ways.
* If your IdP provides a metadata URL, add that via the Stytch Dashboard or the [Update SAML Connection by Metadata URL](/docs/api-reference/b2b/api/sso/saml/update-saml-connection-by-metadata-url) route.
* If your IdP does not provide a metadata URL, add the IdP SSO URL, IdP Entity ID, and X.509 certificate via the Stytch Dashboard or the [Update SAML Connection](/docs/api-reference/b2b/api/sso/saml/update-saml-connection) route.
Configure your attribute mapping in Stytch. Map the `email` field to the email, `first_name` to the first name, `last_name` to the last name, and `full_name` to the full name. You only need either `full_name` or both `first_name` and `last_name`. You can do this in the Dashboard or via [Update SAML Connection](/docs/api-reference/b2b/api/sso/saml/update-saml-connection).
Example attribute mapping:
```js theme={null}
{
"email": "NameID",
"first_name": "firstName",
"last_name": "lastName"
}
```
**Common pitfalls:**
* NameID format mismatch -- if your IdP lets you configure a NameID, set it to the field with the user's email address.
* Stale IdP metadata in Stytch.
* Certificate paste errors -- if your IdP uses multiple certs, ensure the active signing cert is used.
**Expected result:** Your SAML connection shows as Active in Stytch.
## Generic OIDC (most IdPs)
If you're configuring an OIDC connection, you'll need to perform the following steps:
* Ensure you have your `organization_id` and an OIDC connection created in Stytch.
* Ensure you have admin access to your IdP.
* `redirect_url` from your OIDC connection.
Create a web application in your IdP.
* Select Authorization Code as the grant type and add the Stytch `redirect_url` as a Sign-in Redirect URI.
* Optionally add a Sign-out Redirect URI pointing to your app's logout handler.
* Client ID and Secret, as well as your Issuer URL (generally this is your IdP hostname).
Configure your IdP client with Stytch.
* Add your Client ID, Secret, and Issuer URL via the Stytch Dashboard or the [Update OIDC Connection](/docs/api-reference/b2b/api/sso/oidc/update-oidc-connection) route.
**Common pitfalls:**
* Redirect URI mismatch.
* Using a discovery URL instead of the Issuer base URL.
* Missing client secret.
* Ensure Issuer is the exact issuer value your IdP expects.
**Expected result:** Your OIDC connection shows as Active in Stytch.
## Okta SAML
If you don't already have an Okta admin account, the easiest way to do this is by creating an [Okta Workforce Identity Cloud Developer Edition](https://developer.okta.com/signup/) account. Once you're logged in to the Okta Admin Dashboard click **Create App Integration** in the **Applications** tab:
Select **SAML 2.0** and continue to the **General Settings** form, enter the name of your application and (optionally) your application's logo.
In the **Configure SAML** form:
* Input the `acs_url` from your Stytch SSO Connection as the **Single sign-on URL**
* Input the `audience_uri` from your Stytch SSO Connection as the **Audience URI (SP Entity ID)**
* For **Name ID format** select **EmailAddress**
* For **Application username** select **Email**
* In **Attribute Statements** create three inputs:
* Name: `firstName`; Name format: `Basic`; Value: `user.firstName`
* Name: `lastName`; Name format: `Basic`; Value: `user.lastName`
* Name: `id`; Name format: `Basic`; Value: `user.id`
Your configuration page should look like the following:
Save and continue, indicating that this is an internal application on the last screen.
Copy the **Metadata URL** from the Sign On Settings tab in your newly created Okta application.
In the Stytch Dashboard, click "configure" on your SSO Connection, and in the modal input the Metadata URL you just copied, and the following JSON for the Attribute Mapping.
```js theme={null}
{
"email": "NameID",
"first_name": "firstName",
"last_name": "lastName",
"idp_user_id": "id"
}
```
Click save. You should now see the SSO Connection as "Active". In the SSO Connections JIT Provisioning settings section above, select "Anyone" can JIT Provision through SSO Connections and save.
On the **Assignments** tab under your application in Okta, assign the application to team members who should have access to it by clicking **Assign**:
## Okta OIDC
If you don't already have an Okta admin account, the easiest way to do this is by creating an [Okta Workforce Identity Cloud Developer Edition](https://developer.okta.com/signup/) account. Once you're logged in to the Okta Admin Dashboard and click **Create App Integration** in the **Applications** tab:
Select **OIDC - OpenID Connect** and **Web Application**:
Enter the name of your application and (optionally) your application's logo.
Under **Grant type**, select **Authorization Code**:
In the **Sign-in redirect URIs** section, add the `redirect_url` value from the Stytch connection object.
For the purposes of this guide, you do not need to add any **Sign-out redirect URIs**. In the future, you can (optionally) add a URI corresponding to a page in your application that logs the user out by [revoking their Stytch session](/docs/api-reference/b2b/api/sessions/revoke-session).
Under **Controlled access**, select **Allow everyone in your organization to access** and **Enable immediate access with Federation Broker Mode** and save. You may change these settings later, if desired.
In the **General** tab of your newly created Okta application, locate the **Client ID** in the **Client Credentials** section and **Secret** in the **Client Secrets** section:
In the Stytch Dashboard, click "configure" on your SSO Connection and input the **Client ID** and **Secret** from above and set the Issuer value to your Okta instance URL. This URL should look like `https://dev-111111.okta.com` and is viewable in the top right hand corner drop down under your email address. You can alternatively call the [Update OIDC Connection](/docs/api-reference/b2b/api/sso/oidc/update-oidc-connection) endpoint with the `client_id`, `client_secret` and `issuer` fields.
Click save. You should now see the SSO Connection as "Active". In the SSO Connections JIT Provisioning settings section above, select "Anyone" can JIT Provision through SSO Connections and save.
## Google Workspace SAML
Log into the [Google Workspace Admin Console](https://admin.google.com). Navigate to the **Web and mobile apps** tab under **Apps**:
Select **Add custom SAML app** from the **Add app** dropdown:
Enter the name of your application and (optionally) a description and your application's logo. Click **Continue**.
Copy the following information under **Option 2** and input into your Stytch SSO Connection by clicking "configure":
* **IdP Entity ID**: the **Entity ID** from Google
* **IdP SSO URL**: the **SSO URL** from Google
* **X.509 certificate**: the **Certificate** from Google
* **Attribute Mapping**: input the below JSON:
```js theme={null}
{
"email": "NameID",
"first_name": "firstName",
"last_name": "lastName"
}
```
Your Stytch SSO configuration view should look like the following:
Click save. You should now see the SSO Connection as "Active". In the SSO Connections JIT Provisioning settings section above, select "Anyone" can JIT Provision through SSO Connections and save.
In the Google Admin Console, enter the following information from the Stytch SSO Connection into the **Service provider details** form and then click **Continue**:
* **ACS URL**: `acs_url` from the Stytch SSO Connection
* **Entity ID**: `audience_uri` from the Stytch SSO Connection
* **Name ID format**: EMAIL
* **Name ID**: Basic Information > Primary email
On the next screen add the following two **Attributes**:
* Google Directory attributes: First name; App attributes: `firstName`
* Google Directory attributes: Last name; App attributes: `lastName`
Click **Finish**.
Navigate to the **User access** page for your new Google Workspace app:
Grant access to the Groups or Organizational Units of your choice. For the purposes of this guide, you can also simply set the **Service status** to **ON for everyone** in the **All users in this account** tab:
## Microsoft Entra SAML
Log into Microsoft Entra Admin Center, navigate to Enterprise applications and select to create a new application.
Select create your own application at the top.
Name your application and select **Integrate any other application you don't find in the gallery (Non-gallery)** and then click **Create**.
Once your application is created, navigate to the Single Sign-On setup page and select SAML.
Click Edit on **Basic SAML Configuration** and add the following values from the SSO Connection you created in Stytch:
* **Identifier (Entity ID):** the Audience URI from your Stytch SSO Connection
* **Reply URL (Assertion Consumer Service URL):** the ACS URL from your Stytch SSO Connection
Leave the other values blank and click **Save**.
Next, edit the **Attributes & Claims** section. Click on the **Unique User Identifier (Name ID)** under Required Claim, and change the Source attribute to use **user.primaryauthoritativeemail**
Under Additional claims, delete the preconfigured options and create the following three claims:
* Claim Name: **firstName** Value: **user.givenname**
* Claim Name: **lastName** Value: **user.surname**
* Claim Name: **id** Value: **user.objectid**
Click **Save**.
In the Stytch Dashboard (or with the UpdateSAMLConnection API) click "configure" on your SSO Connection and set the Metadata URL as the **App Federation Metadata Url** from the SAML Certificates section in your Entra app.
For Attribute Mapping on your Stytch SSO Connection set the following JSON:
```js theme={null}
{
"email": "NameID",
"first_name": "firstName",
"last_name": "lastName",
"idp_user_id": "id"
}
```
Click save on your Stytch SSO Connection, and you should now see the status as "Active". In the SSO Connections JIT Provisioning settings section above, select "Anyone" can JIT Provision through SSO Connections and save.
The last step is to add users to your application in Entra, which you can do by navigating to **Users and groups** and selecting "Add user/group".
## Microsoft Entra OIDC
Log into Microsoft Entra Admin Center, navigate to **App registrations** and select to create a New registration
Input a name and select **Accounts in this organizational directory only** for Supported account types and click Register
Navigate to the Authentication section and select "Add a platform" under Platform configurations
Select web and input the Redirect URI from the Stytch SSO Connection you created earlier. Leave the rest blank and click "Configure"
Navigate to Certificates & secrets and select "New client secret". Enter a description of your new secret key, select your desired secret expiration length, and click Add.
In the Stytch Dashboard, click "configure" on your SSO Connection and input the secret value as the Client Secret in Stytch
For Client ID and Issuer, navigate back to the Entra Overview section and copy over the following values into the Stytch OIDC Connection you are configuring:
* **Client ID** in Stytch: set to the **Application (client) ID** from Entra
* **Issuer** in Stytch: set to URL format `https://login.microsoftonline.com//v2.0` where `` is replaced with the **Directory (tenant) ID** from the Overview section
Click save. You should now see the SSO Connection as "Active". In the SSO Connections JIT Provisioning settings section above, select "Anyone" can JIT Provision through SSO Connections and save.
# Standalone SSO
Source: https://stytch.com/docs/multi-tenant-auth/authentication/sso/standalone
Implement SSO with Stytch without migrating your existing authentication stack.
Stytch's embedded, API-first architecture makes it easy to quickly add Enterprise Single Sign-On to your application without needing to migrate the rest of your authentication at the same time.
You can use the Stytch Dashboard and/or Admin UI to manage SSO configuration, or use our APIs to embed configuration into your app directly.
## Implementing SSO authentication
If you haven't done so already, complete the steps in the guide [Getting Started with SSO](/docs/multi-tenant-auth/authentication/sso/overview).
Stytch will make a callback to the Login or Signup [Redirect URL](https://stytch.com/dashboard/redirect-urls) that you specified in the Stytch Dashboard in order to securely communicate that the user has successful logged in via SSO.
If your Redirect URL is `http://localhost:3000/authenticate` you would add the following route to your application:
```python theme={null}
from stytch import B2BClient
stytch_client = B2BClient(
project_id="PROJECT_ID",
secret="SECRET",
)
@app.route("/authenticate", methods=["GET"])
def authenticate() -> str:
try:
resp = stytch_client.sso.authenticate(
sso_token=request.args["token"]
)
except StytchError as e:
return e.details
# user has successfully authenticated
```
Use the response from Stytch to get or create your internal record of the user and organization, and create a session for the user.
In order to initiate SSO, you will call the [Start SSO Login](/docs/api-reference/b2b/api/sso/shared/start-sso-authenticate) method from your client. This will automatically redirect the user to the workforce IdP to initiate the SSO authentication.
You can test the full SSO flow out by calling the `/sso/start` method using the `connection_id` for the SSO Connection you created earlier and your Stytch `public_token` from the [Stytch Dashboard](https://stytch.com/dashboard):
```text theme={null}
https://test.stytch.com/v1/public/sso/start?connection_id={connection_id}&public_token={public_token}
```
## Identifying the SSO Connection
Depending on your application, there are a few different ways you might identify the correct `connection_id` to use for the `sso.start()` request.
### By Organization
If you already know which Organization the end user is attempting to log into you can fetch the Organization object and prompt the user to select between their active Connections
```python theme={null}
try:
resp = client.organizations.get(
organization_id="organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
)
except StytchError as e:
return e.details
return resp.organization.sso_active_connections
```
### By Email Address
If you don't know which Organization the user is trying to access, you can use the [Discover SSO Connections](/docs/api-reference/b2b/frontend-sdks/react/methods/sso/discover-connections) method in our headless frontend SDK
```js theme={null}
import { StytchB2BHeadlessClient } from '@stytch/vanilla-js/b2b/headless';
const stytch = new StytchB2BHeadlessClient('PUBLIC_TOKEN');
export const discoverSSOConnections = () => {
stytch.sso.discoverConnections('sandbox@stytch.com');
};
```
This method attempts to find the SSO Connection that the user wishes to use by prioritizing in the following order:
1. Active SSO Registrations (e.g. the user has previously signed in via this connection)
2. Active Memberships
3. Invites
4. Eligible to join by email domain
At each step, we will check to see if we have SSO Connections that apply and if so will return those. If we have not found SSO Connections, we will continue to the next step. In rare situations where we find SSO Connections for distinct Organizations at a given step we will return an empty array and recommend prompting the user for the Organization they wish to access.
The goal is to optimize directing users to the correct connection without exposing account enumeration attacks or relying on unscalable assumptions like a 1:1 mapping with domain and SSO Connection.
If you would prefer to always show the user all possible SSO Connections, regardless of the account enumeration risk you can do this by calling the [Search Members](/docs/api-reference/b2b/frontend-sdks/react/methods/members/search-members) API.
# Custom auth factors
Source: https://stytch.com/docs/multi-tenant-auth/authentication/trusted-auth-tokens/custom-factors
Use your own JWTs to attest custom authentication factors with Trusted Auth Tokens.
Trusted Auth Tokens let you represent custom authentication factors in Stytch by signing your own JWTs and exchanging them for sessions.
Typically, your existing identity infrastructure will provide a JWT that can be used for this purpose such as an `access_token` or `id_token`. Stytch will use the JWKS endpoint hosted by your existing infrastructure to validate these JWTs and provision users automatically as required -- no additional setup is needed.
If your identity infrastructure does not provide a JWT, you can create your own JWT using a private key.
## Minting a JWT and logging in
Use a library such as OpenSSL to generate an RSA public-private keypair.
The public key can be added to your Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/dashboard/trusted-auth-tokens) (in the JWKS URL row, click "Use public keys instead").
The private key should be kept secret and exposed to your application using an environment variable.
It is often useful to base64 encode the private key for easy addition to `.env` files.
```sh theme={null}
# Generate private key
openssl genrsa -out "private_key.pem" 2048
# Extract public key
openssl rsa -in "private_key.pem" -pubout -out "public_key.pem"
# Base64 encode the private key
base64 < ./private_key.pem
```
Use any compatible JWT library to sign your JWT with your private key.
Here is an example using [jose](https://github.com/panva/jose), a popular library in the Node ecosystem.
Be sure to set the `iss` and `aud` claims to match the issuer and audience configured in your Trusted Auth Token Profile.
Choose a short expiry for the JWT - 5 minutes is a good default.
Other claims (`email`, `sub`, `role_ids`, `jti`, etc... ) should be set according to your previously configured [attribute mapping](/docs/multi-tenant-auth/authentication/trusted-auth-tokens/overview#setting-up-a-trusted-auth-token-profile).
Remember, an email and a token ID are required, and if you want Stytch to provision organizations in real-time, then organization ID is required as well. All other fields are optional.
```js theme={null}
import { importPKCS8, SignJWT } from 'jose';
import { randomBytes } from 'node:crypto';
async function createJWT({email, role_assignments, user_id, token_id}) {
// Expose the private key as an environment variable
// If you base64 encoded it before, remember to decode it here as well
const privateKeyString = Buffer.from(process.env.PRIVATE_KEY, 'base64')
.toString('utf8').trim();
const privateKey = await importPKCS8(privateKeyString, 'RS256');
// details to encode in the token
const claims = {
// Required - the user's email address
'email': email,
// Optional - RBAC roles to assign to the user
'assignments': role_assignments,
};
// Required - a token ID used to uniquely identify the credential
// This can be defined by your application or a random string
const tokenID = token_id || randomBytes(30).toString('base64');
const jwt = await new SignJWT(claims)
.setProtectedHeader({ alg: 'RS256' })
.setIssuedAt()
.setSubject(user_id) // sub claim, used as the user's external ID
.setJti(tokenID)
.setIssuer('https://auth.example.com') // iss claim
.setAudience('https://api.example.com') // aud claim
.setExpirationTime('5 minutes') // token expiration time
.sign(privateKey);
return jwt;
};
createJWT({
email: 'ada.lovelace@example.com',
role_assignments: ['editor', 'reader'],
user_id: 'user_123456'
}).then(jwt => console.log(jwt))
```
After you have minted your JWT, call the [Attest Session](/docs/api-reference/b2b/api/sessions/attest-session) API endpoint to exchange the JWT for a Stytch Session.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.B2BClient({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
createJWT({
email: 'ada.lovelace@example.com',
tenant: "cust_56789",
role_assignments: ['editor', 'reader'],
member_id: 'user_123456'
})
.then(jwt => {
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...",
};
return client.sessions.attest(params)
})
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
## Adding custom auth factors
To add custom auth factors to an existing flow, call the [Attest Session](/docs/api-reference/b2b/api/sessions/attest-session) API endpoint with a `session_token` or `session_jwt` to attach the attestation to an existing session instead of creating a new one.
It may be useful to pass in a unique `token_id` to the JWT to tie the custom authentication factor you validated to the session for later reference.
```js theme={null}
validateCustomFactor()
.then(validation => createJWT({
email: 'ada.lovelace@example.com',
tenant: "cust_56789",
role_assignments: ['editor', 'reader'],
member_id: 'user_123456',
// ID of the validation event - will be passed to the Stytch session as
// trusted_auth_token_factor.token_id
token_id: validation.validation_id,
}))
.then(jwt => {
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...",
// Pass a session token in to add your custom factor to an existing session
session_token: "SESSION_TOKEN",
};
return client.sessions.attest(params)
})
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
# External identity providers
Source: https://stytch.com/docs/multi-tenant-auth/authentication/trusted-auth-tokens/external-idps
Exchange third-party JWTs for Stytch sessions using Trusted Auth Tokens.
Trusted Auth Tokens let you attest end-user identities by exchanging signed JWTs for Stytch sessions.
Many existing identity infrastructure tools will provide a JWT that can be used for this purpose, such as an
`access_token` or `id_token`.
Stytch will use the JWKS endpoint hosted by your existing infrastructure to validate these JWTs.
This guide uses Supabase as an example, but the same pattern works for any provider with a public JWKS.
## Logging a user in
[In the Stytch Dashboard](https://stytch.com/dashboard/trusted-auth-tokens), create a Trusted Auth Token Profile for your external provider. In order to validate JWTs from Supabase, set the following values:
| Name | Value |
| - | - |
| Issuer | `https://$project-id.supabase.co/auth/v1/` |
| Audience | `authenticated` |
| JWKS | `https://$project-id.supabase.co/auth/v1/.well-known/jwks.json` |
Add an attribute mapping for the claims you want to import. To map Supabase user information to Stytch, set the following:
| Name | Value |
| - | - |
| email | `email` |
| token\_id | `session_id` |
After you mint or retrieve the provider JWT, send it to your backend so it can be exchanged for a Stytch session. For Supabase, use the [Supabase SDK](https://supabase.com/docs/reference/javascript/auth-getsession) to retrieve an access token JWT:
```js theme={null}
// On the frontend
const { data } = await supabase.auth.getSession()
fetch('/api/exchange-supabase-session', {
method: 'POST',
data: JSON.stringify({ access_token: data.session.access_token })
})
```
Use the [Attest Session endpoint](/docs/api-reference/b2b/api/sessions/attest-session) to exchange the token for a Stytch session.
```js theme={null}
// On the backend
const client = new stytch.B2BClient({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...", // access token from Supabase
organization_id: "organization-..."
};
client.sessions.attest(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
You now have a Stytch session linked to the external user identity.
# Trusted auth tokens overview
Source: https://stytch.com/docs/multi-tenant-auth/authentication/trusted-auth-tokens/overview
Exchange signed JWTs for Stytch sessions to support external identity providers and custom auth factors.
Trusted Auth Tokens let you attest end-user identities by exchanging signed JWTs for Stytch sessions. Use them to integrate external identity providers or custom authentication factors without building bespoke OIDC/OAuth support.
## Use cases
Trusted auth tokens support a range of powerful patterns:
* **3rd-party SSO integrations**: Exchange external identity provider tokens (like Vercel or Zendesk) for Stytch sessions.
* **Bring-your-own-auth**: Accept JWTs your product already issues.
* **Custom auth factors**: Add external factors (biometrics, device attestation) and represent them in the Stytch session.
***
## How it works
Define the JWT issuer and audience, add public key(s) or a JWKS URL, and map JWT claims to Stytch attributes.
Mint a JWT in your backend or accept one from a 3rd-party identity provider.
Call the [Attest Session endpoint](/docs/api-reference/b2b/api/sessions/attest-session) to create or extend a Stytch session.
### Configuring a Trusted Auth Token profile
In the Stytch Dashboard, navigate to the [Trusted Auth Tokens](https://stytch.com/dashboard/trusted-auth-tokens) page. Here, you can create a new Trusted Auth Token Profile for the provider of the tokens that you want to attest, or view and edit existing profiles.
The issuer (`iss`) and audience (`aud`) should match the corresponding values in the JWTs that you are trying to attest.
Attribute mappings are used to tie per-member claims within the JWT to Stytch platform attributes. The following attribute mappings are available today:
| Attribute | Required | Purpose |
| - | - | - |
| `email` | Yes | The email address of the member identified by the JWT |
| `token_id` | Yes | A unique identifier for the JWT |
| `organization_id` | No | The Organization ID or [external organization id](/docs/resources/migrations/external-ids) of the member |
| `external_member_id` | No | An optional [external member id](/docs/resources/migrations/external-ids) to attach to the member |
| `role_ids` | No | An string array of RBAC [Role IDs](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy#roles) to assign to the member |
### JIT provisioning
By default, Trusted Auth Tokens cannot be used to create new Members and Organizations, and can only be used to authenticate an existing Member.
To allow Trusted Auth Tokens to create new Members and Organizations, enable **Allow JIT Provisioning** in the dashboard.
Additionally, to allow Organization JIT provisioning, you will need to:
1. Add an `organization_id` attribute mapping to your Trusted Auth Token profile
2. Ensure your JWT contains the mapped claim for `organization_id`
3. Call [Attest Session](/docs/api-reference/b2b/api/sessions/attest-session) without passing an `organization_id` parameter
If the value of the `organization_id` claim doesn't match an existing Organization, a new Organization will be JIT provisioned using the claim as the `external_id`.
### Exchanging a Trusted Auth Token for a Session
Once you have a profile set up for the source of your trusted auth tokens, you can use the backend API to exchange a token for a Stytch session, or add it as an auth factor for an existing session using the [Attest Session](/docs/api-reference/b2b/api/sessions/attest-session) API endpoint. The API endpoint is available in all Stytch Backend and Frontend SDKs.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.B2BClient({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...",
organization_id: "organization-..."
};
client.sessions.attest(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
Some things to note about attested sessions:
* They are not mutable and cannot have new authentication factors added, and cannot have their duration extended.
* They bypass Organization auth requirements, including MFA.
## Putting it all together
Suppose you configure a Trusted Auth Token Profile with an issuer of `https://auth.example.com`, an audience of `https://api.example.com` and the following attribute mapping:
* `email`: `email`
* `token_id`: `jti`
* `organization_id`: `tenant`
* `external_user_id`: `sub`
* `roles`: `assignments`
A JWT with the following claims:
```json theme={null}
{
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"sub": "user_123456",
"email": "ada.lovelace@example.com",
"tenant": "cust_56789",
"jti": "tok_654321",
"assignments": [
"editor",
"reader"
]
}
```
Can be exchanged to log in or create a Member with the following properties:
```json theme={null}
{
"member_id": "member-...",
"external_id": "user_123456",
"email": "ada.lovelace@example.com",
"email_address_verified": true,
"roles": [
"stytch_member",
"editor",
"reader"
]
}
```
As well as a Member session with the following properties:
```json theme={null}
{
"member_id": "member-...",
"organization_id": "organization-...",
"authentication_factors": [
{
"delivery_method": "trusted_token_exchange",
"trusted_auth_token_factor": {
"token_id": "tok_654321"
}
}
]
}
```
***
## What's next
* Accept credentials from an [External Identity Provider](/docs/multi-tenant-auth/authentication/trusted-auth-tokens/external-idps)
* Create Trusted Auth Tokens for [custom auth factors](/docs/multi-tenant-auth/authentication/trusted-auth-tokens/custom-factors)
* Add [custom claims](/docs/multi-tenant-auth/manage-sessions/custom-claims) to session JWTs
# User impersonation
Source: https://stytch.com/docs/multi-tenant-auth/authentication/user-impersonation
Learn about Stytch's built-in user impersonation functionality.
Stytch offers user impersonation functionality that allows you to log into your application as a specific end user
in order to troubleshoot and debug securely.
Note that the user impersonation functionality described here does **not** allow your customers' administrators to impersonate Members of
their Organization. Rather, it allows members of your own company to impersonate end users.
## Enabling user impersonation for your Stytch project
User impersonation is disabled by default for each of your Stytch projects. In order to enable it, a member of your team with the
Admin role in the Stytch Dashboard should navigate to the [Password and User Policies page](https://stytch.com/dashboard/password-strength-config)
and toggle on the **Allow user impersonation** setting. Be sure to click **Save** after modifying the setting.
## Generating a user impersonation token
Once user impersonation has been enabled for your project, team members with the Admin, Developer, or Support Manager [roles](/docs/resources/workspace-management/dashboard-roles-and-permissions)
can begin the user impersonation flow by selecting the Member they'd like to impersonate on the [Members page](https://stytch.com/dashboard/members),
scrolling down, and clicking **Impersonate Member**.
Your team member will be asked to provide a reason for impersonating the Member, which will be available later on for auditing purposes. They'll then click
**Launch in new tab**, which initiates the user impersonation flow in a new browser tab at your project's default login redirect URL
with an impersonation token appended to the query parameters. Here's an example:
```
https://yourdomain.com/authenticate?stytch_token_type=multi_tenant_impersonation&token=yXxYg2g2h4Ythaue-PwvKpxgTDw5ObE_MFsC1JLwefia
```
Note that it is **not** possible to generate a user impersonation token programmatically. If that is a requirement for your
use case, see the [Programmatic user impersonation](/docs/multi-tenant-auth/authentication/user-impersonation#programmatic-user-impersonation) section below.
## Authenticating a user impersonation token
Your application should then retrieve the impersonation token from your login redirect URL's query parameters and authenticate it via the
[Authenticate impersonation token endpoint](/docs/api-reference/b2b/api/impersonation/authenticate-token).
If you're using our frontend JavaScript SDK, you can authenticate the token via the [authenticateByUrl method](/docs/api-reference/b2b/frontend-sdks/vanilla-js/methods/impersonation/authenticate).
However, you'll first need to enable the **Authenticate member impersonation tokens** setting on the [Frontend SDK Configuration page](https://stytch.com/dashboard/sdk-configuration) in the Stytch Dashboard.
At this point, you'll receive a new Member Session for the target user. Member Sessions created through user impersonation always have a duration of one hour, and can be explicitly revoked via the
[Revoke Session endpoint](/docs/api-reference/b2b/api/sessions/revoke-session). They contain a single factor in their `authentication_factors` array of type `impersonated` that includes the impersonator's email address.
Here's an example Member Session that was created through user impersonation:
```json theme={null}
"member_session": {
"authentication_factors": [
{
"created_at": "2026-04-27T14:57:20Z",
"delivery_method": "impersonation",
"impersonated_factor": {
"impersonator_email_address": "email@example.com",
"impersonator_id": "member-live-..."
},
"last_authenticated_at": "2026-04-27T14:57:20Z",
"sequence_order": "PRIMARY",
"type": "impersonated",
"updated_at": "2026-04-27T14:57:20Z"
}
],
"custom_claims": {...},
"expires_at": "2026-04-27T15:57:20Z",
"last_accessed_at": "2026-04-27T14:57:48Z",
"member_id": "member-live-...",
"member_session_id": "member-session-live-...",
"organization_id": "organization-live-...",
"organization_slug": "example",
"roles": [...],
"started_at": "2026-04-27T14:57:20Z"
}
```
Member Sessions created through user impersonation can be authenticated via the [Authenticate session](/docs/api-reference/b2b/api/sessions/authenticate-session)
endpoints or SDK methods using the `session_token` or `session_jwt` values from the Authenticate user impersonation token response.
## Audit logging
After a user impersonation token is authenticated, you'll find an event in the Stytch Dashboard's [Activity tab](https://stytch.com/dashboard/activity) with
an `action` value of `B2BAuthenticateMemberImpersonationToken`. This event will contain additional details about the user impersonation,
such as the `member_id`, `organization_id`, the impersonator's email address, and the reason that the impersonator provided for the
impersonation. If you take advantage of our [Event log streaming](/docs/resources/workspace-management/event-log-streaming) feature,
the user impersonation logs are available there as well.
We also emit a [webhook](https://stytch.com/dashboard/webhooks) with an event type of `direct.member.impersonate` whenever
a Member is impersonated.
## Programmatic user impersonation
For security purposes, we do not provide an API endpoint that generates user impersonation tokens. However, programmatic user
impersonation may be desirable for some use cases.
If you understand and accept the security implications, it is possible to use our Trusted Auth Tokens feature to programmatically
generate a Stytch Session on behalf of a Member. See our [Custom auth factors](/docs/multi-tenant-auth/authentication/trusted-auth-tokens/custom-factors)
guide for implementation instructions.
If you choose to use Trusted Auth Tokens for user impersonation, you will be responsible for maintaining your own access controls, audit logs,
and other security guardrails.
If implemented improperly or without sufficient protections against misuse, user impersonation can introduce serious security
risks in your application.
# Customize Stytch UI
Source: https://stytch.com/docs/multi-tenant-auth/build-auth/branding/custom-themes
How to customize Stytch UI to fit the look and feel of your app
Many elements of Stytch UI can be customized. You can use our pre-built, bring your own shadcn/ui themes, or write your own theme. You can also replace any string appearing in Stytch UI. Select your package to see all customization options.
Here are some examples of customized Stytch UI components:
[From tweakcn](https://tweakcn.com/editor/theme?theme=neo-brutalism)
[From tweakcn](https://tweakcn.com/editor/theme?theme=bubblegum)
[From tweakcn](https://tweakcn.com/editor/theme?theme=amethyst-haze)
# Integration options
Source: https://stytch.com/docs/multi-tenant-auth/build-auth/custom/overview
Using Stytch's SDKs or API to build your own custom auth flow.
## Flexible architecture
Stytch is architected to be embedded directly into your application, offering you flexibility and control over your integration and creating a seamless and native experience for users. Most customers mix-and-match approaches to fit their needs:
Your client invokes Stytch's headless frontend SDKs, which handles things like client-side session management.
Entirely server-side integration, where your backend calls Stytch's API through our backend SDK.
***
## Using the frontend SDK
Using the frontend SDK headlessly enables you to build your own custom auth flow — directly on the frontend. However, there are certain operations that can only be done with a backend implementation. A high-level implementation might look like:
1. The end user attempts to log into your application with your custom UI.
2. Your frontend handles the UI events, collects all the necessary authentication data, and then utilizes Stytch's frontend SDK methods to call the Stytch API to perform an auth-related operation.
3. Stytch API processes the request and returns a response with pertinent data.
4. Your frontend handles the response as needed, which may involve calling the Stytch API again, updating your UI, or relaying the data to your backend.
5. Once the end user successfully authenticates, Stytch's frontend SDK automatically manages the storage of session tokens using browser cookies or mobile storage.
## Using the backend SDK or API
As the most feature-complete and flexible option, our backend API and SDKs enable you to tightly integrate Stytch's auth primitives with your application's logic. A high-level, server-side implemention of Stytch would look like:
1. Your backend collects all necessary authentication data (e.g., IDs, tokens, emails, and metadata) and calls the Stytch API to perform a specific auth related operation (e.g., initiate an auth flow, retrieve an organization or member record, and refresh a session).
2. Stytch API processes the request and returns a response with pertinent data (e.g., minted session, organization data, auth metadata, and statuses).
3. Your backend handles the response and transforms the data as needed, which may involve calling the Stytch API again, passing the data to a different backend or microservice, or relaying data back to your frontend.
# Via backend SDK or API
Source: https://stytch.com/docs/multi-tenant-auth/build-auth/custom/via-api
Examples and considerations when doing a server-side integration with Stytch.
Our [frontend SDK](./via-sdk) provide automatic session management, prebuilt login or signup, multi-step flow handling, and more. They can also be used alongside our backend API or SDKs.
## Quickstart
This quickstart guides you through building a Discovery login flow. Select a framework:
Node.js code examples
Hosted Node.js demo
Stytch Node.js library
API & SDK references
* Have your `public_token` and `secret_key` from your Stytch project & environment.
* While the guide uses Express, the Node.js SDK is framework agnostic.
1. Install the SDK in your Express environment:
```bash npm icon="terminal" theme={null}
npm install stytch
```
2. Then add your Stytch API keys to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
# Use your Project's 'live' or any available 'test' credentials
```
1. Initialize the Stytch client with your environment variables.
2. Create a `/login` route that takes in a user's email address and initiates the login or signup flow.
```javascript title="POST '/login'" lines theme={null}
const express = require('express');
const stytch = require('stytch');
const session = require('express-session');
const bodyParser = require('body-parser');
const app = express();
app.use(express.json());
/**
* Express session management
*/
app.use(session({
resave: true,
saveUninitialized: false,
secret: 'session-signing-secret',
cookie: {maxAge: 60000}
}));
const stytchClient = new stytch.B2BClient({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
// Key to retrieve the session token from express-session
// Referenced as req.session[StytchSessionToken]
const StytchSessionToken = 'stytch_session_token'
app.post('/login', (req, res) => {
const email = req.body.email;
stytchClient.magicLinks.email.discovery.send({
email_address: email
})
.then(response => {
res.json(response)
})
.catch(err => {
res.status(500).send(err.toString())
});
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
```
1. Stytch calls the [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) with a token to complete a user's authentication flow.
2. Add an `/authenticate` route to handle the redirect callback in your app.
```javascript title="GET '/authenticate'" lines theme={null}
app.get('/authenticate', async (req, res) => {
const token = req.query.token;
const tokenType = req.query.stytch_token_type;
// Handle Discovery authentication.
if (tokenType !== 'discovery') {
console.error(`Unrecognized token type: '${tokenType}'`);
res.status(400).send();
return;
}
const authResp = await stytchClient.magicLinks.discovery.authenticate({
discovery_magic_links_token: token,
});
if (authResp.status_code !== 200) {
console.error('Authentication error')
res.status(500).send();
return;
}
});
```
1. At this point, a user has been authenticated but still needs to specify an [Organization](/docs/api-reference/b2b/api/organizations/organization-object) to log in to and begin a session.
2. Stytch provides an `intermediate_session_token` that you can use to preserve the user's authentication state while you provide them with options to proceed (e.g. selecting from a list of organizations).
3. For this example, we'll add logic to automatically sign in to an existing organization or create one if none already exist:
```javascript title="GET '/authenticate'" lines theme={null}
app.get('/authenticate', async (req, res) => {
const token = req.query.token;
const tokenType = req.query.stytch_token_type;
// Handle Discovery authentication.
if (tokenType !== 'discovery') {
console.error(`Unrecognized token type: '${tokenType}'`);
res.status(400).send();
return;
}
const authResp = await stytchClient.magicLinks.discovery.authenticate({
discovery_magic_links_token: token,
});
if (authResp.status_code !== 200) {
console.error('Authentication error')
res.status(500).send();
return;
}
// Sign into existing Organization if already Member // [!code ++:32]
const ist = authResp.intermediate_session_token
if (authResp.discovered_organizations.length > 0 ) {
const exchangeResp = await stytchClient.discovery.intermediateSessions.exchange({
intermediate_session_token: ist,
organization_id: authResp.discovered_organizations[0].organization.organization_id,
});
if (exchangeResp.status_code !== 200) {
console.error(`Error exchanging IST into Organization: ${JSON.stringify(exchangeResp, null, 2)}`);
res.status(500).send();
return;
}
// Store the returned session and return session member information
// Using express sessions with the const key of StytchSessionToken
req.session[StytchSessionToken] = exchangeResp.session_token;
res.send(`Hello, ${authResp.member.email_address}! You're logged into the ${authResp.organization.organization_name} organization`);
return;
}
// If not eligible to log into an existing org, create new one
const createResp = await stytchClient.discovery.organizations.create({
intermediate_session_token: ist,
});
if (createResp.status_code !== 200) {
console.error(`Error creating Organization: '${JSON.stringify(createResp, null, 2)}'`);
res.status(500).send();
return;
}
// Store the returned session and return session member information
req.session[StytchSessionToken] = createResp.session_token;
res.send(`Hello, ${createResp.member.email_address}! You're logged into the ${createResp.organization.organization_name} organization`);
return;
});
```
1. Create a helper method that returns the user's [Session](/docs/api-reference/b2b/api/sessions/session-object) information.
2. Use the method to gate any protected route that should only be accessed by an authenticated user.
```javascript title="GET '/dashboard'" lines theme={null}
app.get('/dashboard', async (req, res) => {
const sessionInfo = await getAuthenticatedMemberAndOrg(req);
if (sessionInfo && sessionInfo.member && sessionInfo.organization) {
res.send(`Hello, ${sessionInfo.member.email_address}! You're logged into the ${sessionInfo.organization.organization_name} organization`);
return;
}
res.send("Log in to view this page");
});
async function getAuthenticatedMemberAndOrg(req) {
const sessionToken = req.session[StytchSessionToken];
if (!sessionToken) {
return null;
}
const resp = await stytchClient.sessions.authenticate({session_token: sessionToken});
if (resp.status_code !== 200) {
console.error('Invalid session found');
req.session[StytchSessionToken] = undefined;
return null;
}
req.session[StytchSessionToken] = resp.session_token;
return {
member: resp.member,
organization: resp.organization,
};
}
```
```javascript expandable lines theme={null}
const express = require('express');
const stytch = require('stytch');
const session = require('express-session');
const bodyParser = require('body-parser');
const app = express();
app.use(express.json());
/**
* Express session management
*/
app.use(session({
resave: true,
saveUninitialized: false,
secret: 'session-signing-secret',
cookie: {maxAge: 60000}
}));
const stytchClient = new stytch.B2BClient({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
// Key to retrieve the session token from express-session
// Referenced as req.session[StytchSessionToken]
const StytchSessionToken = 'stytch_session_token'
/**
* Retrieves the authenticated member and organization from the session, if present.
* @param req Express request.
* @returns {Promise<{organization: Organization, member: Member}|null>}
*/
async function getAuthenticatedMemberAndOrg(req) {
const sessionToken = req.session[StytchSessionToken];
if (!sessionToken) {
return null;
}
const resp = await stytchClient.sessions.authenticate({session_token: sessionToken});
if (resp.status_code !== 200) {
console.error('Invalid session found');
req.session[StytchSessionToken] = undefined;
return null;
}
req.session[StytchSessionToken] = resp.session_token;
return {
member: resp.member,
organization: resp.organization,
};
}
/**
* Routes
*/
app.post('/login', (req, res) => {
const email = req.body.email;
stytchClient.magicLinks.email.discovery.send({
email_address: email
})
.then(response => {
res.json(response)
})
.catch(err => {
res.status(500).send(err.toString())
});
});
app.get('/authenticate', async (req, res) => {
const token = req.query.token;
const tokenType = req.query.stytch_token_type;
// Handle Discovery authentication.
if (tokenType !== 'discovery') {
console.error(`Unrecognized token type: '${tokenType}'`);
res.status(400).send();
return;
}
const authResp = await stytchClient.magicLinks.discovery.authenticate({
discovery_magic_links_token: token,
});
if (authResp.status_code !== 200) {
console.error('Authentication error')
res.status(500).send();
return;
}
// Sign into existing Organization if already Member
const ist = authResp.intermediate_session_token
if (authResp.discovered_organizations.length > 0 ) {
const exchangeResp = await stytchClient.discovery.intermediateSessions.exchange({
intermediate_session_token: ist,
organization_id: authResp.discovered_organizations[0].organization.organization_id,
});
if (exchangeResp.status_code !== 200) {
console.error(`Error exchanging IST into Organization: ${JSON.stringify(exchangeResp, null, 2)}`);
res.status(500).send();
return;
}
// Store the returned session and return session member information
req.session[StytchSessionToken] = exchangeResp.session_token;
res.redirect('/dashboard');
return;
}
// If not eligible to log into an existing org, create new one
const createResp = await stytchClient.discovery.organizations.create({
intermediate_session_token: ist,
});
if (createResp.status_code !== 200) {
console.error(`Error creating Organization: '${JSON.stringify(createResp, null, 2)}'`);
res.status(500).send();
return;
}
// Store the returned session and return session member information
req.session[StytchSessionToken] = createResp.session_token;
res.redirect('/dashboard');
return;
});
app.get('/logout', (req, res) => {
req.session[StytchSessionToken] = undefined;
res.redirect('/dashboard');
});
app.get('/dashboard', async (req, res) => {
const sessionInfo = await getAuthenticatedMemberAndOrg(req);
if (sessionInfo && sessionInfo.member && sessionInfo.organization) {
res.send(`Hello, ${sessionInfo.member.email_address}! You're logged into the ${sessionInfo.organization.organization_name} organization`);
return;
}
res.send("Log in to view this page");
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click on the magic link you recieved to finish the login or signup flow.
Go code examples
Stytch Go library
API & SDK references
Have your `public_token` and `secret_key` from your Stytch project & environment.
1. Install the SDK:
```bash go icon="terminal" theme={null}
go get github.com/stytchauth/stytch-go/v16
```
2. Then add your Stytch API keys to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
# Use your Project's 'live' or any available 'test' credentials
```
1. Initialize the Stytch client with your environment variables.
2. Register an HTTP handler for a `/login` route that takes in a user's email address and initiates the login or signup flow:
```go lines theme={null}
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
gorillaSessions "github.com/gorilla/sessions"
"github.com/joho/godotenv"
"github.com/stytchauth/stytch-go/v16/stytch/b2b/b2bstytchapi"
discoveryOrgs "github.com/stytchauth/stytch-go/v16/stytch/b2b/discovery/organizations"
"github.com/stytchauth/stytch-go/v16/stytch/b2b/magiclinks/discovery"
emlDiscovery "github.com/stytchauth/stytch-go/v16/stytch/b2b/magiclinks/email/discovery"
)
var ctx = context.Background()
const sessionKey = "stytch_session_token"
func main() {
// Load variables from .env file into the environment.
if err := godotenv.Load(".env.local"); err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
// Instantiate a new API service.
service := NewAuthService(
os.Getenv("STYTCH_PROJECT_ID"),
os.Getenv("STYTCH_SECRET"),
)
// Register HTTP handlers.
mux := http.NewServeMux()
mux.HandleFunc("/login", service.sendMagicLinkHandler)
// Start server.
server := http.Server{
Addr: ":3000",
Handler: mux,
}
log.Println("WARNING: For testing purposes only. Not intended for production use...")
log.Println("Starting server on http://localhost:3000")
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
type AuthService struct {
client *b2bstytchapi.API
store *gorillaSessions.CookieStore
}
func NewAuthService(projectId, secret string) *AuthService {
client, err := b2bstytchapi.NewClient(projectId, secret)
if err != nil {
log.Fatalf("Error creating client: %v", err)
}
return &AuthService{
client: client,
store: gorillaSessions.NewCookieStore([]byte("your-secret-key")),
}
}
func (s *AuthService) sendMagicLinkHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
log.Printf("Error parsing form: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
email := r.Form.Get("email")
if email == "" {
http.Error(w, "Email is required", http.StatusBadRequest)
return
}
_, err := s.client.MagicLinks.Email.Discovery.Send(
ctx,
&emlDiscovery.SendParams{
EmailAddress: email,
})
if err != nil {
log.Printf("Error sending email: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Successfully sent magic link email!")
}
```
1. Stytch calls the [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) with a token to complete a user's authentication flow.
2. Add a route to handle the `/authenticate` redirect callback in your app.
```go lines theme={null}
import (
// [!code ++:2]
// Add new imports
"github.com/stytchauth/stytch-go/v16/stytch/b2b/magiclinks/discovery"
)
func main() {
// [!code ++:2]
// Add new HTTP handler
mux.HandleFunc("/authenticate", service.authenticateHandler)
}
// [!code ++:25]
func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
tokenType := r.URL.Query().Get("stytch_token_type")
token := r.URL.Query().Get("token")
if tokenType != "discovery" {
log.Printf("Error: unrecognized token type %s\n", tokenType)
http.Error(
w,
fmt.Sprintf("Unrecognized token type %s", tokenType),
http.StatusBadRequest,
)
return
}
resp, err := s.client.MagicLinks.Discovery.Authenticate(
ctx,
&discovery.AuthenticateParams{
DiscoveryMagicLinksToken: token,
})
if err != nil {
log.Printf("Error authenticating: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
```
1. At this point, a user has been authenticated but still needs to specify an [Organization](/docs/api-reference/b2b/api/organizations/organization-object) to log in to and begin a session.
2. `s.client.MagicLinks.Discovery.Authenticate()` returns an `intermediate_session_token` that you can use to preserve the user's authentication state while you provide them with options to proceed (e.g. selecting from a list of organizations).
3. For this example, we'll add logic to automatically sign in to an existing organization or create one if none already exist:
```go lines theme={null}
import (
// [!code ++:3]
// Add new imports
"github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/intermediatesessions"
discoveryOrgs "github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/organizations"
)
func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
tokenType := r.URL.Query().Get("stytch_token_type")
token := r.URL.Query().Get("token")
if tokenType != "discovery" {
log.Printf("Error: unrecognized token type %s\n", tokenType)
http.Error(
w,
fmt.Sprintf("Unrecognized token type %s", tokenType),
http.StatusBadRequest
)
return
}
resp, err := s.client.MagicLinks.Discovery.Authenticate(
ctx,
&discovery.AuthenticateParams{
DiscoveryMagicLinksToken: token,
})
if err != nil {
log.Printf("Error authenticating: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// [!code ++:42]
ist := resp.IntermediateSessionToken
var sessionToken string
if len(resp.DiscoveredOrganizations) > 0 {
organizationId := resp.DiscoveredOrganizations[0].Organization.OrganizationID
resp, err := s.client.Discovery.IntermediateSessions.Exchange(
ctx,
&intermediatesessions.ExchangeParams{
IntermediateSessionToken: ist,
OrganizationID: organizationId,
})
if err != nil {
log.Printf("Error exchanging organization: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sessionToken = resp.SessionToken
} else {
resp, err := s.client.Discovery.Organizations.Create(
ctx,
&discoveryOrgs.CreateParams{
IntermediateSessionToken: ist,
})
if err != nil {
log.Printf("Error creating organization: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sessionToken = resp.SessionToken
}
// Store the session token
session, _ := s.store.Get(r, sessionKey)
session.Values["token"] = sessionToken
_ = session.Save(r, w)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(
w,
"Welcome %s! You're logged into the %s organization",
resp.Member.EmailAddress,
resp.Organization.OrganizationName
)
}
```
1. Create a helper method that returns the user's [Session](/docs/api-reference/b2b/api/sessions/session-object) information.
2. Use the method to gate any protected route that should only be accessed by an authenticated user.
```go lines theme={null}
import (
// [!code ++:3]
// Add new import
"github.com/stytchauth/stytch-go/v15/stytch/b2b/sessions"
"github.com/stytchauth/stytch-go/v15/stytch/b2b/organizations"
)
func main() {
// [!code ++:2]
// Add new HTTP handler
mux.HandleFunc("/", service.indexHandler)
}
// [!code ++:15]
func (s *AuthService) indexHandler(w http.ResponseWriter, r *http.Request) {
member, organization, ok := s.authenticatedMemberAndOrg(w, r)
if !ok {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Please log in to see this page")
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(
w,
"Welcome %s! You're logged into the %s organization",
member.EmailAddress,
organization.OrganizationName,
)
}
// [!code ++:23]
func (s *AuthService) authenticatedMemberAndOrg(
w http.ResponseWriter,
r *http.Request,
) (*organizations.Member, *organizations.Organization, bool) {
sessionToken, exists := s.getSession(w, r, sessionKey)
if (!exists) {
return nil, nil, false
}
resp, err := s.client.Sessions.Authenticate(
ctx,
&sessions.AuthenticateParams{
SessionToken: sessionToken,
})
if err != nil {
log.Printf("Error authenticating session: %v\n", err)
s.clearSession(w, r, sessionKey)
return nil, nil, false
}
return &resp.Member, &resp.Organization, true
}
// [!code ++:5]
func (s *AuthService) clearSession(w http.ResponseWriter, r *http.Request, key string) {
session, _ := s.store.Get(r, key)
delete(session.Values, "token")
_ = s.store.Save(r, w, session)
}
// [!code ++:8]
func (s *AuthService) getSession(w http.ResponseWriter, r *http.Request, key string) (string, bool) {
session, err := s.store.Get(r, key)
if session == nil || err != nil {
return "", false
}
token, ok := session.Values["token"].(string)
return token, token != "" && ok
}
```
```go expandable lines theme={null}
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
gorillaSessions "github.com/gorilla/sessions"
"github.com/joho/godotenv"
"github.com/stytchauth/stytch-go/v15/stytch/b2b/b2bstytchapi"
"github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/intermediatesessions"
discoveryOrgs "github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/organizations"
"github.com/stytchauth/stytch-go/v15/stytch/b2b/magiclinks/discovery"
emlDiscovery "github.com/stytchauth/stytch-go/v15/stytch/b2b/magiclinks/email/discovery"
"github.com/stytchauth/stytch-go/v15/stytch/b2b/organizations"
"github.com/stytchauth/stytch-go/v15/stytch/b2b/sessions"
)
var ctx = context.Background()
const sessionKey = "stytch_session_token"
func main() {
// Load variables from .env file into the environment.
if err := godotenv.Load(".env.local"); err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
// Instantiate a new API service.
service := NewAuthService(
os.Getenv("STYTCH_PROJECT_ID"),
os.Getenv("STYTCH_SECRET"),
)
// Register HTTP handlers.
mux := http.NewServeMux()
mux.HandleFunc("/", service.indexHandler)
mux.HandleFunc("/login", service.sendMagicLinkHandler)
mux.HandleFunc("/authenticate", service.authenticateHandler)
mux.HandleFunc("/logout", service.logoutHandler)
// Start server.
server := http.Server{
Addr: ":3000",
Handler: mux,
}
log.Println("WARNING: For testing purposes only. Not intended for production use...")
log.Println("Starting server on http://localhost:3000")
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
type AuthService struct {
client *b2bstytchapi.API
store *gorillaSessions.CookieStore
}
func NewAuthService(projectId, secret string) *AuthService {
client, err := b2bstytchapi.NewClient(projectId, secret)
if err != nil {
log.Fatalf("Error creating client: %v", err)
}
return &AuthService{
client: client,
store: gorillaSessions.NewCookieStore([]byte("your-secret-key")),
}
}
func (s *AuthService) sendMagicLinkHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
log.Printf("Error parsing form: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
email := r.Form.Get("email")
if email == "" {
http.Error(w, "Email is required", http.StatusBadRequest)
return
}
_, err := s.client.MagicLinks.Email.Discovery.Send(
ctx,
&emlDiscovery.SendParams{
EmailAddress: email,
})
if err != nil {
log.Printf("Error sending email: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Successfully sent magic link email!")
}
func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
tokenType := r.URL.Query().Get("stytch_token_type")
token := r.URL.Query().Get("token")
if tokenType != "discovery" {
log.Printf("Error: unrecognized token type %s\n", tokenType)
http.Error(w, fmt.Sprintf("Unrecognized token type %s", tokenType), http.StatusBadRequest)
return
}
resp, err := s.client.MagicLinks.Discovery.Authenticate(
ctx,
&discovery.AuthenticateParams{
DiscoveryMagicLinksToken: token,
})
if err != nil {
log.Printf("Error authenticating: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
ist := resp.IntermediateSessionToken
var sessionToken string
if len(resp.DiscoveredOrganizations) > 0 {
organizationId := resp.DiscoveredOrganizations[0].Organization.OrganizationID
resp, err := s.client.Discovery.IntermediateSessions.Exchange(
ctx,
&intermediatesessions.ExchangeParams{
IntermediateSessionToken: ist,
OrganizationID: organizationId,
})
if err != nil {
log.Printf("Error exchanging organization: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sessionToken = resp.SessionToken
} else {
resp, err := s.client.Discovery.Organizations.Create(
ctx,
&discoveryOrgs.CreateParams{
IntermediateSessionToken: ist,
})
if err != nil {
log.Printf("Error creating organization: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sessionToken = resp.SessionToken
}
// Store the session token
session, _ := s.store.Get(r, sessionKey)
session.Values["token"] = sessionToken
_ = session.Save(r, w)
s.indexHandler(w, r)
}
func (s *AuthService) indexHandler(w http.ResponseWriter, r *http.Request) {
member, organization, ok := s.authenticatedMemberAndOrg(w, r)
if !ok {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Please log in to see this page")
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(
w,
"Welcome %s! You're logged into the %s organization",
member.EmailAddress,
organization.OrganizationName,
)
}
func (s *AuthService) logoutHandler(w http.ResponseWriter, r *http.Request) {
s.clearSession(w, r, sessionKey)
s.indexHandler(w, r)
return
}
func (s *AuthService) authenticatedMemberAndOrg(
w http.ResponseWriter,
r *http.Request,
) (*organizations.Member, *organizations.Organization, bool) {
sessionToken, exists := s.getSession(w, r, sessionKey)
if !exists {
return nil, nil, false
}
resp, err := s.client.Sessions.Authenticate(
ctx,
&sessions.AuthenticateParams{
SessionToken: sessionToken,
})
if err != nil {
log.Printf("Error authenticating session: %v\n", err)
s.clearSession(w, r, sessionKey)
return nil, nil, false
}
return &resp.Member, &resp.Organization, true
}
func (s *AuthService) clearSession(w http.ResponseWriter, r *http.Request, key string) {
session, _ := s.store.Get(r, key)
delete(session.Values, "token")
_ = s.store.Save(r, w, session)
}
func (s *AuthService) getSession(w http.ResponseWriter, r *http.Request, key string) (string, bool) {
session, err := s.store.Get(r, key)
if session == nil || err != nil {
return "", false
}
token, ok := session.Values["token"].(string)
return token, ok && token != ""
}
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click on the magic link you recieved to finish the login or signup flow.
Stytch Python library
API & SDK references
* Have your `public_token` and `secret_key` from your Stytch project & environment.
* While the guide uses Flask, the Python SDK is framework agnostic.
1. Install the SDK in your Express environment:
```bash pip icon="terminal" theme={null}
pip install stytch
```
2. Then add your Stytch API keys to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
# Use your Project's 'live' or any available 'test' credentials
```
1. Initialize the Stytch client with your environment variables.
2. Create a `/login` route that takes in a user's email address and initiates the login or signup flow.
```python title="POST '/login'" lines theme={null}
import os
import dotenv
from flask import Flask, request, redirect, session, url_for
from stytch import B2BClient
from stytch.core.response_base import StytchError
# load the .env file
dotenv.load_dotenv()
# Load stytch client
stytch_client = B2BClient(
project_id=os.getenv("STYTCH_PROJECT_ID"),
secret=os.getenv("STYTCH_SECRET"),
environment="test"
)
app = Flask(__name__)
app.secret_key = 'some-secret-key'
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
email = data.get("email", None)
if not email:
return "Email required"
try:
stytch_client.magic_links.email.discovery.send(
email_address=email
)
except StytchError as e:
return e.details.original_json
return "Success! Check your email"
if __name__ == '__main__':
app.run(host='localhost', port=3000, debug=True)
```
1. Stytch calls the [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) with a token to complete a user's authentication flow.
2. Add an route to handle the `/authenticate` redirect callback in your app.
```python title="GET '/authenticate'" lines theme={null}
@app.route('/authenticate', methods=['GET'])
def authenticate():
token = request.args.get('token', None)
token_type = request.args.get('stytch_token_type', None)
# Distinct token_type for each auth flow
# so you know which authenticate() method to use
if token_type != 'discovery':
return f"token_type: {token_type} not supported"
try:
resp = stytch_client.magic_links.discovery.authenticate(
discovery_magic_links_token=token,
)
except StytchError as e:
return e.details.original_json
```
1. At this point, a user has been authenticated but still needs to specify an [Organization](/docs/api-reference/b2b/api/organizations/organization-object) to log in to and begin a session.
2. `stytch_client.magic_links.discovery.authenticate()` provides an `intermediate_session_token` that you can use to preserve the user's authentication state while you provide them with options to proceed (e.g. selecting from a list of organizations).
3. For this example, we'll add logic to automatically sign in to an existing organization or create one if none already exist:
```python title="GET '/authenticate'" lines theme={null}
@app.route('/authenticate', methods=['GET'])
def authenticate():
token = request.args.get('token', None)
token_type = request.args.get('stytch_token_type', None)
# Distinct token_type for each auth flow
# so you know which authenticate() method to use
if token_type != 'discovery':
return f"token_type: {token_type} not supported"
try:
resp = stytch_client.magic_links.discovery.authenticate(
discovery_magic_links_token=token,
)
except StytchError as e:
return e.details.original_json
# [!code ++:26]
ist = resp.intermediate_session_token
discovered_orgs = resp.discovered_organizations
if len(discovered_orgs):
# email belongs to >= 1 organization, log into the first one
try:
resp = stytch_client.discovery.intermediate_sessions.exchange(
intermediate_session_token=ist,
organization_id=discovered_orgs[0].organization.organization_id,
)
except StytchError as e:
return e.details.original_json
# Store the returned session in cookies and redirect to home
session['stytch_session_token'] = resp.session_token
return redirect(url_for("dashboard"))
# email does not belong to any organizations, create a new one
try:
resp = stytch_client.discovery.organizations.create(
intermediate_session_token=ist
)
except StytchError as e:
return e.details.original_json
session['stytch_session_token'] = resp.session_token
return f"Welcome {resp.member.email_address}! You're logged into the {resp.organization.organization_name} organization"
```
1. Create a helper method that returns the user's [Session](/docs/api-reference/b2b/api/sessions/session-object) information.
2. Use the method to gate any protected route that should only be accessed by an authenticated user.
```python title="GET '/dashboard'" lines theme={null}
@app.route('/dashboard', methods=["GET"])
def dashboard():
member, organization = get_authenticated_member_and_organization()
if not member or not organization:
return "Log in to view this page."
return f"Welcome {member.email_address}! You're logged into the {organization.organization_name} org!"
def get_authenticated_member_and_organization():
stytch_session = session.get('stytch_session_token')
if not stytch_session:
return None, None
try:
resp = stytch_client.sessions.authenticate(session_token=stytch_session)
except StytchError as e:
# Session has expired or is invalid, clear it
session.pop('stytch_session_token', None)
return None, None
return resp.member, resp.organization
```
```python expandable lines theme={null}
import os
import dotenv
from flask import Flask, request, redirect, session, url_for
from stytch import B2BClient
from stytch.core.response_base import StytchError
# load the .env file
dotenv.load_dotenv()
# Load stytch client
stytch_client = B2BClient(
project_id=os.getenv("STYTCH_PROJECT_ID"),
secret=os.getenv("STYTCH_SECRET"),
environment="test"
)
app = Flask(__name__)
app.secret_key = 'some-secret-key'
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
email = data.get("email", None)
if not email:
return "Email required"
try:
stytch_client.magic_links.email.discovery.send(
email_address=email
)
except StytchError as e:
return e.details.original_json
return "Success! Check your email"
@app.route('/authenticate', methods=['GET'])
def authenticate():
token = request.args.get('token', None)
token_type = request.args.get('stytch_token_type', None)
# Distinct token_type for each auth flow
# so you know which authenticate() method to use
if token_type != 'discovery':
return f"token_type: {token_type} not supported"
try:
resp = stytch_client.magic_links.discovery.authenticate(
discovery_magic_links_token=token,
)
except StytchError as e:
return e.details.original_json
ist = resp.intermediate_session_token
discovered_orgs = resp.discovered_organizations
if len(discovered_orgs):
# email belongs to >= 1 organization, log into the first one
try:
resp = stytch_client.discovery.intermediate_sessions.exchange(
intermediate_session_token=ist,
organization_id=discovered_orgs[0].organization.organization_id,
)
except StytchError as e:
return e.details.original_json
# Store the returned session in cookies and redirect to home
session['stytch_session_token'] = resp.session_token
return redirect(url_for("dashboard"))
# email does not belong to any organizations, create a new one
try:
resp = stytch_client.discovery.organizations.create(
intermediate_session_token=ist
)
except StytchError as e:
return e.details.original_json
session['stytch_session_token'] = resp.session_token
return redirect(url_for("dashboard"))
@app.route('/dashboard', methods=["GET"])
def dashboard():
member, organization = get_authenticated_member_and_organization()
if not member or not organization:
return "Log in to view this page."
return f"Welcome {member.email_address}! You're logged into the {organization.organization_name} org!"
@app.route('/logout', methods=["GET"])
def logout():
session.pop('stytch_session_token', None)
return redirect(url_for('dashboard'))
def get_authenticated_member_and_organization():
stytch_session = session.get('stytch_session_token')
if not stytch_session:
return None, None
try:
resp = stytch_client.sessions.authenticate(session_token=stytch_session)
except StytchError as e:
# Session has expired or is invalid, clear it
session.pop('stytch_session_token', None)
return None, None
return resp.member, resp.organization
if __name__ == '__main__':
app.run(host='localhost', port=3000, debug=True)
```
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click on the magic link you recieved to finish the login or signup flow.
Stytch Ruby gem
API & SDK references
Have your `public_token` and `secret_key` from your Stytch project & environment.
1. Install the SDK in your Express environment:
```bash gem icon="terminal" theme={null}
gem 'stytch'
```
2. Then add your Stytch API keys to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
# Use your Project's 'live' or any available 'test' credentials
```
1. Initialize the Stytch client.
2. Define `/login` and `/authenticate` routes and controller actions to handle authentication.
```ruby title="config/routes.rb" icon="file-code" theme={null}
Rails.application.routes.draw do
post 'login', to: 'authentication#login'
get 'authenticate', to: 'authentication#authenticate'
end
```
```ruby title="app/controllers/authentication_controller.rb" icon="file-code" theme={null}
class AuthenticationController < ApplicationController
def stytch_client
@stytch_client ||= StytchB2B::Client.new(
project_id: ENV['STYTCH_PROJECT_ID'],
secret: ENV['STYTCH_SECRET']
)
endf
def login
email = params[:email]
response = stytch_client.magic_links.email.discovery.send(
email_address: email
)
render json: response
rescue => e
render json: { error: e.message }, status: :internal_server_error
end
def authenticate
token = params[:token]
response = stytch_client.magic_links.discovery.authenticate(discovery_magic_links_token: token)
render plain: "Hello, #{response.email_address}! Complete the Discovery flow by creating an Organization with your intermediate session token: #{response.intermediate_session_token}."
rescue => e
render plain: e.message, status: :unauthorized
end
end
```
Specify an [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) in your Dashboard.
* Run your application and send a `POST` request to the `/login` endpoint with your email address.
* You should receive an email magic link in your inbox.
* Click on the magic link, which should redirect you to the `/authenticate` endpoint or redirect URL.
Stytch Java library
API & SDK references
### Create an API client
```kotlin title="Kotlin" theme={null}
import com.stytch.java.b2b.StytchB2BClient
val client = StytchB2BClient(
projectId = "YOUR_PROJECT_ID",
secret = "YOUR_SECRET_KEY"
)
```
```java title="Java" theme={null}
import com.stytch.java.b2b.StytchB2BClient;
StytchB2BClient client = new StytchB2BClient(
"YOUR_PROJECT_ID",
"YOUR_SECRET_KEY"
);
```
### Create an organization
```kotlin title="Kotlin" theme={null}
when (val result = client.organizations.create(CreateRequest(
organizationName = "Acme Co",
organizationSlug = "acme-co",
emailAllowedDomains = listOf("acme.co"),
))) {
is StytchResult.Success -> println(result.value)
is StytchResult.Error -> println(result.exception)
}
```
```java title="Java" theme={null}
var emailAllowedDomains = new ArrayList();
emailAllowedDomains.add("acme.co");
CreateRequest request = new CreateRequest(
"Acme Co",
"acme-co",
emailAllowedDomains
);
StytchResult response = client.organizations.createCompletable(request).get();
if (response instanceof StytchResult.Error) {
var exception = ((StytchResult.Error) response).getException();
System.out.println(exception.getReason());
} else {
System.out.println(((StytchResult.Success) response).getValue());
}
```
### Log a user into an organization
```kotlin title="Kotlin" theme={null}
when (val result = client.magicLinks.email.loginOrSignup(LoginOrSignupRequest(
organizationId = "organization-id-from-create-response-...",
emailAddress = "admin@acme.co",
loginRedirectURL = "https://example.com/authenticate",
signupRedirectURL = "https://example.com/authenticate",
)) {
is StytchResult.Success -> println(result.value)
is StytchResult.Error -> println(result.exception)
}
```
```java title="Java" theme={null}
LoginOrSignupRequest request = new LoginOrSignupRequest(
"organization-id-from-create-response-...",
"admin@acme.co",
"https://example.com/authenticate",
"https://example.com/authenticate"
);
StytchResult response = client.magicLinks.getEmail().loginOrSignup(request).get();
if (response instanceof StytchResult.Error) {
var exception = ((StytchResult.Error) response).getException();
System.out.println(exception.getReason());
} else {
System.out.println(((StytchResult.Success) response).getValue());
}
```
## Next steps
* Handle a session
# Via headless SDK
Source: https://stytch.com/docs/multi-tenant-auth/build-auth/custom/via-sdk
Examples and considerations when using the headless SDKs to build your own custom auth.
API & SDK reference
Headless code examples
***
## Libraries
While the SDKs include Stytch UI components, you can use the frontend SDKs headlessly to build your own custom auth flow and UI.
***
## Example code
See the [headless example apps](https://github.com/stytchauth/stytch-all-examples/tree/main/frontend) for more complete codes examples, including examples for other frameworks.
### Starting the authentication flow
```jsx theme={null}
// Start the authentication flow
import { useStytchB2BClient, useStytchMember, useStytchMemberSession } from '@stytch/nextjs/b2b';
const Login = (organization) => {
const stytchB2BClient = useStytchB2BClient();
const sendEmailMagicLink = async () => {
await stytchB2BClient.magicLinks.email.loginOrSignup({
email_address: 'sandbox@stytch.com',
organization_id: organization.organization_id
});
};
return ;
//...
```
### Completing the authentication flow
```jsx theme={null}
// Complete the authentication flow
const Authenticate = () => {
const stytchB2BClient = useStytchB2BClient();
const router = useRouter();
useEffect(() => {
const token = router?.query?.token?.toString();
stytchB2BClient.magicLinks.authenticate({
magic_links_token: token,
session_duration_minutes: 60,
});
}, [router, stytchB2BClient]);
//...
```
# Add login or signup
Source: https://stytch.com/docs/multi-tenant-auth/build-auth/discovery
Build a centralized login or signup page across orgs with Discovery login, where users authenticate first before selecting which organization to log into.
## Quickstart
Select a framework to start:
Next.js code example
Try out the login flow
Next.js SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
1. In your Next.js app, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/nextjs --save
```
2. Then add your Stytch `public_token` to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN=$PUBLIC_TOKEN
```
1. Create a custom `Stytch` wrapper component that initializes the Stytch client.
```jsx components/stytch-provider.tsx icon=file-code lines theme={null}
'use client';
import { createStytchB2BClient, StytchB2BProvider } from '@stytch/nextjs/b2b';
const stytchClient = createStytchB2BClient(
process.env.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN
);
export default function Stytch({ children }: { children: React.ReactNode }) {
return {children};
}
```
2. Wrap your application with `` to provide context to all child components:
```jsx app/layout.tsx icon=file-code lines {1,11} theme={null}
import Stytch from './components/stytch-provider';
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
);
}
```
1. Create a `` component.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/b2b/frontend-sdks/nextjs/prebuilt-ui/login/configuration).
```jsx components/LoginOrSignupDiscoveryForm.tsx icon=file-code lines theme={null}
import { AuthFlowType, B2BProducts, StytchB2B } from '@stytch/nextjs/b2b';
const config = {
authFlowType: AuthFlowType.Discovery,
products: [B2BProducts.emailMagicLinks, B2BProducts.oauth],
emailMagicLinksOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
},
oauthOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
providers: ['google'],
},
sessionOptions: {
sessionDurationMinutes: 60,
},
};
export const LoginOrSignupDiscoveryForm = () => {
return ;
};
```
Point `discoveryRedirectURL` to the URL in your app where users are redirected back to after clicking on their email magic link or after completing OAuth:
* **Using a single route:** Set the redirect back to the `/login` route if you plan on having one shared experience for both the start (login or signup) and end (MFA, organization discovery) of the authentication flow.
* **Using separate routes:** Set the redirect to a new route (e.g. `/authenticate`) if you want a different user experience between the start and the end of the authentication flow. For example, a marketing-oriented login page that redirects to a more focused authentication experience for MFA and organization selection.
For security, add redirect URLs in the [Dashboard](https://stytch.com/dashboard/redirect-urls) before being used.
1. Add `` to your `/login` page.
```jsx app/login/page.tsx icon=file-code theme={null}
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useStytchMemberSession } from '@stytch/nextjs/b2b';
import { LoginOrSignupDiscoveryForm } from "src/components/LoginOrSignupDiscoveryForm";
export default function Login() {
const { session, isInitialized } = useStytchMemberSession();
const router = useRouter();
// Route users to your app once they have fully authenticated (either immediately or upon completing the remainder of the auth flow, like MFA)
useEffect(() => {
if (session && isInitialized) {
router.replace("/home");
}
}, [session, isInitialized, router]);
if (!isInitialized || session) {
return
Loading...
;
}
return ;
}
```
2. If you're using separate routes, add `` to your `/authenticate` redirect route as well, e.g. `app/authenticate/page.tsx` or `pages/authenticate.tsx`.
After a user selects an organization, the `` component automatically exchanges their `intermediate_session_token` for a fully minted `session_token` and `session_jwt`, both of which are stored in browser cookies.
At this point, you can retrieve [Session data](/docs/api-reference/b2b/api/sessions/session-object) and [Member data](/docs/api-reference/b2b/api/members/member-object) via the SDK. For example, [`useStytchMemberSession()`](/docs/api-reference/b2b/frontend-sdks/nextjs/hooks/use-stytch-member-session) lets you easily check for an active session.
Next.js code example
Try out the login flow
Next.js SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
1. In your Next.js app, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/nextjs --save
```
2. Then add your Stytch `public_token` to your project's `.env` file:
```dotEnv .env icon=file-code theme={null}
NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN=$PUBLIC_TOKEN
```
1. Initialize the Stytch client through `createStytchB2BClient()`.
2. Pass the Stytch client to the `` component at the root of your application, making it accessible to all child components.
```jsx pages/_app.tsx {2,3,6,10,12} icon=file-code lines theme={null}
// For Page Router setups
import { createStytchB2BClient, StytchB2BProvider } from '@stytch/nextjs/b2b';
const stytch = createStytchB2BClient(process.env.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN!);
export default function MyApp({ Component, pageProps }: AppProps) {
return (
);
}
```
1. Create a `` component.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/b2b/frontend-sdks/nextjs/prebuilt-ui/login/configuration).
```jsx components/LoginOrSignupDiscoveryForm.tsx icon=file-code lines theme={null}
import { StytchB2B } from '@stytch/nextjs/b2b';
import { AuthFlowType, B2BProducts } from '@stytch/vanilla-js/b2b';
const config = {
authFlowType: AuthFlowType.Discovery,
products: [B2BProducts.emailMagicLinks, B2BProducts.oauth],
emailMagicLinksOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
},
oauthOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
providers: ['google'],
},
sessionOptions: {
sessionDurationMinutes: 60,
},
};
export const LoginOrSignupDiscoveryForm = () => {
return ;
};
```
Point `discoveryRedirectURL` to the URL in your app where users are redirected back to after clicking on their email magic link or after completing OAuth:
* **Using a single route:** Set the redirect back to the `/login` route if you plan on having one shared experience for both the start (login or signup) and end (MFA, organization discovery) of the authentication flow.
* **Using separate routes:** Set the redirect to a new route (e.g. `/authenticate`) if you want a different user experience between the start and the end of the authentication flow. For example, a marketing-oriented login page that redirects to a more focused authentication experience for MFA and organization selection.
For security, add redirect URLs in the [Dashboard](https://stytch.com/dashboard/redirect-urls) before being used.
1. Add `` to your `/login` page.
```jsx pages/login.tsx icon=file-code lines theme={null}
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useStytchMemberSession } from '@stytch/nextjs/b2b';
import { LoginOrSignupDiscoveryForm } from "src/components/LoginOrSignupDiscoveryForm";
export default function Login() {
const { session, isInitialized } = useStytchMemberSession();
const router = useRouter();
// Routes users to your app once they have fully authenticated (either immediately or upon completing the remainder of the auth flow, like MFA)
useEffect(() => {
if (session && isInitialized) {
router.replace("/home");
}
}, [session, isInitialized, router]);
if (!isInitialized || session) {
return
Loading...
;
}
return ;
}
```
2. If you're using separate routes, add `` to your `/authenticate` redirect route as well, e.g. `app/authenticate/page.tsx` or `pages/authenticate.tsx`.
After a user selects an organization, the `` component automatically exchanges their `intermediate_session_token` for a fully minted `session_token` and `session_jwt`, both of which are stored in browser cookies.
At this point, you can retrieve [Session data](/docs/api-reference/b2b/api/sessions/session-object) and [Member data](/docs/api-reference/b2b/api/members/member-object) via the SDK. For example, [`useStytchMemberSession()`](/docs/api-reference/b2b/frontend-sdks/nextjs/hooks/use-stytch-member-session) lets you easily check for an active session.
React code example
Try out the login flow
React SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
In your React app, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/react --save
```
1. Initialize the Stytch client through `createStytchB2BClient()`.
2. Pass the Stytch client to the `` component at the root of your application, making it accessible to all child components.
```jsx lines theme={null}
import { createStytchB2BClient, StytchB2BProvider } from '@stytch/react/b2b';
const stytch = createStytchB2BClient($YOUR_PUBLIC_KEY);
// Add the component at the root of your application
export function App() {
return (
{/* Your app code */}
);
}
```
1. Create a `` component.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/login/configuration).
```jsx lines theme={null}
import { AuthFlowType, B2BProducts, StytchB2B } from '@stytch/react/b2b';
const config = {
authFlowType: AuthFlowType.Discovery,
products: [B2BProducts.emailMagicLinks, B2BProducts.oauth],
emailMagicLinksOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
},
oauthOptions: {
discoveryRedirectURL: 'https://localhost:3000/authenticate',
providers: ['google'],
},
sessionOptions: { sessionDurationMinutes: 60 },
};
const LoginOrSignup = () => {
return ;
};
```
Point `discoveryRedirectURL` to the URL in your app where users are redirected back to after clicking on their email magic link or after completing OAuth:
* **Using a single route:** Set the redirect back to the `/login` route if you plan on having one shared experience for both the start (login or signup) and end (MFA, organization discovery) of the authentication flow.
* **Using separate routes:** Set the redirect to a new route (e.g. `/authenticate`) if you want a different user experience between the start and the end of the authentication flow. For example, a marketing-oriented login page that redirects to a more focused authentication experience for MFA and organization selection.
For security, add redirect URLs in the [Dashboard](https://stytch.com/dashboard/redirect-urls) before being used.
1. Add `` to your `/login` page:
```jsx Login.tsx icon=file-code lines theme={null}
import { useStytchMemberSession } from "@stytch/react/b2b";
import { Navigate } from "react-router";
import { LoginOrSignup } from "./LoginOrSignup";
export const Login = () => {
const { session } = useStytchMemberSession();
// Route users to your app once they have fully authenticated (either immediately or upon completing the remainder of the auth flow, like MFA)
if (session) {
return ;
}
return ;
};
```
2. If you're using separate routes, add `` to your `/authenticate` redirect route as well, e.g. `Authenticate.tsx`.
After a user selects an organization, the `` component automatically exchanges their `intermediate_session_token` for a fully minted `session_token` and `session_jwt`, both of which are stored in browser cookies.
At this point, you can retrieve [Session data](/docs/api-reference/b2b/api/sessions/session-object) and [Member data](/docs/api-reference/b2b/api/members/member-object) via the SDK. For example, [`useStytchMemberSession()`](/docs/api-reference/b2b/frontend-sdks/react/hooks/use-stytch-member-session) lets you easily check for an active session.
Vanilla JS code example
Try out the login flow
Vanilla JS SDK reference
* Have a `public_token` from your Stytch project & environment.
* Enable and configure [Frontend SDKs](https://stytch.com/dashboard/sdk-configuration) in your Dashboard.
* Add your application's domain to your project's [Authorized Domains](https://stytch.com/dashboard/sdk-configuration).
In your project, run the command to install the SDK:
```bash npm icon=terminal theme={null}
npm install @stytch/vanilla-js --save
```
1. Initialize the Stytch client through `createStytchB2BClient()`.
2. Export the initialized client as `stytch` so it can be used across your app.
```js js/stytch-client.js icon=file-code lines theme={null}
import { createStytchB2BClient } from "@stytch/vanilla-js/b2b";
export const stytch = createStytchB2BClient($YOUR_PUBLIC_KEY);
```
1. Create a `login.js` file to initialize and mount `stytch`.
2. Set authentication methods and relevant options in the [config object](/docs/api-reference/b2b/frontend-sdks/vanilla-js/prebuilt-ui/login/configuration).
```js js/login.js icon=file-code lines theme={null}
import { B2BProducts, StytchB2B } from '@stytch/vanilla-js/b2b'
import { stytch } from "./stytch-client.js";
// Stytch SDK method to get the current session synchronously
const session = stytch.session.getSync();
if (session) {
// User already has a session, redirect to your app
window.location.href = "/home";
return;
}
// Register the stytch-ui custom element
customElements.define('stytch-ui', StytchB2B);
// Pass in the Stytch client and configuration
const login = document.getElementById('stytch-ui');
login.render({
client: stytch,
config: {
authFlowType: "Discovery",
products: [B2BProducts.emailMagicLinks, B2BProducts.oauth],
sessionOptions: { sessionDurationMinutes: 60 },
emailMagicLinks: {
discoveryRedirectURL: "https://localhost:3000/authenticate",
},
oauthOptions: {
discoveryRedirectURL: "https://localhost:3000/authenticate",
providers: [{ type: "google" }],
},
},
});
```
Point `discoveryRedirectURL` to the URL in your app where users are redirected back to after clicking on their email magic link or after completing OAuth:
* **Using a single route:** Set the redirect back to the `/login` route if you plan on having one shared experience for both the start (login or signup) and end (MFA, organization discovery) of the authentication flow.
* **Using separate routes:** Set the redirect to a new route (e.g. `/authenticate`) if you want a different user experience between the start and the end of the authentication flow. For example, a marketing-oriented login page that redirects to a more focused authentication experience for MFA and organization selection.
For security, add redirect URLs in the [Dashboard](https://stytch.com/dashboard/redirect-urls) before being used.
1. Add `` on your `/login` page:
```html login.html {5,6} icon=file-code lines theme={null}
...
```
2. If you're using separate routes, add `` on your `/authenticate` redirect route as well, e.g. `authenticate.html`.
After a user selects an organization, the Stytch UI component automatically exchanges their `intermediate_session_token` for a fully minted `session_token` and `session_jwt`, both of which are stored in browser cookies.
At this point, you can retrieve [Session data](/docs/api-reference/b2b/api/sessions/session-object) and [Member data](/docs/api-reference/b2b/api/members/member-object) via the SDK. For example, [`session.getSync()`](/docs/api-reference/b2b/frontend-sdks/vanilla-js/methods/sessions/get-session-sync) lets you retrieve the current session and can be used to determine if a user is already logged in.
## Next steps
* Authentication methods
* Session management
# Add an org-specific login
Source: https://stytch.com/docs/multi-tenant-auth/build-auth/org-specific
Build a specialized login page for a single tenant.
If you're just getting started, follow our [Quickstart](./discovery) on implementing a general login or signup.
## Overview
This guide shows you how to configure the `` component auth flow to be scoped to a specific organization. This typically means you have or plan to have org-specific routes in your app (e.g. `acme.yourapp.com` or `yourapp.com/team/acme`).
***
## Prerequisites
* [JavaScript](https://www.npmjs.com/package/@stytch/vanilla-js), [Next.js](https://www.npmjs.com/package/@stytch/nextjs), or [React](https://www.npmjs.com/package/@stytch/react) B2B SDK installed
* `public_token` from your Stytch project & environment added to your app
* Enable the Frontend SDK configuration and any relevant authentication products in the [Dashboard](https://stytch.com/dashboard/sdk-configuration).
In most cases, the Stytch UI component would be hosted at an org-specific URL (e.g. `https://yourapp.com/acme/login`).
* [See the SDK reference](#)
Having an organization-specific URL makes it more difficult to be abused for [account enumeration](#account-enumeration-risk).
If you're using a URL with the `organization_slug` (e.g. `acme` in the example above):
* Add it as an [Authorized Domain](https://stytch.com/dashboard/sdk-configuration) using an *Organization URL template.*
* Or set the `organizationSlug` param in the component `config` directly.
Configure under the component `config`:
```jsx theme={null}
authFlowType: 'Organization'
```
Specify auth methods under the `config`, for example:
```jsx theme={null}
products: [B2BProducts.emailMagicLinks, B2BProducts.sso],
emailMagicLinksOptions: {
loginRedirectURL: "https://example.com/login",
signupRedirectURL: "https://example.com/signup"
},
ssoOptions: {
loginRedirectURL: "https://example.com/login",
signupRedirectURL: "https://example.com/signup"
},
```
Redirect URLs determine where users are routed to complete their auth flow per method `config`. For example:
* `signupRedirectURL`: Where **new** users are routed after clicking an email magic link.
* `loginRedirectURL`: Where **existing** users are routed after clicking an email magic link.
These can be set to be the same or different URL depending if you want to redirect users differently.
Add the `` component to your redirect URL routes to handle the remainder of the authentication flow.
Upon a successful authentication, a `session_token` and `session_jwt` for the organization is stored in browser cookies or mobile storage.
You can retrieve both Session and Member data via the SDK. For example, `useStytchMemberSession()` could be used to route authenticated users to your core app experience.
***
## Account enumeration risk
An account enumeration attack is when a bad actor identifies valid users, emails, etc. through an app's auth flow to gain information about a system that can be used in further attacks.
Org-specific logins surface the organization's name and login methods. Passwords and Magic Links can further expose whether or not a user has an existing account if error messages aren't opaque enough. **Preventative measures you can take:**
* Use an organization-specific URL
* Enable opaque errors in the [Dashboard](https://stytch.com/dashboard/sdk-configuration)
* Or use the [Discovery login](/docs/multi-tenant-auth/build-auth/discovery)
***
## Next steps
* Authentication methods
* Session management
# Stytch UI
Source: https://stytch.com/docs/multi-tenant-auth/build-auth/overview
How to build a log in and sign up flow for your app using the Stytch UI components included in the frontend SDK.
## Select a login flow
Our frontend SDKs includes the `` component that makes it easy for you to build two types of authentication flows into your app:
Cross-org login page where users authenticate first before selecting an org to log in to. **Recommended for most use cases.**
Specialized login page for a specific organization, often a subdomain containing the organization slug.
Stytch SDKs and UI are hosted by your application.
# Multi-tenant data model
Source: https://stytch.com/docs/multi-tenant-auth/data-model
Multi-tenant auth is built around two core entities: Organizations and Members.
## Key objects
A tenant in your app, typically used for your top-level customers.
An individual end user's account within an Organization.
## Overview
In multi-tenant applications, a business account owner (organization) is in control of who (members) can access their instance, how they authenticate, and what they can do within that instance. The organization is ultimately in control of all of the end users who access their instance.
Members are identified by their email:
```mermaid theme={null}
---
config:
title: Relationship model between Orgs & Members
layout: elk
elk:
mergeEdges: true
---
erDiagram
p["Multi-tenant project"]
o1[Organization] {
string id "org-001..."
string slug "acme"
}
o2[Organization] {
string id "org-002..."
string slug "stytch"
}
m1[Member] {
string id "member-583..."
string email "ada@stytch.com"
}
m2[Member] {
string id "member-9a1..."
string email "ada@stytch.com"
}
m3[Member] {
string id "member-30w..."
string email "baker@stytch.com"
}
p ||--|| o1 : ""
p ||--|| o2 : ""
o1 ||--|| m1 : ""
o2 ||--|| m2 : ""
o2 ||--|| m3 : ""
```
### Unique identifiers
Organizations are uniquely identified by:
* `organization_id`
* `organization_slug`
Members are uniquely identified by:
* `member_id` is unique across all Organizations
* `email_address` is unique within the Organization, but can link a member across Organizations
### Organization-specific configurations
Each Organization has its own set of configurations, allowing your customers control over their own auth requirements. Organization-specific settings include, but are not limited to: approved auth methods, JIT provisioning, SSO connections, MFA policies, custom metadata, and more.
***
## Hybrid model
If you have a mix of business customers and individual end users, **we recommend using multi-tenant auth and creating an Organization behind the scene for each individual user "tenant"** in order to allow these single-player accounts to seamlessly upgrade and add potential collaborators in the future.
***
## Don't need multi-tenancy?
Use [consumer auth](/docs/consumer-auth/data-model) if you don't require Organizations or multi-tenancy.
# Admin Portal
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/admin-portal
Use Admin Portal to build Org Management UI in your application
## Admin Portal
The simplest way to give your customers a UI to manage their organization and member settings is using Stytch's pre-built [Admin Portal](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/admin-portal).
Admin Portal provides:
* [`AdminPortalMemberManagement`](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/admin-portal/member-management) - allows Members of the Organization to invite new Members and manage existing Members
* [`AdminPortalOrgSettings`](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/admin-portal/organization-settings) - allows Members of the Organization to manage their own Organization settings, including authentication methods and JIT provisioning settings.
* [`AdminPortalSSO`](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/admin-portal/sso) - allows Members of the Organization to manage their own SSO connections
* [`AdminPortalSCIM`](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/admin-portal/scim) - allows Members of the Organization to manage their own SCIM connections
## Custom organization management UI
If you need more control over your org management ui, you can build custom flows for managing members and organization settings using the Stytch API.
Use [`Search Members`](/docs/api-reference/b2b/frontend-sdks/react/methods/members/search-members) to display all Members in the Organization.
Use [`Update Member`](/docs/api-reference/b2b/frontend-sdks/react/methods/members/update-member) to update a Member's Roles.
Use [`Invite Member`](/docs/api-reference/b2b/frontend-sdks/react/methods/email-magic-links/invite) to invite a new Member to the Organization.
Use [`Search Members`](/docs/api-reference/b2b/frontend-sdks/react/methods/members/search-members) to display all Members in the Organization.
```jsx theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
const stytch = useStytchB2BClient();
export const MemberTable = () => {
const [members, setMembers] = useState([]);
const stytch = useStytchB2BClient();
useEffect(() => {
const fetchMembers = async () => {
const response = await stytch.members.search();
setMembers(response.members);
};
fetchMembers();
setMembers(response.members);
}, [stytch]);
return (
);
};
```
Use [`Get Organization`](/docs/api-reference/b2b/frontend-sdks/react/methods/organizations/get-organization) to get the Organization's settings.
Use [`Update Organization`](/docs/api-reference/b2b/frontend-sdks/react/methods/organizations/update-organization) to update the Organization's settings.
Use the [`Get Organization`](/docs/api-reference/b2b/frontend-sdks/react/methods/organizations/get-organization) endpoint to display the Organization's settings.
```jsx theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const OrganizationInfo = () => {
const { organization } = useStytchOrganization();
return (
);
};
```
Use [`Update Organization`](/docs/api-reference/b2b/frontend-sdks/react/methods/organizations/update-organization) to update the Organization. In this example, we'll allow updating the allowed authentication methods.
```jsx theme={null}
import { useStytchB2BClient } from '@stytch/react/b2b';
export const UpdateOrganization = () => {
const { organization } = useStytchOrganization();
const [authMethods, setAuthMethods] = useState<'ALL_ALLOWED' | 'RESTRICTED'>('ALL_ALLOWED');
const [allowedAuthMethods, setAllowedAuthMethods] = useState([]);
const handleUpdate = async () => {
await stytch.organization.update({ auth_methods: authMethods, allowed_auth_methods: allowedAuthMethods });
};
return (
);
```
# Configure auth methods
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/org-management/configure-auth-methods
Multi-tenant auth allows granular configuration of which authentication methods can be used by Members.
You can update these settings on the [Stytch Dashboard](https://stytch.com/dashboard/organizations), or by using the [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization) endpoint.
```bash cURL icon="https://d3gk2c5xim1je2.cloudfront.net/devicon/bash.svg" theme={null}
curl --request PUT \
--url https://api.stytch.com/v1/b2b/organizations/{organization_id} \
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data '{
"auth_methods": "RESTRICTED",
"allowed_auth_methods": ["google_oauth"],
"mfa_policy": "REQUIRED_FOR_ALL",
}'
```
```python Python icon="python" theme={null}
client.organizations.update(
organization_id="organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
auth_methods="RESTRICTED",
allowed_auth_methods=["google_oauth"],
mfa_policy="REQUIRED_FOR_ALL",
)
```
```javascript Javascript icon="js" theme={null}
client.organizations.update({
organization_id: "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
auth_methods: "RESTRICTED",
allowed_auth_methods: ["google_oauth"],
mfa_policy: "REQUIRED_FOR_ALL",
})
```
## Setting allowed authentication methods
Allowed authentication methods are controlled by the [`auth_methods`](/docs/api-reference/b2b/api/organizations/update-organization#body-auth-methods) setting. The accepted values are:
* `ALL_ALLOWED` – the default setting which allows all authentication methods allowed by your application to be used
* `RESTRICTED` – only methods that comply with [`allowed_auth_methods`](/docs/api-reference/b2b/api/organizations/update-organization#body-allowed-auth-methods) can be used for authentication.
If `auth_methods` is `RESTRICTED`, `allowed_auth_methods` must be set to an array of allowed authentication methods. The valid values are:
* `sso`
* `magic_link`
* `email_otp`
* `password`
* `google_oauth`
* `microsoft_oauth`
* `slack_oauth`
* `github_oauth`
* `hubspot_oauth`
## Setting allowed MFA methods
Allowed MFA methods are controlled by the [`mfa_methods`](/docs/api-reference/b2b/api/organizations/update-organization#body-mfa-methods) setting. The accepted values are:
* `ALL_ALLOWED` – the default setting which allows all MFA methods allowed by your application to be used
* `RESTRICTED` – only methods that comply with [`allowed_mfa_methods`](/docs/api-reference/b2b/api/organizations/update-organization#body-allowed-mfa-methods) can be used for MFA.
If `mfa_methods` is `RESTRICTED`, `allowed_mfa_methods` must be set to an array of allowed MFA methods. The valid values are:
* `sms_otp`
* `totp`
## Requiring MFA
Allowed MFA methods are controlled by the [`mfa_policy`](/docs/api-reference/b2b/api/organizations/update-organization#body-mfa-policy) setting. The accepted values are:
* `REQUIRED_FOR_ALL` – all Members of the Organization will be required to complete an MFA flow in order to log in.
* `OPTIONAL` – MFA is optional, but individual Members can still enroll in MFA, which will require them to complete an MFA flow to log in going forward.
## Breakglass Members
Breakglass Members serve as protection against invalid provisioning settings. A breakglass Member has permissions to authenticate into an Organization by bypassing the Organization's settings. Typically, this is used for emergency purposes to gain access outside of normal authentication procedures.
# Invite members
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/org-management/invite-members
Invite Members to an Organization via email invitation.
Organizations can allow new Members to join via **explicit email invitation** or [just-in-time (JIT) provisioning](/docs/multi-tenant-auth/enterprise-ready/org-management/jit-provision-members).
The right solution depends on the Organization's use case:
* To reduce friction to joining an Organization, JIT provisioning is a simple way to ensure that new Members are added to their company's Organization as soon as they authenticate.
* If admins of your product need more control over who gets access to their Organization, they may want to restrict to email invites, or JIT provisioning by a specific OAuth tenant.
Often, the right solution will vary by Organization. To learn about how to enable Organizations to manage their own provisioning settings, see the [Admin Portal](/docs/multi-tenant-auth/enterprise-ready/admin-portal) guide.
## Inviting Members via email
Enable **inviting Members via email** to allow Organization Members to invite new Members to the Organization via email invitation.
Using the [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization) endpoint:
* Set [`email_invites`](/docs/api-reference/b2b/api/organizations/update-organization#body-email-invites) to either:
* `RESTRICTED` to allow specific email domains to be invited to the Organization
* `ALL_ALLOWED` to allow any email domain to be invited to the Organization
* If `RESTRICTED`, specify which email domains should be allowed with [`email_allowed_domains`](/docs/api-reference/b2b/api/organizations/update-organization#body-email-allowed-domains).
```bash cURL icon="https://d3gk2c5xim1je2.cloudfront.net/devicon/bash.svg" theme={null}
curl --request PUT \
--url https://api.stytch.com/v1/b2b/organizations/{organization_id} \
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data '{
"email_invites": "RESTRICTED",
"email_allowed_domains": ["companyname.com"],
}'
```
```python Python icon="python" theme={null}
client.organizations.update(
organization_id="organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
email_invites="RESTRICTED",
email_allowed_domains=["companyname.com"],
)
```
```javascript Javascript icon="js" theme={null}
client.organizations.update({
organization_id: "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
email_invites: "RESTRICTED",
email_allowed_domains: ["companyname.com"],
})
```
Stytch disallows setting certain common email domains on the `email_allowed_domains` array:
* `gmail`
* `aol`
* `yahoo`
* `icloud`
* `hotmail`
* `msn`
* `comcast`
* `live`
* `outlook`
* `att`
* `earthlink`
* `me`
* `mac`
* `sbcglobal`
* `verizon`
* `ig`
* `mail`
* `hey`
* `laposte`
* `wanadoo`
* `googlemail`
* `orange`
* `rediffmail`
* `uol`
* `bol`
* `free`
* `gmx`
* `yandex`
* `ymail`
* `libero`
# JIT provision members
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/org-management/jit-provision-members
Add Members to an Organization via just-in-time (JIT) provisioning.
Organizations can allow JIT provisioning of new Members based on specific **email domains**, **SSO Connections**, or **OAuth tenants** (Github Organizations, Slack Workspaces or Hubspot Teams). When JIT provisioning is enabled, an Organization doesn't need to explicitly invite or add Members to the Organization; they'll be added as soon as they authenticate.
## By email domain
Enable **JIT provisioning by email domain** to allow any user with a certain email domain (for example, `@companyname.com`) to log into a given Organization. Using the [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization) endpoint, set:
* [`email_jit_provisioning`](/docs/api-reference/b2b/api/organizations/update-organization#body-email-jit-provisioning) to `RESTRICTED`
* [`email_allowed_domains`](/docs/api-reference/b2b/api/organizations/update-organization#body-email-allowed-domains) to an array of allowed email domains
During the Discovery flow, in order for a user with a given email domain to access an Organization via JIT Provisioning, **there must already be at least one Member in the Organization with a verified email address with the same email domain**. This restriction is in place to avoid phishing attacks.
```bash cURL icon="https://d3gk2c5xim1je2.cloudfront.net/devicon/bash.svg" theme={null}
curl --request PUT \
--url https://api.stytch.com/v1/b2b/organizations/{organization_id} \
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data '{
"email_jit_provisioning": "RESTRICTED",
"email_allowed_domains": ["companyname.com"],
}'
```
```python Python icon="python" theme={null}
client.organizations.update(
organization_id="organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
auth_methods="RESTRICTED",
allowed_auth_methods=["google_oauth"],
)
```
```javascript Javascript icon="js" theme={null}
client.organizations.update({
organization_id: "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
auth_methods: "RESTRICTED",
allowed_auth_methods: ["google_oauth"],
})
```
Stytch disallows setting certain common email domains on the `email_allowed_domains` array:
* `gmail`
* `aol`
* `yahoo`
* `icloud`
* `hotmail`
* `msn`
* `comcast`
* `live`
* `outlook`
* `att`
* `earthlink`
* `me`
* `mac`
* `sbcglobal`
* `verizon`
* `ig`
* `mail`
* `hey`
* `laposte`
* `wanadoo`
* `googlemail`
* `orange`
* `rediffmail`
* `uol`
* `bol`
* `free`
* `gmx`
* `yandex`
* `ymail`
* `libero`
### OAuth step-up flow
If a user authenticates via OAuth, they will only be able to access an Organization via JIT Provisioning by email domain if the OAuth provider indicates active ownership of the email address. Otherwise, the user will be prompted to complete a step-up form of primary authentication before they can access the Organization. [See here](/docs/multi-tenant-auth/authentication/oauth/overview#understanding-step-up-requirements-in-oauth-discovery) for additional information.
## By SSO Connection
Enable **JIT Provisioning by SSO Connection** to allow users to log into an Organization via their company's Identity Provider, assuming an [SSO Connection](/docs/multi-tenant-auth/authentication/sso/overview) has been set up for that Organization and Identity Provider.
For example, assume one of your customers sets up a Microsoft Entra SSO Connection. If JIT Provisioning is enabled for that SSO Connection, your customer's team members will be able to log into the customer's Stytch Organization without being explicitly added or invited, as long as the team members are assigned to your application within Microsoft Entra.
To enable JIT Provisioning by SSO Connection:
* Set [`sso_jit_provisioning`](/docs/api-reference/b2b/api/organizations/update-organization#body-sso-jit-provisioning) to either:
* `ALL_ALLOWED` if you'd like to enable JIT Provisioning for all of the Organization's active SSO Connections
* `RESTRICTED` if you'd like to specify which of the Organization's SSO Connections should be enabled for JIT Provisioning
* If `RESTRICTED`, specify which SSO Connections should be enabled for JIT Provisioning with [`sso_jit_provisioning_allowed_connections`](/docs/api-reference/b2b/api/organizations/update-organization#body-sso-jit-provisioning-allowed-connections).
```bash cURL icon="https://d3gk2c5xim1je2.cloudfront.net/devicon/bash.svg" theme={null}
curl --request PUT \
--url https://api.stytch.com/v1/b2b/organizations/{organization_id} \
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data '{
"sso_jit_provisioning": "RESTRICTED",
"sso_jit_provisioning_allowed_connections": ["sso-connection-123"],
}'
```
```python Python icon="python" theme={null}
client.organizations.update(
organization_id="organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
sso_jit_provisioning="RESTRICTED",
sso_jit_provisioning_allowed_connections=["sso-connection-123"],
)
```
```javascript Javascript icon="js" theme={null}
client.organizations.update({
organization_id: "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
sso_jit_provisioning: "RESTRICTED",
sso_jit_provisioning_allowed_connections: ["sso-connection-123"],
})
```
## By OAuth tenant
Enable **JIT Provisioning by OAuth tenant** to allow users to log into an Organization based on their OAuth tenant membership (for example, if they belong to a specific Github Organization, Slack Workspace, or Hubspot Team).
If you're using Google or Microsoft OAuth, in most cases you can use [JIT Provisioning by email domain](#by-email-domain) instead.
Using the [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization) endpoint:
* Set [`oauth_tenant_jit_provisioning`](/docs/api-reference/b2b/api/organizations/update-organization#body-oauth-tenant-jit-provisioning) to `RESTRICTED`.
* Add the allowed OAuth tenants to [`allowed_oauth_tenants`](/docs/api-reference/b2b/api/organizations/update-organization#body-allowed-oauth-tenants) object, where the available keys are `slack`, `hubspot`, and `github`, and the values are arrays of tenant identifiers (for example, Slack Team IDs).
```bash cURL icon="https://d3gk2c5xim1je2.cloudfront.net/devicon/bash.svg" theme={null}
curl --request PUT \
--url https://api.stytch.com/v1/b2b/organizations/{organization_id} \
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data '{
"oauth_tenant_jit_provisioning": "RESTRICTED",
"allowed_oauth_tenants": {
"hubspot": ["HUBSPOT-ABC", "HUBSPOT-DEF"],
"slack": ["SLACK-123", "SLACK-456"],
"github": [12345, 67890]
},
}'
```
```python Python icon="python" theme={null}
client.organizations.update(
organization_id="organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
sso_jit_provisioning="RESTRICTED",
allowed_oauth_tenants={
"hubspot": ["HUBSPOT-ABC", "HUBSPOT-DEF"],
"slack": ["SLACK-123", "SLACK-456"],
"github": [12345, 67890],
},
)
```
```javascript Javascript icon="js" theme={null}
client.organizations.update({
organization_id: "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
oauth_tenant_jit_provisioning: "RESTRICTED",
allowed_oauth_tenants: {
hubspot: ["HUBSPOT-ABC", "HUBSPOT-DEF"],
slack: ["SLACK-123", "SLACK-456"],
github: [12345, 67890],
},
})
```
### Determining tenant IDs
Your **Slack tenant ID** (referred to as a Workspace ID by Slack) can be determined by [following this guide](https://slack.com/help/articles/221769328-Locate-your-Slack-URL-or-ID#free,-pro,-and-business+-plans-2).
Your **HubSpot tenant ID** (referred to as a Hub ID by HubSpot) can be determined by either:
* Logging into your HubSpot developer account and parsing it from the URL path. It will have the form `https://app.hubspot.com/developer/${TENANT_ID}`.
* When logged into your HubSpot developer account, clicking on the dropdown menu under your account name in the upper right-hand corner. It will be the sequence of numbers under your account name.
**GitHub tenant IDs** are taken from the internal ID of a GitHub organization. There are a number of ways to retrieve this value, depending on your permissions in relation to the GitHub organization and the organization's privacy settings:
* You can query [GitHub's organization endpoint](https://docs.github.com/en/rest/orgs/orgs?apiVersion=2022-11-28#get-an-organization). The tenant ID will be the id value in the JSON response.
* If you are a member of the GitHub organization in question, it may be easier to use the GitHub CLI to request a list of the organizations you are a member of with the [List organizations for the authenticated user endpoint](https://docs.github.com/en/rest/orgs/orgs?apiVersion=2022-11-28#list-organizations-for-the-authenticated-user).
# Org configuration rules
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/org-management/org-configuration-rules
To ensure that your Organization's authentication settings are valid, the following rules apply.
If no Organization authentication parameters are passed in, `email_invites` defaults to `ALL_ALLOWED` allowing the Organization to add Members. If some authentication parameters are passed but `email_invites` is omitted, it defaults to `NOT_ALLOWED`.
Additionally, the following rules apply:
### Allowed Auth Methods
* If `allowed_auth_methods` is empty
* then `auth_methods` cannot be set to RESTRICTED.
* If `allowed_auth_methods` is not empty
* and `auth_methods` is set to `RESTRICTED`, then all members looking to authenticate will be required to use one of these auth methods.
* If `allowed_auth_methods` is updated
* then the entire list will be overwritten by the new values.
* then all currently active Members will remain active even if their auth methods no longer comply with `allowed_auth_methods`. However, the member will not be able to authenticate with the disallowed methods.
* and `auth_methods` is set to `RESTRICTED`, then `auth_methods` cannot be set to an empty array.
### Allowed MFA Methods
* If `allowed_mfa_methods` is empty
* then `mfa_methods` cannot be set to RESTRICTED.
* If `allowed_mfa_methods` is not empty
* and `mfa_methods` is set to `RESTRICTED`, then all members looking to authenticate with MFA will be required to use one of these MFA methods.
* If `allowed_mfa_methods` is updated
* then the entire list will be overwritten by the new values.
* then all currently active Members will remain active even if their MFA methods no longer comply with `allowed_mfa_methods`. However, the member will not be able to MFA authenticate with the disallowed methods.
* and `mfa_methods` is set to RESTRICTED, then `mfa_methods` cannot be set to an empty array.
### Email Magic Links
* If `email_allowed_domains` is empty
* then `email_invites` cannot be set to RESTRICTED.
* then `email_jit_provisioning` cannot be set to RESTRICTED.
* If `email_allowed_domains` is not empty
* and `email_invites` is set to `RESTRICTED`, then all new Members must have an email address that complies with `email_allowed_domains` in order to be invited by email.
* and `email_invites` is set to `ALL_ALLOWED`, then any new Member can be invited to email.
* and `email_invites` is set to `NOT_ALLOWED`, then email invites are disabled.
* If `email_allowed_domains` is not empty
* and `email_jit_provisioning` is set to `RESTRICTED`, then all new Members must have an email address that complies with `email_allowed_domains` in order to be provisioned via Email Magic Links.
* and `email_jit_provisioning` is set to `ALL_ALLOWED`, then any new Member can be provisioned via Email Magic Links.
* and `email_jit_provisioning` is set to `NOT_ALLOWED`, then JIT provisioning via Email Magic Links is disabled.
* If `email_allowed_domains` is updated
* then the entire list will be overwritten by the new value.
* then all Members will remain active even if their email\_address no longer complies with `email_allowed_domains`.
* and either `email_invites` or `email_jit_provisioning` is set to `RESTRICTED`, then `email_allowed_domains` cannot be set to an empty array.
### Single Sign On
* If `sso_jit_provisioning_allowed_connections` is empty
* then `sso_jit_provisioning` cannot be set to `RESTRICTED`.
* If `sso_jit_provisioning_allowed_connections` is not empty
* and `sso_jit_provisioning` is set to `RESTRICTED`, then all new Members must have an SSO login that complies with `sso_jit_provisioning_allowed_connections` in order to be provisioned via SSO.
* and `sso_jit_provisioning` is set to `ALL_ALLOWED`, then all new Members will be automatically provisioned via any of the Organization's `sso_active_connections`.
* and `sso_jit_provisioning` is set to `NOT_ALLOWED`, then JIT provisioning via SSO is disabled.
* If `sso_jit_provisioning_allowed_connections` is updated
* then the entire list will be overwritten by the new value.
* then all currently active Members will remain active even if their `sso_registrations` no longer complies with `sso_jit_provisioning_allowed_connections`.
* and `sso_jit_provisioning` is set to `RESTRICTED`, then `sso_jit_provisioning_allowed_connections` cannot be set to an empty array.
# Member management overview
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/org-management/overview
Understanding Stytch Organizations and Members
In your Stytch project, each **Organization** represents a tenant in your application, typically 1:1 with one of your customers. An Organization contains one or more **Members**, which represent the users of your application.
A tenant in your app, typically 1:1 with one of your customers.
An individual end user within an Organization.
### Overview
```mermaid theme={null}
---
config:
title: Relationship model between Orgs & Members
layout: elk
elk:
mergeEdges: true
---
erDiagram
p["Your Stytch project"]
o1[Organization] {
string id "org-001..."
string slug "acme"
}
o2[Organization] {
string id "org-002..."
string slug "stytch"
}
m1[Member] {
string id "member-583..."
string email "ada@stytch.com"
}
m2[Member] {
string id "member-9a1..."
string email "ada@stytch.com"
}
m3[Member] {
string id "member-30w..."
string email "baker@stytch.com"
}
p ||--|| o1 : ""
p ||--|| o2 : ""
o1 ||--|| m1 : ""
o2 ||--|| m2 : ""
o2 ||--|| m3 : ""
```
## Organization management
Settings on the Organization object control how Members can authenticate (ie. forcing all Members to use OAuth), and how new Members can be provisioned (ie. allowing anyone with an `@example.com` email to join an Organization).
These settings are covered in more detail in the following guides:
Control which authentication methods can be used by Members of an Organization.
Invite new Members to an Organization via email invitation or JIT provisioning.
Add UI to your application to allow end-users to manage their own Organizations and Members.
# Assigning Roles to Members
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/rbac/assigning-roles-to-members
Assign roles to your Members to grant them access to resources in your application
When a Member is assigned a role, they are granted permission to perform actions on the resources that the role allows. Roles are additive, so Members are granted the cumulative permissions of all roles they are assigned.
Roles can be assigned to Members either **explicitly** by direct assignment or **implicitly** by matching attributes or conditions.
## Explicit assignment
**Explicit role assignment** is when you directly assign a role to (or revoke from) a specific Member. Once assigned, the Member has that Role unconditionally until it is explicitly revoked.
You can assign roles when making calls to the following API endpoints:
* [Create Member](/docs/api-reference/b2b/api/members/create-member)
* [Update Member](/docs/api-reference/b2b/api/members/update-member)
* [Send Email Invite](/docs/api-reference/b2b/api/email-magic-links/organization/send-invite-email)
* [Password Migrate](/docs/api-reference/b2b/api/passwords/migrate)
or manually in the [Stytch Dashboard](https://stytch.com/dashboard/members).
## Implicit assignment
**Implicit role assignment** is when a Member is automatically assigned a Role by meeting certain criteria or possessing certain attributes, designated by their Organization. For example, a Organization can be configured so that all Members with a `stytch.com` email domain are assigned the Role of `developer` upon account creation.
Implicit assignment can be thought of as a mechanism to define rules for automatically assigning Roles to Members.
Stytch offers three ways to assign roles implicitly:
* By email domain: everyone with the `@example.com` email domain gets the `developer` Role.
* By SSO Connection: everyone who authenticates via a specific SSO Connection gets the `employee` Role.
* By SSO Connection IdP Group: everyone who authenticates via a specific SSO Connection *and* is a part of the `engineering` IdP group gets the `developer` Role.
Stytch currently only supports SSO connection implicit role assignments for SAML connections, not OIDC connections. Please [contact us](https://stytch.com/contact) if you would like to use this functionality for OIDC connections.
Implicit roles assignments will be revoked when the condition for the assignment is no longer met. In the example above,if the Member's email domain is changed to `@not-example.com`, they will no longer be assigned the `developer` Role.
### Email domain role assignments
Create and update email domain implicit role assignment rules using the `rbac_email_implicit_role_assignments` argument when making calls to the following API endpoints:
* [Create Organization](/docs/api-reference/b2b/api/organizations/create-organization)
* [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization)
* [Create Organization via Discovery](/docs/api-reference/b2b/api/discovery/create-organization-via-discovery)
or manually in the [Stytch Dashboard](https://stytch.com/dashboard/members).
### SSO Connection role assignments
Read on to learn more about managing SSO Connection implicit role assignments with SAML:
# Create your RBAC Policy
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy
Use Role-Based Access Control (RBAC) to manage access to specific resources
Stytch's RBAC model streamlines the management and enforcement of permissions with a flexible interface that's designed for a multi-tenant auth system.
Use the RBAC API to check permissions on your backend.
Use the RBAC SDKs to gate UI based on permissions.
Manage your RBAC policy in the Stytch Dashboard.
## The RBAC Policy
An RBAC policy is a governing document that stores all access controls. It is the canonical source for all RBAC authorizations within a Stytch Project, made up of three core concepts:
* **Resources**: an abstract entity that your application interacts with
* Examples: `documents`, `images`, `users`
* **Actions**: the specific operations that can be performed on a resource
* Examples: `create`, `read`, `update`, `delete`
* **Roles**: a named collection of permissions that can be assigned to a member
* Examples: `admin`, `editor`, `viewer`
The relationship between these three concepts is the foundation for Stytch's RBAC permissioning model.
### Resources
A [Resource](/docs/api-reference/b2b/api/rbac/resource-object) represents an entity that your application interacts with. It is identified by `resource_id`, a unique, user-inputted string that allows you to freely name a Resource after almost anything.
Typically, Resources are modeled after data objects your users of your application create or manage. Such as `documents`, `images`, `invoices`, `products`, `employees`, or `credit-cards`.
### Actions
Resources are associated with a specific list of [Actions](/docs/api-reference/b2b/api/rbac/resource-object#param-actions) that can be performed on them.
Typically, these actions are standard CRUD operations like `create`, `read`, `update`, and `delete`. However, you can also define more application-specific actions like `share`, `invite`, `block`, and `export`.
### Permissions
A permission is a combination of a Resource and an Action. For example, the permission to `read` a `document` or to `create` an `employee`.
### Roles
A [Role](/docs/api-reference/b2b/api/rbac/role-object) is a named collection of permissions. It is identified by `role_id`, a unique, user-inputted string that allows you to name a Role after the persona it represents.
For example, you might create an `admin` role with the following permissions:
* `read: employees`
* `create: employees`
* `update: employees`
* `delete: employees`
(Note: you could also use a `*` wildcard to grant a Role permission to use all possible actions related to a Resource.)
And a `viewer` role with the following permissions:
* `create: employees`
* `read: employees`
## Putting it all together
Create as many **Resources**, **Actions**, and **Roles** as your application needs. The more granular you can make your permissions, the more control you have over who can do what in your application.
To get started:
1. Configure your RBAC policy in the [Stytch Dashboard](https://stytch.com/dashboard/rbac) or using the [Programmatic Workspace Actions API](/docs/api-reference/pwa/api/v3/rbac/get-rbac-policy).
2. Grant permissions by [assigning Roles to Members](/docs/multi-tenant-auth/enterprise-ready/rbac/assigning-roles-to-members)
3. Use your RBAC policy to [gate access in your application](/docs/multi-tenant-auth/enterprise-ready/rbac/enforcing-permissions).
## Default Roles and Resources
### Default Resources
Within your RBAC Policies, you'll find the following five default Resources, prefixed with `stytch.`, that represent core entities in a Stytch Project:
* `stytch.self`: access controls for the logged-in user's Member.
* `stytch.organization`: access controls for the Organization.
* `stytch.member`: access controls for all Members in the Organization.
* `stytch.sso`: access controls for SSO Connections.
* `stytch.scim`: access controls for SCIM Connections.
You can view the full list of Actions associated with each Stytch Resource in your [Stytch Dashboard](https://stytch.com/dashboard/rbac).
### Default Roles
To manage the default Resources, Stytch provides two default Roles for your convenience:
* **`stytch_member`: the base Role that's automatically assigned to all Members.** By default, it contains all `stytch.self` permissions, enabling permissions like updating your own name. It is automatically assigned to all Members, and cannot be removed.
* **`stytch_admin`: a Role that's automatically assigned to the Member that creates a new Organization.** By default, it includes all permissions for the `stytch.organization`, `stytch.member`, and `stytch.sso` resources.
These default roles cannot be deleted, but you can edit their permissions to suit the needs of your Project. For example, if you wanted to create your own `enterprise_admin` role as the *only* role that can manage SSO connections, you could remove the `stytch.sso` permissions from the `stytch_admin` Role.
# Enforcing Permissions
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/rbac/enforcing-permissions
Enforce RBAC permissions in your application's frontend and backend
To enforce RBAC permissions in your application, it is critical to both:
* Gate various UI and Member actions in your application's frontend
* Confirm authentication and authorization on your backend before honoring requests from the client
## Frontend authorization checks
Use Stytch's frontend SDKs to check user permissions client-side.
### Conditionally render UI based on permissions
It's good practice to adapt UI based on permissions, even if your backend will enforce permissions on the server-side.
```jsx React icon="react" theme={null}
import { useStytchIsAuthorized } from '@stytch/react/b2b';
const DocumentActions = () => {
// Use the useStytchIsAuthorized hook to check if the current Member is authorized to perform an action.
const { isAuthorized: canReadDocument } = useStytchIsAuthorized('documents', 'read');
const { isAuthorized: canWriteDocument } = useStytchIsAuthorized('documents', 'write');
return (
{ /* Disable a button if the Member is not authorized */ }
}
{ /* or just hide the button entirely */ }
{canWriteDocument && }
);
};
```
```jsx Next.js icon="triangle" theme={null}
import { useStytchIsAuthorized } from '@stytch/nextjs/b2b';
const DocumentActions = () => {
// Use the useStytchIsAuthorized hook to check if the current Member is authorized to perform an action.
const { isAuthorized: canReadDocument, isInitialized: isReadDocumentInitialized } = useStytchIsAuthorized('documents', 'read');
const { isAuthorized: canWriteDocument, isInitialized: isWriteDocumentInitialized } = useStytchIsAuthorized('documents', 'write');
if (!isReadDocumentInitialized || !isWriteDocumentInitialized) {
return
Loading...
;
}
return (
{ /* Disable a button if the Member is not authorized */ }
}
{ /* or just hide the button entirely */ }
{canWriteDocument && }
);
};
```
```jsx Vanilla JS icon="js" theme={null}
import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
const stytch = new StytchB2BClient(process.env.STYTCH_PUBLIC_TOKEN);
// Fetch specific permissions for the current Member.
const canReadDocument = await stytch.rbac.isAuthorized('documents', 'read');
const canWriteDocument = await stytch.rbac.isAuthorized('documents', 'write');
// Or just fetch ALL permissions for the current Member.
const allPermissions = await stytch.rbac.allPermissions();
```
### Check permissions before making API requests
You should also do pre-emptive client-side authorization checks to avoid any unnecessary API requests or ensure the user isn't about to enter a flow they cannot complete.
```jsx theme={null}
const canReadDocument = await stytch.rbac.isAuthorized('documents', 'read');
if (!canReadDocument) {
throw new Error('You do not have permission to read documents');
}
loadDocument();
```
### Built-in Stytch Resource authorization
Stytch's frontend SDKs offer built-in RBAC protections, allowing you to make requests directly from your public client without proxying through your backend.
When you call methods like `organization.update()` or `magicLinks.email.invite()` directly from the client:
1. The frontend SDK will automatically include the logged in member's session in the request to the Stytch API.
2. The Stytch API will only authorize the request if the session is valid and the Member has at least one Role that grants them permission to take that action.
To enable the SDK to make these requests, enable **Member actions & organization modifications** in the [Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration).
## Backend authorization checks
Regardless of if you are using Stytch's frontend SDKs, you must always perform server-side authorization checks before proceeding with a request by authorizing a valid Session Token or JWT.
If you are using **Session Tokens**, [session authentication](/docs/api-reference/b2b/api/sessions/authenticate-session) will trigger an outbound call to Stytch and authorization will be done on Stytch's servers.
```mermaid theme={null}
sequenceDiagram
participant Client
participant You Application
participant Stytch API
Client->>You Application: POST api/document with session_token
You Application->>Stytch API: sessions.authenticate() with authorization_check
Note over Stytch API: Verifies session_token is valid and does authorization check
Stytch API->>You Application: Returns authorized:true and a fresh session_token
Note over You Application: Save document edits
You Application->>Client: 200 response, set session_token in cookies
```
For example to check authorization in a python backend:
```python theme={null}
resp = stytch_client.sessions.authenticate(
session_token='mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q',
authorization_check={
'organization_id': 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
'resource_id': 'documents',
'action': 'edit',
}
)
if resp.status_code != 200: # The Member is not authenticated, redirect to login page
return 'not authenticated', 401
if not resp.authorized: # The Member is authenticated, but does not have permission to edit the document
return 'unauthorized', 403
# Member is authenticated and has permission to edit the document
save_document_edits()
```
If you are using **Session JWTs**, the Member's Roles are contained in the JWT.
When using [`authenticateJwt()`](/docs/api-reference/b2b/api/sessions/authenticate-jwt), authorization checks will be done locally during the lifetime of the JWT without incurring any additional latency of an outbound call. Stytch's backend SDKs will cache your Project's RBAC policy and refresh it every five minutes, ensuring that any RBAC policy changes are incorporated into local evaluations within 5 minutes.
If `authenticateJwt()` method detects the JWT is expired, it will make an outbound call to Stytch to refresh the JWT. This ensures that any changes to the Member's Role will take effect within five minutes and that the underlying session has not been revoked.
```mermaid theme={null}
sequenceDiagram
participant Client
participant You Application
participant Stytch Backend SDK
participant Stytch API
Client->>You Application: POST api/document with session_jwt
Stytch Backend SDK-->>Stytch API: Periodic refresh of RBAC policy
You Application->>Stytch Backend SDK: sessions.authenticateJwt() with authorization_check
Note over Stytch Backend SDK: sessions.authenticateJwtLocal()
opt If JWT is expired
Stytch Backend SDK->>Stytch API: sessions.authenticateJwt() with authorization_check
Note over Stytch API: Verifies session_jwt is valid and does authorization check
Stytch API->>Stytch Backend SDK: Returns fresh JWT and AuthZ verdict
end
Stytch Backend SDK->>You Application: returns authorized:true and session_jwt
Note over You Application: Save document edits
You Application->>Client: 200 response, set session_jwt in cookies
```
```python theme={null}
resp = stytch_client.sessions.authenticateJwt(
session_jwt="eyJ...",
authorization_check={
'organization_id': 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
'resource_id': 'documents',
'action': 'edit',
}
)
if resp.status_code != 200: # The Member is not authenticated, redirect to login page
return 'not authenticated', 401
if not resp.authorized: # The Member is authenticated, but does not have permission to edit the document
return 'unauthorized', 403
# Member is authenticated and has permission to edit the document
save_document_edits()
```
### Built-in Stytch Resource authorization
Any Stytch API endpoints and Backend SDK methods that act on a [default Stytch Resource](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy#default-roles-and-resources) (e.g. `stytch.self` or `stytch.organization`) support passing the Member's Session Token or Session JWT to have Stytch perform authentication *and* authorization prior to honoring the request.
This allows the authentication and authorization checks to occur as close as possible to the action being taken, streamlining your handling.
For example, if a Member makes a request to delete another Member from an Organization, you can pass their Session Token or JWT into the `organizations.members.delete()` call. Stytch will authenticate the session and perform an authorization check prior to honoring the call.
```python Python icon="python" highlight={4-8} theme={null}
resp = stytch_client.organizations.members.delete(
organization_id="organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
member_id="member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
method_options=DeleteRequestOptions( # [!code ++:5]
authorization=Authorization(
session_token="",
),
),
)
```
```bash cURL icon="https://d3gk2c5xim1je2.cloudfront.net/devicon/bash.svg" theme={null}
curl --request DELETE \
--url https://test.stytch.com/v1/b2b/organizations/organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931/members/member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f \
-u ':' \
-H 'Content-Type: application/json' \
-H "X-Stytch-Member-Session: " # [!code ++]
```
# SAML Role Assignments
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/rbac/saml
Assign roles to your Members via SAML connections
SAML can be used to [assign roles to your Members implicitly](/docs/multi-tenant-auth/enterprise-ready/rbac/assigning-roles-to-members#implicit-assignment), based on either the Member's SSO Connection or the Member's SSO Connection IdP Group.
You can create or update SSO connection or IdP group implicit role assignment by making calls to the following API endpoints:
* [Create SAML Connection](/docs/api-reference/b2b/api/sso/saml/create-saml-connection)
* [Update SAML Connection](/docs/api-reference/b2b/api/sso/saml/update-saml-connection)
* [Update SAML Connection by Metadata URL](/docs/api-reference/b2b/api/sso/saml/update-saml-connection-by-metadata-url)
or manually updating the SSO Connection for the Organization in the [Stytch Dashboard](https://stytch.com/dashboard/organizations).
## SSO Connection-based Role Assignments
Assign roles to your Members implicitly based on their **specific SSO Connection** by passing `role_id`s to [`connection_implicit_role_assignments`](/docs/api-reference/b2b/api/sso/saml/create-saml-connection#body-connection-implicit-role-assignments).
For example, if you want to assign the `admin` role to anyone who authenticates via the SSO connection, you can pass the following `connection_implicit_role_assignments` argument:
```json theme={null}
{
"connection_implicit_role_assignments": [
{ "role_id": "admin" }
]
}
```
## SSO Connection IdP Group-based Role Assignments
Assign roles to your Members implicitly based on their **SSO Connection IdP Groups** by passing `role_id` and `group` pairs to [`group_implicit_role_assignments`](/docs/api-reference/b2b/api/sso/saml/create-saml-connection#body-group-implicit-role-assignments).
For example, if you want to assign the `admin` role to anyone in the `engineering` group, you can pass the following `group_implicit_role_assignments` argument:
```json theme={null}
{
"group_implicit_role_assignments": [
{ "role_id": "admin", "group": "Engineering" }
]
}
```
Then, add a `groups` key to the SAML connection's `attribute_mapping`. The IdP should be configured to send a list of strings under that key.
For example, if your Stytch SAML connection has the following attribute mapping:
```json theme={null}
{
"attribute_mapping": {
"email": "email",
"full_name": "name",
"groups": "groups"
}
}
```
And Stytch receives the following SAML assertion for a Member:
```xml theme={null}
EPDEngineering
```
Based on the `group_implicit_role_assignments` and their membership in the `Engineering` group, the Member will be assigned the `admin` role.
## Interactions between Implicit and Explicit Role Assignments
Because implicit role assignments are based on the Member's login method, RBAC authorization checks are based on the roles for a Member's **Session**, rather than the Member definition. This means that a Member's roles may vary, depending on the login method for that Session.
If an explicitly assigned Role is removed from a Member, and the Member is also implicitly assigned that Role from an SSO connection or an SSO group, we will by default revoke any existing Sessions for the Member that contain any SSO authentication factors with the affected connection ID.
For example, consider a Member with the following Roles:
```json theme={null}
{
"member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"roles": [
{
"role_id": "editor",
"sources": [
{
"type": "direct_assignment",
"details": {}
},
{
"type": "sso_connection",
"details": {
"connection_id": "saml-connection-test-51861cbc-d3b9-428b-9761-227f5fb12be9"
}
}
]
},
{
"role_id": "stytch_member",
"sources": [
{
"type": "direct_assignment",
"details": {}
}
]
}
]
}
```
This Member's `editor` role comes from two sources: an explicit role assignment and an SSO connection implicit role assignment.
Now consider the following Member Session, which contains a SAML SSO authentication factor:
```json theme={null}
{
"member_session": {
"member_session_id": "session-test-fe6c042b-6286-479f-8a4f-b046a6c46509",
"member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"authentication_factors": [
{
"delivery_method": "sso_saml",
"saml_sso_factor": {
"id": "saml-member-registration-test-9a6d293d-d8b3-42e8-abb4-220cc2060e93",
"provider_id": "saml-connection-test-51861cbc-d3b9-428b-9761-227f5fb12be9"
},
"type": "sso"
}
],
"organization_id": "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
"roles": ["stytch_member", "editor"]
}
}
```
If the Member is then updated (for example, from a call to [Update Member](/docs/api-reference/b2b/api/members/update-member)) to remove the `editor` explicit role assignment, we will revoke the Member Session with the SAML SSO factor.
If you do not want any Sessions to be revoked when updating a Member's explicit role assignments, you can pass the [`preserve_existing_sessions`](/docs/api-reference/b2b/api/members/update-member#body-preserve-existing-sessions) argument with a value of `true` to the following endpoints:
* [Update Member](/docs/api-reference/b2b/api/members/update-member)
* [Password Migrate](/docs/api-reference/b2b/api/passwords/migrate)
# Microsoft Entra
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/scim/microsoft-entra
Set up a SCIM connection with Microsoft Entra
To test out SCIM, set up an [Entra developer instance](https://learn.microsoft.com/en-us/entra/verified-id/how-to-create-a-free-developer-account) to use for this guide.
## Configure a SCIM connection for a specific Organization
If you don't already have an application in Entra, create one by navigating to “Applications” → “Enterprise Applications” and selecting “create your own application”.
In the app creation flow, input a name for your application and select “Integrate any other application you didn't find in the gallery".
Once you have an application, enable SCIM by clicking on “Provisioning” on the left hand management side bar, or “Provision User Accounts” under the Getting Started section.
On the next page, click “Get started” and then switch the provisioning mode from “Manual” to “Automatic”.
You should now see an "Admin Credentials" section. Leave this tab open, and navigate back to Stytch.
Create a SCIM Connection on the Organization in the [Stytch Dashboard](https://stytch.com/dashboard/organizations) or the [Create SCIM Connection endpoint](/docs/api-reference/b2b/api/scim/connection-management/create-scim-connection). Select Microsoft Entra as the IdP.
Once you click save, you'll be provided with the base url and bearer token you'll need for the next step.
Leave this tab open and navigate back to Entra to input the returned credentials.
On the "Provisioning" tab under "Admin Credentials":
1. Copy the "BaseURL" from Stytch into the “Tenant URL” field
2. Copy the "HTTP Header Bearer Token" from Stytch into the "Secret Token" field
If you did not specify an IdP when creating the SCIM Connection, you must append `?aadOptscim062020` to the returned BaseURL to flag the application into [Entra's SCIM 2.0 compliant version](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/application-provisioning-config-problem-scim-compatibility#scim-20-compliance-issues-and-status)
Click "Test Connection", then save.
Navigate back to “Provisioning”, and
1. Under "Mappings" ensure that `objectId` is mapped to `externalId` (i.e. `objectId` is set to the "Source" attribute and `externalId` is set to the "Target" attribute).
2. Toggle "Provisioning Status" to “On”.
Once saved, you can test the SCIM integration by assigning people to and removing people from the application. Entra does automatic syncing on a 40 minute timer, but you can provision on demand to speed up testing.
You should see the status of the member changing from `active` to `deactivated`.
To notify your own system of changes that occur via SCIM, [configure webhooks](/docs/resources/workspace-management/webhooks). See the full list of relevant webhooks [here](/docs/multi-tenant-auth/enterprise-ready/scim/overview#supported-scim-actions).
## Next Steps
If you only have a few customers who require SCIM connections, you can manage them by hand in the [Stytch Dashboard](https://stytch.com/dashboard/organizations). However, as your enterprise customer base grows, you may want to build a UI in your application to allow admins of Organizations to self-serve creating and updating their own SCIM connections.
The simplest way to add SCIM connection management to your application is to use Stytch's pre-built [Admin Portal](/docs/multi-tenant-auth/enterprise-ready/admin-portal) component.
Read the guide on Admin Portal.
Jump straight to the React, Next.js, or Vanilla JS SDK reference.
# SCIM with Okta
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/scim/okta
Set up a SCIM connection with Okta
To test out SCIM, set up an [Okta developer instance](https://developer.okta.com/signup/) to use for this guide.
## Configure a SCIM connection for a specific Organization
On the [existing SSO SAML application in Okta](/docs/multi-tenant-auth/authentication/sso/overview), enable SCIM provisioning by navigating to the General tab of the application and checking “Enable SCIM Provisioning” under "App Settings".
Save this change.
Create a SCIM Connection on the Organization in the [Stytch Dashboard](https://stytch.com/dashboard/organizations) or the [Create SCIM Connection endpoint](/docs/api-reference/b2b/api/scim/connection-management/create-scim-connection). Select Okta as the IdP.
Once you click save, you'll be provided with the base url and bearer token you'll need for the next step.
Leave this tab open and navigate back to Okta to input the returned credentials.
In the application view in Okta, navigate to the new "Provisioning" tab and:
1. Change the Authentication mode to HTTP Header
2. Copy the "BaseURL" from Stytch into the “SCIM connector base URL” field
3. Set the Unique identifier to `userName`
4. Under "Supported provisioning actions", select all the "Push.." options
5. Copy the "HTTP Header Bearer Token" from Stytch into the "HTTP Header → Authorization" field
Your connection settings should look as follows:
Save.
Once saved, you can test the SCIM integration by assigning people to and removing people from the application.
You should see the status of the member changing from `active` to `deactivated`.
To notify your own system of changes that occur via SCIM, [configure webhooks](/docs/resources/workspace-management/webhooks). See the full list of relevant webhooks [here](/docs/multi-tenant-auth/enterprise-ready/scim/overview#supported-scim-actions).
If you haven't already configured a SAML application, create a standalone SCIM Application by
1. Navigate to Applications → Browse App Catalog
2. Search for “SCIM 2.0 Header Auth”
3. Select the "SCIM 2.0 Test App (Header Auth)"
Name your application:
On the "Sign-on Options" tab scroll to the bottom, and under "Credential Details" change the "Application username format" to "Email".
Save the application, then navigate to the "Provisioning" tab and click "Configure API Integration".
Create a SCIM Connection on the Organization in the [Stytch Dashboard](https://stytch.com/dashboard/organizations) or the [Create SCIM Connection endpoint](/docs/api-reference/b2b/api/scim/connection-management/create-scim-connection). Select Okta as the IdP.
Back in the Okta admin dashboard, input the returned BaseURL and set the API Token to `Bearer ${returned_bearer_token}`.
You will then be able to select which actions to send via SCIM.
Once saved, you can test the SCIM integration by assigning people to and removing people from the application.
You should see the status of the member changing from `active` to `deactivated`.
To notify your own system of changes that occur via SCIM, [configure webhooks](/docs/resources/workspace-management/webhooks). See the full list of relevant webhooks [here](/docs/multi-tenant-auth/enterprise-ready/scim/overview#supported-scim-actions).
## Next Steps
If you only have a few customers who require SCIM connections, you can manage them by hand in the [Stytch Dashboard](https://stytch.com/dashboard/organizations). However, as your enterprise customer base grows, you may want to build a UI in your application to allow admins of Organizations to self-serve creating and updating their own SCIM connections.
The simplest way to add SCIM connection management to your application is to use Stytch's pre-built [Admin Portal](/docs/multi-tenant-auth/enterprise-ready/admin-portal) component.
Read the guide on Admin Portal.
Jump straight to the React, Next.js, or Vanilla JS SDK reference.
# SCIM Overview
Source: https://stytch.com/docs/multi-tenant-auth/enterprise-ready/scim/overview
Allow enterprise customers to use SCIM to manage identity and access from a workforce IdP
**System for Cross-Domain Identity Management (SCIM)** allows IT Admins to make changes to their employee's identity information and access within their workforce IdP (i.e Okta), and have those changes **automatically propagated** out to all of their connected applications. SCIM is critical for large enterprises, who rely on this protocol as a way to centrally and securely manage employee identity and access the thousands of SaaS tools companies use at scale.
Use the SCIM API endpoints to manage SCIM connections on your backend.
Use the frontend SDKs to manage SCIM connections on your frontend.
Add drop-in SCIM connection management UI to your application.
## How it works
Stytch's SCIM product handles everything out-of-the-box for you, immediately updating Members, handling session revocation, and granting/revoking RBAC roles based on actions from the IdP.
To keep other systems in sync, use Stytch's [webhooks](/docs/resources/workspace-management/webhooks) to trigger updates elsewhere in your application.
### Example flow
```mermaid theme={null}
sequenceDiagram
participant IdP as Workforce IdP
participant Stytch
participant App as Your App
Note over IdP: User is deprovisioned in the Organization's IdP
IdP->>Stytch: Sends SCIM request communicating the change
Note over Stytch: Updates Member status to deactivated, Revokes all sessions
Stytch->>App: Triggers `scim.member.delete` webhook
Note over App: [Optional] Receive webhook and make any necessary updates to your internal records
```
## Supported SCIM actions
The follow actions in SCIM are supported, and result in the following updates to the corresponding Stytch Organization and Members:
| SCIM action | Stytch updates | RBAC updates | Session updates | Webhook triggered |
| - | - | - | - | - |
| **Member provisioned** | Creates Member in Organization | Assigns any email domain Implicit Role Grants | scim.member.create | |
| **Member info updated** | Update Member entity | Revokes or Assigns any email domain Implicit Role Grants | RBAC changes propagated to active JWTs on next refresh \<= 5 min | `scim.member.update` |
| **Member deprovisioned** | Updates Member.Status to deactivated | Revokes all current Roles | Revokes all current Sessions | `scim.member.delete` |
| **Member reactivated** | Updates Member.Status to active | Apply any qualified Implicit Role Grants (Explicit Roles Grants must be re-assigned) | `scim.member.update` | |
| **Group created** | Creates a SCIM Group record with a stable UUID | `scim.scim_group.create` | | |
| **Group info updated** | Updates SCIM Group's Display Name (only editable field) | `scim.scim_group.update` | | |
| **Member added to group** | Creates record of MemberID belonging to SCIM Group | Applies any SCIM Group Implicit Role Grants | RBAC changes propagated to active JWTs on next refresh \<= 5 min | `scim.scim_member_group.create` |
| **Member removed from group** | Sets Member's SCIM Group membership record to deleted | Removes any SCIM Group Implicit Role Grants | RBAC changes propagated to active JWTs on next refresh \<= 5 min | `scim.scim_member_group.delete` |
| **Group deleted** | Updates SCIM Group and membership records to deleted | Revokes any SCIM Group Implicit Role Grants | RBAC changes propagated to active JWTs on next refresh \<= 5 min | `scim.scim_group.delete` |
## Attribute mappings
Stytch requires any identity provider to specify the email and name of the member attempting to log in. It parses these from the attributes sent in the IdP's assertion to Stytch. To recognize which attributes correspond to which fields, Stytch allows your connection to define an `attribute_mapping`.
An example `attribute_mapping` looks like this:
```json theme={null}
{
"email": "EmailAddress",
"full_name": "FullName",
"idp_user_id": "ExternalID",
"title": "Title",
}
```
Stytch requires an `email` key to be present to determine the member's email. If the connection uses the Email Address NameID format, set the email key to NameID to tell Stytch to parse the `email` from the `NameID`. If the connection uses an alternative `NameID` format, set `email` to the value of the SAML attribute containing the member's email. Additionally, in order to determine the member's name, Stytch requires either the `full_name` key to be present, or both the `first_name` and `last_name` keys.
We **strongly recommend** configuring an `idp_user_id` attribute, set to the user's stable ID in their IdP. This will allow Stytch to find the existing Member in the event of an IdP-driven email change, and log the user into their existing account rather than creating a new Member record.
Stytch reserves the following Attribute Mapping keys for internal use:
* `email`
* `full_name`
* `first_name`
* `last_name`
* `groups`
* `idp_user_id`
Any additional Custom Attribute Mappings defined will be used to promote SSO Attributes to the member's Trusted Metadata. Custom SSO Attributes will be merged with the member's existing Trusted Metadata using Stytch's [metadata update behavior.](/docs/api-reference/b2b/api/resources/object-update-behavior)
### Considerations for using SSO Custom Attributes
* Although Trusted Metadata can still be updated directly via the [Update Member](/docs/api-reference/b2b/api/members/update-member) endpoint, fields driven by the IdP will be overwritten again on subsequent logins.
* Metadata derived from IdP attributes will still exist after the IdP is deleted.
* If an IdP does NOT send a field, or the field is removed from the attribute mapping, Stytch will NOT delete the field from the Trusted Metadata on next login. All fields need to be deleted explicitly via the [Update Member](/docs/api-reference/b2b/api/members/update-member) endpoint.
Putting this all together, given the following SAML assertion:
```yaml theme={null}
john.doe@example.com
John Doe
u_123_example
Staff Software Engineer
```
Stytch will extract the following values:
* `member.email`: [john.doe@example.com](mailto:john.doe@example.com)
* `member.name`: John Doe
* `member.sso_registrations[0].external_id`: u\_123\_example
* `member.trusted_metadata.title`: Staff Software Engineer
The attribute mapping can be set in the [Update SAML Connection](/docs/api-reference/b2b/api/sso/saml/update-saml-connection) endpoint.
### Single sign-on URLs
Some IdPs may ask for a single sign-on URL. This is listed as the `acs_url` in the [SAML connection object](/docs/api-reference/b2b/api/sso/saml-connection-object). Note that for Stytch's purposes, the `acs_url` and `audience_uri` are the same value.
# Check for an active session
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/check-active-session
Use the SDK to control your application's UI or navigation based on whether a member has an active session.
## Use case
* **Gate your app's UI or navigation:** Restrict access to application features that require authentication.
* **Route authenticated members:** Direct members with active sessions to your main application experience.
* **Redirect unauthenticated members:** Send members without sessions to your login or signup flow.
## Retrieve a member session
Select a framework:
Follow our [Next.js quickstart](/docs/multi-tenant-auth/build-auth/discovery#next-js-app) for an example routing a member based on whether there's an active session.
We recommend placing this logic close to the entry point to each page you want to control in your application.
You can use the [`useStytchMemberSession()`](/docs/api-reference/b2b/frontend-sdks/nextjs/hooks/use-stytch-member-session) hook to retrieve a member's session data and listen for changes:
```jsx lines theme={null}
import { useStytchMemberSession } from '@stytch/nextjs/b2b';
import { useRouter } from 'next/navigation';
// Protected page example that routes a member to login if there's no active session
export default function Home() {
const { session, isInitialized } = useStytchMemberSession();
const router = useRouter();
useEffect(() => {
// Route the member to login if there's no session
if (!session && isInitialized) {
router.replace("/login");
}
}, [session, isInitialized, router]);
if (!isInitialized || !session) {
// Show loading while redirecting or if the SDK hasn't finished initializing
return
Loading...
;
}
// If there's an active session, show the home content
return
Welcome! You have an active session.
;
}
```
The [`useStytchMemberSession()`](/docs/api-reference/b2b/frontend-sdks/nextjs/hooks/use-stytch-member-session) hook returns the values:
Whether or not the SDK has finished initializing. Ensure this is `true` before checking for an active session.
The active [Member Session](/docs/api-reference/b2b/api/sessions/session-object) object. `null` if there's no active session.
Whether the SDK is returning cached session data based on if the SDK has completed an [Authenticate Session](/docs/api-reference/b2b/frontend-sdks/nextjs/methods/sessions/authenticate-session) call or not.
For most cases, you can rely on checking the `session` value as long as `isInitialized` is `true`.
The frontend SDK utilizes [stale-while-revalidate (SWR)](https://web.dev/case-studies/ads-case-study-stale-while-revalidate#what%27s-it-mean) for faster time-to-interactivity and UX:
1. The page first loads.
2. Stytch's SDK checks and returns cached Member and Session data to your application.
3. The SDK refreshes Member and Session data in background session authentication while your application starts.
For use cases where you'd prefer to wait until the session data finishes updating instead of relying on cached data, you can verify that `fromCache` is `false` before checking the session value.
Follow our [React quickstart](/docs/multi-tenant-auth/build-auth/discovery#react) for an example routing a member based on whether there's an active session.
We recommend placing this logic close to the entry point to each page you want to control in your application.
You can use the [`useStytchMemberSession()`](/docs/api-reference/b2b/frontend-sdks/react/hooks/use-stytch-member-session) hook to retrieve a member's session data and listen for changes:
```jsx lines theme={null}
import { useStytchMemberSession } from '@stytch/react/b2b';
import { Navigate } from "react-router";
// Protected page example that routes a member to login if there's no active session
export const Home = () => {
const { session } = useStytchMemberSession();
// Route members to login if there's no active session
if (!session) {
return ;
}
return
Welcome! You have an active session.
;
};
```
The [`useStytchMemberSession()`](/docs/api-reference/b2b/frontend-sdks/react/hooks/use-stytch-member-session) hook returns the values:
The active [Member Session](/docs/api-reference/b2b/api/sessions/session-object) object. `null` if there's no active session.
Whether the SDK is returning cached session data based on if the SDK has completed an [Authenticate Session](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/authenticate-session) call or not.
For most cases, you can rely on checking the `session` value.
The frontend SDK utilizes [stale-while-revalidate (SWR)](https://web.dev/case-studies/ads-case-study-stale-while-revalidate#what%27s-it-mean) for faster time-to-interactivity and UX:
1. The page first loads.
2. Stytch's SDK checks and returns cached Member and Session data to your application.
3. The SDK refreshes Member and Session data in background session authentication while your application starts.
For use cases where you'd prefer to wait until the session data finishes updating instead of relying on cached data, you can verify that `fromCache` is `false` before checking the session value.
Follow our [Vanilla JS quickstart](/docs/multi-tenant-auth/build-auth/discovery#vanilla-js) for an example routing a member based on whether there's an active session.
We recommend placing this logic close to the entry point to each page you want to control in your application.
You can use the [`session.getSync()`](/docs/api-reference/b2b/frontend-sdks/vanilla-js/methods/sessions/get-session-sync) method to retrieve a member's session data:
```javascript lines theme={null}
import { stytch } from "./stytch-client.js"; // or where Stytch is initialized
function init() {
const session = stytch.session.getSync();
if (!session) {
// Redirect to login if there's no active session
window.location.href = "/login";
return;
}
// If there's an active session, show the protected content
document.getElementById('app').innerHTML = '
Welcome! You have an active session.
';
}
// Initialize when the page loads
document.addEventListener("DOMContentLoaded", init);
```
The `session.getSync()` method returns the value:
The active [Member Session](/docs/api-reference/b2b/api/sessions/session-object) object. `null` if there's no active session.
You can use the [`session.onChange()`](/docs/api-reference/b2b/frontend-sdks/vanilla-js/methods/sessions/on-change-session) method to listen for changes in the member's session:
```javascript lines theme={null}
// Listen for session changes
const unsubscribeSession = stytch.session.onChange((newSession) => {
session = newSession;
// Trigger any relevant actions in your application based on the session update
handleSessionChange();
});
window.addEventListener('beforeunload', () => {
unsubscribeSession && unsubscribeSession();
});
```
* The method takes in a callback that gets called whenever the Session object changes.
* It returns an unsubscribe method for you to call when you no longer want to listen for changes.
# Cookies & sessions
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/cookies
Options for cookie configuration options and HttpOnly cookies.
## Overview
The Member Session is automatically saved in two cookies:
Contains the opaque `session_token` returned from the API.
Contains the `session_jwt` returned from the API.
For example, if the SDK is loaded on `https://app.example.com`, the cookies will be set as:
* `document.cookie = "stytch_session=secret-session; domain=app.example.com; path=/; SameSite=Lax; Secure; max-age=whenever-it-expires"`
* `document.cookie = "stytch_session_jwt=eyJhxxx.xxx.xxx; domain=app.example.com; path=/; SameSite=Lax; Secure; max-age=whenever-it-expires"`
The cookie will not be marked as secure if the SDK is loaded on `localhost`.
***
## Configure cookie options
The Stytch SDK allows you to configure the following directives and values for cookie management via `cookieOptions`:
```json expandable theme={null}
{
"cookieOptions": {
/**
* The name of the cookie containing the opaque Stytch session token.
* Defaults to `stytch_session`.
*/
"opaqueTokenCookieName": "string",
/**
* The name of the cookie containing the opaque Stytch session token.
* Defaults to `stytch_session_jwt`.
*/
"jwtCookieName": "string",
/**
* The name of the cookie containing the Stytch intermediate session token.
* Defaults to `stytch_intermediate_session_token`.
*/
"istCookieName": "string",
/**
* What HTTP paths the cookies should be available on.
* Equal to configuring the `;path=${}` param in the set-cookie directive.
* Defaults to unset.
*/
"path": "string",
/**
* What domain the cookies should be available on.
* Equal to configuring the `;domain=${}` param in the set-cookie directive.
* Requires setting availableToSubdomains to have any effect.
* Defaults to unset.
*/
"domain": "string",
/**
* Whether to make the cookies available to subdomains.
* When true, equivalent to configuring the `;domain=${window.location.host}` directive.
* When false, equivalent to leaving the directive unset.
* Defaults to false.
*/
"availableToSubdomains": "boolean"
}
}
```
To override default values, set the desired values in the `cookieOptions` configuration and pass it as part of the optional second parameter when initializing the Stytch SDK client:
```javascript lines theme={null}
import { StytchB2BClient } from "@stytch/vanilla-js/b2b"
const STYTCH_PUBLIC_TOKEN = 'YOUR_PUBLIC_TOKEN';
const stytchOptions = {
cookieOptions: {
opaqueTokenCookieName: "my_stytch_session",
jwtCookieName: "my_stytch_session_jwt",
path: "/",
availableToSubdomains: true,
domain: "app.example.com",
}
}
const stytchClient = new StytchB2BClient(STYTCH_PUBLIC_TOKEN, stytchOptions)
```
`cookieOptions` do not apply to HttpOnly cookies.
***
## HttpOnly cookies
For added security, you can enable the use of HttpOnly cookies for your project. This moves cookie management from the client to Stytch's backend.
When HttpOnly cookies are used, Stytch's backend sets the `Domain` of the cookie to the parent of your custom domain. Cookies are accessible to the `Domain` specified and all its subdomains, for example:
* If your custom domain is `login.example.com`, the `Domain` will be set to `example.com`, making the cookies accessible to `example.com`, `app.example.com`, `login.example.com`, etc.
* If your custom domain is `login.app.example.com`, the `Domain` will be set to `app.example.com`, making the cookies accessible to `app.example.com` and its subdomains, but not `example.com`.
By default, cookies managed by the SDK running in the client are not marked as `HttpOnly`.
***
### Enable HttpOnly cookies
Important things to consider before enabling HttpOnly cookies:
#### Requirements
1. Using HttpOnly cookies **requires** a custom domain to be set up for your Stytch project to allow Stytch's backend to set cookies in a first-party context for your domain.
2. HttpOnly Cookies are not supported in localhost environments.
#### Considerations
1. Using HttpOnly cookies means the cookies are no longer accessible via JavaScript in the browser. This includes `session_tokens`, `session_jwts`, and `intermediate_session_tokens`. These tokens are omitted from any response bodies to the client.
2. `session.getTokens()` will no longer return session tokens when an active session exists.
3. `session.updateSession()` will only work if there's **not** an existing session token set using an HttpOnly cookies. Make sure `cookieOptions` are configured to match the domain and cookie names used by Stytch's backend if you use this method.
4. If the HttpOnly cookies setting is set to `enforced`, Stytch will only accept browser requests for your project that use your custom domain.
5. `cookieOptions` does not apply to HttpOnly cookies.
***
Configure a [custom domain](https://stytch.com/dashboard/custom-domains) for your project.
Set the HttpOnly cookies setting to `Enabled` under the [Frontend SDK settings](https://stytch.com/dashboard/sdk-configuration?env=live).
In your application, set the `customBaseUrl` option in your Stytch SDK client to your custom domain to tell the SDK to use your custom domain for requests.
We recommend initially changing this setting in a staging environment before enabling it in production.
Verify that your application behaves as expected. The `stytch_session` and `stytch_session_jwt` cookies should now be marked as `HttpOnly`.
After confirming your implementation works as expected, you can set the HttpOnly cookies setting to `Enforced` to require all browser requests to use your custom domain.
This will block all browser SDK requests that do not use your custom domain.
***
### Disable HttpOnly cookies
Stop using HttpOnly cookies by removing the `customBaseUrl` setting from your Stytch client configuration.
This will not remove existing HttpOnly cookies from the user's browser, which the SDK will be unable to access.
To avoid disruptions to existing sessions, have your backend replace HttpOnly cookies with non-HttpOnly cookies.
# Add custom claims
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/custom-claims
Encode additional information into your session JWTs using custom claims.
## Use case
* **Store or pass member information:** Pass temporary session state or member metadata to your application.
* **Integrate with 3rd-party services:** Integrate with providers that support JWT-based authentication.
* **Implement custom authorization logic:** Use custom claim templates to pass RBAC information to sessions.
***
## Add custom claims to a session
Include the `session_custom_claims` argument on any authenticate method to add custom claims to a session. This argument takes in an arbitrary JSON object—which may be represented as a `map`, `dictionary`, or object:
```python theme={null}
# initial claims object {}
stytch.session.authenticate(token, session_custom_claims={"key_1": 1, "key_2": 2})
# current claims object {"key_1": 1, "key_2": 2}
stytch.session.authenticate(token, session_custom_claims={"key_1": 9})
# resulting claims object {"key_1": 9, "key_2": 2}
```
### Remove claims
Supply a `null` value to delete a key:
```python theme={null}
# current claims object {"key_1": 1, "key_2": 2}
stytch.session.authenticate(token, session_custom_claims={"key_1": null})
# resulting claims object {"key_2": 2}
```
If the value of a custom claim is a JSON object, the nested values can be updated or deleted using the same methods as above:
```python theme={null}
sessions.authenticate(token, session_custom_claims={
"b": null,
"c": 3.5,
"e": {
"nested1": "val1",
"nested2": "val2"
}
})
# resulting claims object
# {
# "c": 3.5,
# "d": 4,
# "e": {
# "nested1": "val1",
# "nested2": "val2"
# }
# }
stytch.session.authenticate(token, session_custom_claims={
"e": {
"nested1": nil,
"nested3": "val3"
}
)
# resulting claims object
# {
# "c": 3.5,
# "d": 4,
# "e": {
# "nested2": "val2",
# "nested3": "val3"
# }
# }
```
***
## Custom claim templates
Use [custom claim templates](https://stytch.com/dashboard/custom-claim-templates) to add the same set of claims to **every** session.
* The JSON object output for a particular member will be used as the initial set of custom claims for all of that member's sessions.
* Claims from templates can still be added, updated, or deleted by passing in a `session_custom_claims` argument in an API request.
* Updates to the custom claim template or member metadata propagate to existing sessions the next time a JWT is minted.
* Previously minted JWTs are immutable and cannot be updated.
### Markup syntax
Custom claim template markup language uses `{{ variable }}` syntax to denote information that should be passed in at runtime.
### Member or Organization metadata
Use dot notation to access nested fields when handling Member or Organization metadata. For example, `{{ member.trusted_metadata.subscription.level }}` accesses the `level` field of the `subscription` object in the `trusted_metadata` on a Member object.
### Member RBAC values
The variable `{{ member.rbac.roles }}` is evaluated as an array of all role IDs assigned to the member, e.g. `["stytch_member", "editor", "support_admin"]`.
The variable `{{ member.rbac.$RESOURCE_ID.actions }}` is evaluated as an array of all actions the member can perform on the specific resources. For example, `{{ member.rbac.documents.actions }}` might be evaluated as `["create", "read", "delete"]`.
### List of supported template variables
* `{{ member.external_id }}`
* `{{ member.member_id }}`
* `{{ member.name }}`
* `{{ member.email_address }}`
* `{{ member.rbac.roles }}`
* `{{ member.rbac.$RESOURCE_ID.actions }}`
* `{{ member.trusted_metadata.$PATH }}`
* `{{ organization.external_id }}`
* `{{ organization.organization_id }}`
* `{{ organization.organization_name }}`
* `{{ organization.trusted_metadata.$PATH }}`
The template:
```json theme={null}
{
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "reader",
"x-hasura-allowed-roles": {{ member.rbac.roles }},
"x-hasura-user-id": {{ member.member_id }},
"x-hasura-custom-key": {{ member.trusted_metadata.custom_key }},
"x-hasura-organization-id": {{ organization.organization_id }}
}
}
```
could be combined with the member's information:
```json theme={null}
{
"member_id": "member-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6",
"trusted_metadata": {
"custom_key": "custom-value"
}
}
```
to produce the final set of custom claims:
```json theme={null}
{
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "reader",
"x-hasura-allowed-roles": ["admin", "reader"],
"x-hasura-user-id": "member-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6",
"x-hasura-custom-key": "custom-value",
"x-hasura-organization-id": "org-test-12345"
}
}
```
***
## Restrictions
Both API-driven custom claims and custom claim templates share these restrictions:
* These claims are reserved and result in an error if set:
`iss` `sub` `aud` `exp` `nbf` `iat` `jti` `https://stytch.com/*`
* Total claims size cannot exceed 4kb.
* A JWT cannot be modified once it is minted. You must authenticate again to update claims.
# Hydrating sessions
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/hydrate-session
Hydrate the frontend session state from the backend.
## Use case
* **Backend-initiated session changes:** Ensure your frontend session state stays in sync when sessions are created or updated on the backend.
***
## Session hydration
Provide both the active `session_token` and `session_jwt` to the `session.updateSession()` [method](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/update-session) to prime the frontend with a valid set of tokens.
Authenticate the session using the `session.authenticate()` [method](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/authenticate-session) to validate the updated session tokens.
```javascript lines theme={null}
export const hydrateSession = () => {
stytch.session.updateSession({
session_token: 'ACTIVE_SESSION_TOKEN',
session_jwt: 'ACTIVE_SESSION_JWT',
});
stytch.session.authenticate();
};
```
When `HttpOnly` cookies are enabled for your Stytch project, the [Sessions Update method](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/update-session) only works if there isn't an existing session token set in an `HttpOnly` cookie.
* If `HttpOnly` cookies are enabled, make sure your SDK client's `cookieOptions` are configured to match the domain and cookie names used by Stytch's backend.
* Alternatively, you can override the session cookies via response headers from your backend. Be sure to still make a frontend authenticate session call.
# Organization discovery
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/intermediate-sessions/discovery
Build a Discovery authentication flow for organizations.
## What is discovery?
Discovery refers to a state during authentication where an end user is not yet associated with an organization and is "discovering" the organizations that they are eligible to authenticate into.
***
## What happens
An `intermediate_session_token` is returned in response to any of the Discovery authenticate methods ([example](/docs/api-reference/b2b/frontend-sdks/react/methods/email-magic-links/authenticate-discovery-magic-link)).
A list of [Discovered Organization objects](/docs/api-reference/b2b/api/discovery/discovered-organization-object) is included in the response. This is a list of organizations associated with the member. This can be used to inform subsequent UI to list available organizations for the member to select from.
If only one organization is associated with the member, you can automatically start a session with that organization by calling `discovery.intermediateSessions.exchange()` with the organization's ID.
Similarly, if no organizations are associated with the member, you can choose to create a new organization for them to streamline your user experience.
[Stytch UI](/docs/multi-tenant-auth/build-auth/overview) provides configurations, such as `directLoginForSingleMembership`, to handle these scenarios out-of-the-box.
### Sample response
```json Response 200 expandable theme={null}
{
"discovered_organizations": [
{
"member_authenticated": false,
"membership": {
"details": null,
"member": {
// Full Member object
},
"type": "active_member"
},
"mfa_required": {
"member_options": {
"mfa_phone_number": "XXXXXXX1234",
"totp_registration_id": ""
},
"secondary_auth_initiated": null
},
"organization": {
// Full Organization object (fields omitted for brevity)
...
"organization_id": "organization-...",
"organization_logo_url": "",
"organization_name": "Example Organization One",
"organization_slug": "example-organization-one",
},
"primary_required": null
},
{
"member_authenticated": true,
"membership": {
"details": null,
"member": {
// Full Member object
},
"type": "pending_member"
},
"mfa_required": null,
"organization": {
// Full Organization object (fields omitted for brevity)
...
"organization_id": "organization-...",
"organization_logo_url": "",
"organization_name": "Example Organization Two",
"organization_slug": "example-organization-two",
},
"primary_required": null
},
],
"email_address": "example@stytch.com",
"intermediate_session_token": "Is9gF...",
"request_id": "request-id-...",
"status_code": 200
}
```
***
## Select or create an organization
At this point, you can have users select an organization to start a session with or allow them to create a new organization using either method.
Start a session with a selected organization.
Allow users to create a new organization.
The `intermediate_session_token` is automatically saved in browser cookies by the SDK and isn't required to be explicitly passed into these methods.
To allow organization creation via the frontend, the [Create Organizations setting](https://stytch.com/dashboard/sdk-configuration) must be enabled for your project.
### Example
```javascript wrap theme={null}
export const exchangeDiscoveryIntermediateSessionToken = (organization_id) => {
stytch.discovery.intermediateSessions.exchange({
organization_id,
session_duration_minutes: DESIRED_SESSION_LENGTH,
});
}
export const createOrganization = (organization_name, organization_slug) => {
stytch.discovery.organizations.create({
organization_name,
organization_slug,
session_duration_minutes: DESIRED_SESSION_LENGTH,
});
}
```
***
## Next steps
If the end user selects an organization where `member_authenticated` is `false`, you will need to trigger additional MFA or step-up authentication steps based on the authentication requirements of the selected organization.
Instead of calling the endpoints above, you can proceed directly to step-up authentication (if the `primary_required` value is non-null) or MFA (if the `mfa_required` value is non-null).
Adding step-up authentication.
Handling multi-factor authentication.
# Multi-factor authentication (MFA)
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/intermediate-sessions/mfa
Sessions during multi-factor authentication flows.
## What is multi-factor authentication?
Multi-factor authentication (MFA) is a mechanism that requries users to provide two or more authentication factors to verify their identity.
***
## What happens
If a user is required to complete multi-factor authentication, a `intermediate_session_token` is returned instead of a `session_token` in response to the initial authentication request, and an OTP code is automatically sent to the user's phone number (if they are enrolled in SMS OTP).
The following values will also be set in the authentication response:
Value will be `false`.
Value may be non-null if the member also needs to complete a primary step-up authentication flow.
If this value is non-null, the primary step-up flow should be completed before proceeding to MFA completion.
Value will be non-null and will contain which form of MFA is required to be completed.
### Sample response
```json Response 200 expandable theme={null}
{
"intermediate_session_token": "Fxq0E...",
"member": {
// Full Member object
},
"member_authenticated": false,
"member_id": "member-...",
"member_session": null,
"method_id": "member-email-...",
"mfa_required": {
"member_options": {
"mfa_phone_number": "XXXXXXX1234",
"totp_registration_id": ""
},
"secondary_auth_initiated": "sms_otp"
},
"organization": {
// Full Organization object
},
"organization_id": "organization-...",
"request_id": "request-id-...",
"reset_sessions": false,
"session_jwt": "",
"session_token": "",
"status_code": 200
}
```
***
## Next steps
At this point, you will need to prompt the user to completed the required MFA step. See our guide on how to implement multi-factor authentication:
Guide to integrating multi-factor into your own authentication flow.
# Intermediate sessions in authentication
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/intermediate-sessions/overview
Intermediate sessions are used during authentication flows that require intermediate steps before a member can be fully authenticated.
## Overview
The Stytch API returns an `intermediate_session_token` instead of a full `session_token` when intermediate authentication steps are required, such as multi-factor authentication (MFA).
Once a user completes all necessary authentication steps, the `intermediate_session_token` can be exchanged for a full member `session_token`.
The [frontend SDK](/docs/api-reference/b2b/frontend-sdks/react/overview) automatically stores the returned `intermediate_session_token` and will include them in subsequent authentication requests.
***
## In authentication flows
Intermediate session tokens are used to retain the authentication state throughout these flows.
These guides detail what happens during each flow and provide examples for guidance (if you are building your own authentication UI flow while using the Stytch API):
The state after authentication and before the user selects an organization to access.
Also called two-factor authentication (2FA), multi-factor authentication requires users to provide two or more factors to verify their identity.
A type of multi-factor authentication where an application requests additional identification information to verify a user's identity, often to meet security or compliance requirements.
[Stytch UI](/docs/multi-tenant-auth/build-auth/overview) provides pre-built, out-of-box support for all of the above authentication flows.
# Step-up authentication
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/intermediate-sessions/step-up
Sessions during step-up authentication flows.
## What is step-up authentication?
Step-up authentication is a specific type of multi-factor authentication where the application requests additional identification information to verify a user's identity.
When a user is eligible to join an organization via their email domain but the OAuth provider's tenant ID does not match an allowed tenant,
step-up primary authentication is required before a session is granted.
For example, a user may sign in with GitHub OAuth and then complete step-up primary authentication using Google OAuth or an email OTP.
**Also see:**
* [Understanding step-up requirements in OAuth discovery](/docs/multi-tenant-auth/authentication/oauth/overview#understanding-step-up-requirements)
***
## What happens
If a user is required to an additional form of primary authentication, an `intermediate_session_token` is returned instead of a `session_token` in response to the initial authentication request.
The following values will also be set in the authentication response:
Value will be `false`.
Value will be non-null and include which forms of primary authentication are required by the organization.
Details the list of auth methods that the member can complete to fulfill the primary authentication requirements of the organization. The member must complete at least one of the options presented.
* For example, a value of `[magic_link]` indicates that the Member must also complete a magic link authentication step.
* The `intermediate_session_token` must be passed into that primary authentication step.
Value may be non-null if the member also needs to complete MFA for the organization. The member should complete the primary step-up authentication step first before proceeding to the MFA step.
### Sample response
An OAuth authenticate response where primary step-up authentication is required:
```json Response 200 expandable theme={null}
{
"intermediate_session_token": "fTwWh...",
"member": {
// Full Member object
},
"member_authenticated": false,
"member_id": "member-...",
"member_session": null,
"mfa_required": null,
"organization": {
// Full Organization object
},
"organization_id": "organization-...",
"primary_required": {
"allowed_auth_methods": [
"sso",
"microsoft_oauth",
"email_otp",
"magic_link",
"google_oauth"
]
},
"provider_subject": "U08...",
"provider_type": "Slack",
"provider_values": {
"access_token": "x2f...",
"expires_at": "2024-12-10T20:11:53Z",
"id_token": null,
"refresh_token": null,
"scopes": [
"users:read",
"users:read.email"
]
},
"request_id": "request-id-...",
"reset_sessions": false,
"session_jwt": "",
"session_token": "",
"status_code": 200
}
```
***
## Next steps
You will need to prompt the user to complete one of the authentication methods present in the `primary_required` array before they can be fully authenticated and granted a full member session.
The `intermediate_session_token` must be included in the subsequent authenticate request to complete step-up.
The methods specified under `primary_required.allowed_auth_methods` can be used for step-up even if they're not enabled as an authentication method for the organization.
For example, email magic links can be used for step-up authentication even if magic links isn't enabled as a primary authentication method on the organization.
### Additional resources
# JWTs vs. session tokens
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/jwts-and-tokens
Both the `session_jwt` and `session_token` represent the underlying Stytch Session. Your application can use either one or both to balance performance and security.
Stytch allows you to switch between using JWTs and session tokens as needed.
***
## Using session JWTs
A session JWT is a signed token that can be locally verified.
* Contains standard claims and information about the underlying session.
* Expires after 5 minutes, but can be refreshed via API for the duration of the underlying session.
* Will locally validate during the 5-minute period, even if the underlying session is revoked.
### Reasons to use JWTs
* **Reduce latency with JWTs** Speed up authorization requests without a call to the database.
* **Add custom claims** Encode additional information into your Stytch session JWTs.
* **Authorize external applications** Leverage identity information and authorization granted through the JWT.
***
## Using session tokens
A session token is an opaque token that must be verified via an API call.
* Does **not** contain information about the member or the underlying session.
* Invalid once the underlying Session object is revoked or expires.
### Reasons to use session tokens
* **Added security layer** Ensure every call is authorized at that exact moment.
* **Storage limitations** Access to user-side storage is limited and can only store small values.
* **Limit session exposure** If you don't want to expose session data or metadata in user storage (e.g. authentication factors or timestamps).
# Exchange organization sessions
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/lifecycle/exchange-session
Seamlessly switch a member's session between organizations.
## Overview
Session tokens are valid for one organization at a time, but you can exchange a member's active session for a session with another organization using the Session exchange endpoint:
Exchange the member's current session for a session in a specified organization.
***
## Build an organization switcher
See our [example apps](https://github.com/stytchauth/stytch-all-examples/) for a code example of an organization switcher component.
Use the `discovery.organizations.list()` [method](/docs/api-reference/b2b/frontend-sdks/react/methods/discovery/list-discovered-organizations) to retrieve a list of organizations the user has access to:
```jsx theme={null}
export const listOrganizations = async () => {
const { discovered_organizations } = await stytch.discovery.organizations.list();
return discovered_organizations;
};
```
Once the user selects an organization, exchange their active session for a session in the selected organization using the `session.exchange()` [method](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/exchange-session):
```jsx theme={null}
export const exchange = (target_organization_id) => {
stytch.session.exchange({
organization_id: target_organization_id,
session_duration_minutes: DESIRED_SESSION_LENGTH,
});
};
```
* If additional authentication is required for the selected organization, the Exchange session method returns an `intermediate_session_token` instead of a fully minted session.
# Expire or extend sessions
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/lifecycle/extend-or-expire-session
Session duration and extending the lifetime of a session.
## Session expiration
Once a session has expired, it can't be reactivated or extended. The user must re-authenticate to create a new session.
The `session_duration_minutes` parameter sets the lifetime of a session from the time it is created:
* **Minimum:** 5 minutes
* **Maximum:** 527,040 minutes (366 days)
* **Default (if blank):**
* When there is no existing session: 60 minutes
* When extending an existing session: No change to session expiry
***
## Extend a session
By default, the frontend SDK keeps the `session_jwt` updated, but it does **not** automatically extend the lifetime of the underlying session.
For example, if `session_duration_minutes` is set to 60 minutes, the session will expire 60 minutes after it was created regardless of user activity.
Pass a `session_duration_minutes` value to the `session.authenticate()` [method](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/authenticate-session) or [endpoint](/docs/api-reference/b2b/api/sessions/authenticate-session). The session will be set to expire that many minutes from the time of the call.
Techniques could be to set an interval to extend the session's lifetime periodically so the session is automatically extended as long as the user keeps the application open, or setting the expiration based on last user activity.
```javascript lines Via SDK theme={null}
const extendSession = () => {
if (stytch.session.getSync()) {
// Set the session's expiration to 60 minutes from the present
stytch.session.authenticate({
session_duration_minutes: 60,
});
}
};
// Extend the session's lifetime every 10 minutes
let interval = setInterval(extendSession, 600000);
window.addEventListener('beforeunload', () => {
clearInterval(interval);
});
```
```python lines Via API theme={null}
# While getting an authenticated member,
# set their session's expiration to 60 minutes from the present
def get_authenticated_member():
stytch_session = session.get('stytch_session')
if not stytch_session:
return None
try:
resp = stytch_client.sessions.authenticate(
session_token=stytch_session,
session_duration_minutes=60
)
return resp.member
except Exception as e:
return handle_exception(e)
```
# Revoke sessions
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/lifecycle/revoke-session
Revoking a session immediately invalidates the session token, effectively logging the user out. Session JWTs are not immediately revoked; they will still validate locally until they hit the 5-minute expiration mark.
## Using the Stytch SDK
The `sessions.revoke()` [method](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/revoke-session) clears the member and session objects from local storage unless the SDK cannot contact the Stytch servers.
```javascript lines theme={null}
export const logout = () => {
stytch.session.revoke();
};
```
## Using the Stytch API
The `sessions.revoke()` [endpoint](/docs/api-reference/b2b/frontend-sdks/react/methods/sessions/revoke-session) provides two options:
1. Revoke a **single** Member Session by providing the `member_session_id`, `session_token`, or `session_jwt`. This is useful when a member may have multiple sessions on different devices and you want to revoke the current session in question only.
2. Revoke **all** sessions associated with a member by specifying the `member_id`. This option is helpful if you need to revoke all sessions for a member.
```python lines theme={null}
@app.route("/org/logout", methods=["POST"])
def logout():
try:
resp = stytch_client.sessions.revoke(
session_token=session['stytch_session']
)
if resp.status != 200:
return handle_error(resp.status)
# Clear the session managed by Flask as well if successful
session.pop('stytch_session', None)
return redirect(url_for('org_home'))
except Exception as e:
return handle_exception(e)
```
We recommend showing members a list of all their active sessions so they can revoke any unrecognized session by IP address or user agent.
Use [custom claims](multi-tenant-auth/manage-sessions/custom-claims#add-custom-claims-to-a-session) to attach values to the [Member Session object](/docs/api-reference/b2b/api/sessions/session-object#schema-custom-claims) via the `session_custom_claims` parameter.
# Start of a session
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/lifecycle/start-session
The session lifecycle through authentication to a full session.
## Overview
A Member Session begins after an end user meets the primary and secondary factor authentication requirements of an Organization.
In cases where an authentication call does not satisfy the Organization's requirements, an intermediate session is returned. This will be exchanged for a full session after the authentication flow is completed:
Begins if an end user has started a primary factor authentication, but isn't fully authenticated into an organization yet.
Begins once an end user has completed all required authentication factors for an organization.
***
## Intermediate session
An intermediate session begins when an `intermediate_session_token` is returned after a primary factor `authenticate()` call or a discovery authentication call.
It represents a temporary authentication session state of a user that hasn't been fully authenticated into an organization yet.
### Attributes
* **No organizational context**
Intermediate sessions aren't associated with any organization. A user must meet all primary and secondary authentication factors for an organization for the token to be successfully exchanged for a full Member Session with an organization.
* **Fixed intermediate session duration**
Intermediate session tokens are valid for **10 minutes**. If the token is not exchanged for a Member Session before expiration, the end user must restart the authentication process.
* **Using intermediate sessions**
Intermediate sessions are used during an authentication flow to facilitate step-up primary authentication, multi-factor authentication (MFA), and the Discovery login experience.
### Additional resources
***
## Full member session
A full member session is initiated after a successful authentication call that satisfies all of an Organization's authentication requirements.
This could be directly from an `authenticate()` call (e.g. [magic links](/docs/api-reference/b2b/api/email-magic-links/organization/authenticate-magic-link)) for an org-specific login, from an [intermediate session token exchange](/docs/api-reference/b2b/api/discovery/exchange-intermediate-session) after a Discovery flow, or from a secondary factor authentication call (e.g. [SMS OTP](/docs/api-reference/b2b/api/mfa/otp/authenticate-sms-otp)) for an organization that requires MFA.
These methods generate a newly minted [Member Session object](/docs/api-reference/b2b/api/sessions/session-object) in the response, containing useful attributes about the member's logged in state that can be stored as part of your application logic.
```json Response 200 expandable theme={null}
{
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"status_code": 200,
"member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"session_token": "mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q",
"session_jwt": "eyJ...",
"intermediate_session_token": "",
"member_authenticated": true,
"mfa_required": null,
"primary_required", null,
"member_session": {...},
"member": {...},
"organization": {...}
}
```
```json Object 200 expandable theme={null}
{
"member_session": {
"member_session_id": "session-test-fe6c042b-6286-479f-8a4f-b046a6c46509",
"member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"started_at": "2023-01-09T07:41:52Z",
"last_accessed_at": "2023-01-09T07:41:52Z",
"expires_at": "2021-08-10T07:41:52Z",
"authentication_factors": [
{
"delivery_method": "email",
"email_factor": {
"email_address": "sandbox@stytch.com",
"email_id": "email-test-81bf03a8-86e1-4d95-bd44-bb3495224953"
},
"last_authenticated_at": "2023-01-09T07:41:52Z",
"created_at": "2023-01-09T07:41:52Z",
"updated_at": "2023-01-09T07:41:52Z",
"sequence_order": "PRIMARY",
"type": "magic_link"
}
],
"custom_claims": {
"claim1": "value1",
"claim2": "value2"
},
"organization_id": "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
"roles": ["stytch_member", "editor"]
}
}
```
### Attributes
* **Associated to organizations**
A full session is **always** associated with a specific `organization_id`.
* **Configurable session duration**
The default duration of a session is 60 minutes, which can be configured in the call initiating a session via `session_duration_minutes`.
* **Identifying and using sessions**
Sessions are identified by a `session_token` and `session_jwt`, which are used to authenticate and authorize requests between your client and server.
### Additional resources
***
## Intermediate to full session lifecycle example
This illustrates an org-specific login sequence where the organization requires a secondary authentication factor (MFA):
```mermaid theme={null}
sequenceDiagram
participant U as User / Browser
participant A as Your app
participant S as Stytch
U->>S: Completes primary factor authentication
Note over U,S: Intermediate session begins
S-->>A: Returns intermediate_session_token (IST)
A->>U: Prompts for a MFA passcode
U->>A: Inputs code
A->>S: authenticate() - includes the IST
Note over U,S: Intermediate session exchanged for a full session
S-->>A: Member & Session info, session_token, and session_jwt
A-->>U: Session stored in cookies
```
# Using Stytch's session layer
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/overview
Sessions provide a way to uniquely identify users on your application and maintain useful state information, such as detecting whether a user is logged in or not, determining access privileges, personalizing experiences, and building user insights.
The session lifecycle through authentication to a full session.
## What is a session?
A session is a unique identifier for a member's authenticated state within an organization. It ties together the member's identity, authentication factors, and session metadata.
Once a member successfully authenticates, Stytch mints a Session object for the member—represented by two tokens:
### Session represented by tokens
Represents an authenticated session for a specific member.
An opaque token that's verified via API call.
A signed token that can be locally verified.
***
## Session management
Stytch's SDKs provide a session management layer that abstracts token rotation, session validation, and more:
Control your application behavior based on whether a member has an active session.
Validate session tokens or JWTs before any action that requires authorization.
Hydrate the frontend session state from the backend.
Enforce RBAC permissions in your application’s frontend and backend.
Encode additional information into your session JWTs using custom claims.
Options for cookie configuration options and HttpOnly cookies.
### Session lifecycle
Handle sessions during authentication flows that require intermediate steps.
Seamlessly switch a member's session between organizations.
Session duration and extending the lifetime of a session.
Immediately invalidate the session token, effectively logging the member out.
# Authenticate a session
Source: https://stytch.com/docs/multi-tenant-auth/manage-sessions/validate-session
How to validate session tokens and JWTs.
## Use case
* **Protect server-side endpoints:** Validate either the `session_token` or `session_jwt` on requests to your backend before proceeding with the request.
* **Backend authorization checks:** Enforce RBAC permissions and perform server-side user authorization checks before proceeding with the request.
***
## JWT vs. session token validation
Since `session_jwt` and `session_token` are stored in the site's cookies, the browser will include these cookies in the request headers to your backend if they share a domain.
Select between validating a JWT or session token:
Using JSON Web Tokens (JWT) over opaque session tokens **allows you to validate the token locally entirely within your backend service**, which is significantly faster than communicating with Stytch's API on every member session validation check.
### Reasons to use JWTs
* **Minimize latency:** JWTs can be locally validated to authenticate sessions without an API call (until it expires).
* **Authorize actions outside your app:** JWTs contain standard claims and information about the Member Session.
***
### Using the Stytch SDK
Upon initialization, the Stytch SDK client will automatically retrieve and cache your JSON Web Key Set (JWKS) for use in local JWT validation.
Persist the SDK client instead of reinitializing it between requests so your JWKS is only fetched once.
Handles the local JWT authentication and remote session authentication fallback.
**Recommended**
Always communicates with the Stytch API to authenticate a session.
```javascript Node.js icon="node-js" lines theme={null}
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.use(authenticateStytchSession);
const client = new stytch.B2BClient({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
const authenticateStytchSession = (req, res, next) => {
return client.sessions.authenticateJwt({ session_jwt: req.cookies['stytch_session_jwt'] })
.then(session => {
req.stytchSession = session;
return next();
})
.catch(next)
};
```
`authenticateJwt()` does not return the full [Member object](/docs/api-reference/b2b/api/members/member-object) when validated locally. If you need data from the Member object to complete additional authorization checks or any other additional action, use [custom claims](#) to add it to the JWT itself.
***
### Using custom backend JWT validation
Stytch JWTs are compatible with other standard JWT validation libraries. You'll need to:
* Implement logic to fall back to Stytch's [Authenticate Session](/docs/api-reference/b2b/api/sessions/authenticate-session) endpoint when the JWT is expired. You can refer to the [Authenticate JWT](/docs/api-reference/b2b/api/sessions/authenticate-jwt) method for what that logic might look like.
* Supply your validation library the Stytch [Get JWKS](/docs/api-reference/b2b/api/sessions/get-jwks) endpoint for [JWKS rotation](#json-web-key-set-jwks-rotation). Otherwise, your application should decide which JWKS to use for validation by inspecting the JWT and JWKS `kid` value.
- [jose](https://www.npmjs.com/package/jose) (Node.js)
- [jwt](https://pkg.go.dev/github.com/golang-jwt/jwt) (Go)
- [Hasura](https://hasura.io/docs/2.0/auth/authentication/jwt/)
- [NGINX](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-jwt-authentication/)
- [Apollo](https://www.apollographql.com/docs/router/configuration/authn-jwt/)
- [php-jwt](https://packagist.org/packages/firebase/php-jwt)
We strongly recommend using our [backend SDKs](/docs/api-reference/b2b/api/sdks) (available in Python, Node, Ruby, Go, and Java) for JWT validation. Our libraries handle the logic to authenticate JWTs locally and automatically fall back to the Stytch API when the JWT is expired.
***
### JSON Web Key Set (JWKS) rotation
Stytch automatically rotates your JWKS every 6 months for security purposes. Newly minted JWTs will be signed using the new key set. **Both key sets** will be returned by the [Get JWKS](/docs/api-reference/b2b/api/sessions/get-jwks) endpoint for a 1 month grace period.
* \~1 month before your Project's old JWKS expires, a new JWKS will be automatically generated.
* When this happens, there will be a 5 minute period where some JWTs will be signed by the old JWKS and some by the new, due to the JWT set lifetime.
* Match the `kid` value of the JWT and JWKS to determine the correct JWKS to use for validation.
* Most JWT libraries have a mechanism to handle this automatically; if not, you may see errors like `"kid" invalid, unable to lookup correct key` (depending on the library and version you use).
Stytch's [backend SDKs](/docs/api-reference/b2b/api/sdks) handle JWKS rotation for you automatically.
Session tokens are considered opaque because they don't contain any information about the underlying Member Session.
### Reasons to use session tokens
* **Strict security requirements:** Session tokens cannot be locally validated. You must always make a Stytch API call to authenticate the token, which guarantees that the token is valid at that point in time. This provides stricter guarantees than JWTs, which have a 5-minute window where the underlying Member Session may have been revoked before the JWT was refreshed.
***
### Using the Stytch SDK
Always communicates with the Stytch API to authenticate a session.
```python lines theme={null}
def upgrade_member_to_coordinator():
try:
resp = client.sessions.authenticate(
session_token=session['stytch_session'],
custom_claims={
"role": "coordinator",
"read-scope": ["appointments", "deliveries"],
"write-scope": ["calendar"]
}
)
if resp.status != 200:
return handle_error(resp.status)
session['stytch_session'] = resp.session_token
return
except Exception as e:
return handle_exception(e)
```
# Multi-tenant authentication
Source: https://stytch.com/docs/multi-tenant-auth/overview
Use Stytch B2B API & SDKs to build multi-tenant auth for your app.
Quickstart to add login or signup to your application.
Sample apps showcasing how to authenticate users and manage sessions.
See reference documentation for the Stytch B2B API and SDKs.
## Enterprise-ready solutions
Authenticate users using enterprise identity provider credentials.
Out-of-the-box SCIM management to connect with workforce IdPs.
Flexible integrations with 3rd-party SSO and custom auth factors.
Assign roles and permissions to members directly or automatically.
Allow provisioning sources for members to automatically join orgs.
Give your customers control over their own auth policies.
Implement 2-factor verification for added security.
Prevent fraud and abuse with device intelligence.
Pre-built Admin Portal for org and member management.
## AI agent authentication
Enable AI agents to securely connect to your application.
Enable your application to become an OAuth and OIDC Authorization server.
# Integrate crypto wallets via the API
Source: https://stytch.com/docs/consumer-auth/authentication/crypto-wallets/api
Implement Ethereum crypto wallet authentication with the Consumer API.
Our [Crypto wallets](/docs/api-reference/consumer/api/crypto-wallets/authenticate-start) product unlocks Web3 authentication in your app in four easy steps. For this guide we'll walk through integrating [MetaMask](https://metamask.io/), but you may integrate any Ethereum based wallet with Stytch, we'll talk about this more later in the guide.
If you get stuck while working through this guide, feel free to ask questions via [support@stytch.com](mailto:support@stytch.com) or in our [Slack community](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw).
To start, we need the user's Ethereum address we want to authenticate with. We can use the following code snippet to fetch the user's MetaMask Ethereum address from the browser. You'll have to check if window\.ethereum is defined – if it isn't, the user doesn't have MetaMask installed.
```js theme={null}
const [address] = await window.ethereum.request({ method: 'eth_requestAccounts' });
```
Using the Ethereum address, make a request to `/v1/crypto_wallets/authenticate/start`. If you want to add this Ethereum address to an existing user, pass in that user's `user_id`. If the Ethereum address doesn't belong to a user yet, leaving out a `user_id` will create a new user automatically.
You can optionally use the Sign in With Ethereum (SIWE) protocol by passing in the `siwe_params` parameter. SIWE is recommended for Ethereum crypto wallet flows because it will automatically warn the end user if the domain passed in the message does not match the domain of the site making the signing request to the crypto wallet.
The only required parameters for SIWE are domain and uri. You can read more about each individual parameter in our [docs](/docs/api-reference/consumer/api/crypto-wallets/authenticate-start), and you can also check out the [full SIWE spec](https://eips.ethereum.org/EIPS/eip-4361).
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
}
);
const params = {
crypto_wallet_address: "0x6df2dB4Fb3DA35d241901Bd53367770BF03123f1",
crypto_wallet_type: "ethereum",
user_id: "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6", // optional
siwe_params: {
domain: "example.com",
uri: "https://example.com",
}, // optional
};
client.cryptoWallets.authenticateStart(params)
.then(resp => { console.log(resp) })
.catch(err => { console.log(err) });
```
Using the challenge field from the `/v1/crypto_wallets/authenticate/start` response, request the user to sign the message. The user will see a MetaMask popup asking to sign a message with the content from the challenge. You'll also have to gracefully handle the situation where a user chooses to reject the message.
```js theme={null}
// get challenge from authenticate/start call
const signature = await window.ethereum.request({"method": "personal_sign", "params": [challenge, address]})
```
Using the signature from the signMessage call described in the last step, make a request to `/v1/crypto_wallets/authenticate`. Optionally, you may provide an existing `session_token` to reuse an existing session, and/or provide a `session_duration_minutes` value to set the lifetime of the session.
Stytch will perform validation on the address, challenge, and signature and return a 400 if there are any issues. If the authentication is successful, we'll return a 200.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
}
);
const params = {
crypto_wallet_address: "0x6df2dB4Fb3DA35d241901Bd53367770BF03123f1",
crypto_wallet_type: "ethereum",
signature: "0x0c4f82edc3c818b6beff4b89e0682994e5878074609903cecdfb843241728be32f75949e2fbae63dcccdef97c0e3789a26441f7e11456cc1f2ef79b3a436010f1b"
};
client.cryptoWallets.authenticate(params)
.then(resp => { console.log(resp) })
.catch(err => { console.log(err) });
```
You just finished all the critical components to authenticate your users with a MetaMask wallet! Have any feedback after having integrated Ethereum with your app? Get in touch with us and tell us what you think by [contacting us](https://stytch.com/contact).
# Fraud & risk protections
Source: https://stytch.com/docs/consumer-auth/authentication/fraud-and-risk
Ways to protect your application using Device Fingerprinting and Protected Auth.
## Protected Auth
Ensure that only legitimate users can access your authentication flows.
Using Protected Auth to defend your signup and login flows.
### Modes
* **Enforcement:** React to Device Fingerprinting verdicts for your traffic.
* **Observation:** Only observe Device Fingerprinting verdicts.
### Protected methods
These authentication methods collect fingerprints and prevent actions when a fingerprint receives a `BLOCK` verdict:
* `biometrics.authenticate`
* `cryptoWallets.authenticate`
* `impersonation.authenticate`
* `magicLinks.email.loginOrCreate`
* `magicLinks.email.send`
* `otps.authenticate`
* `otps.email.loginOrCreate`
* `otps.sms.loginOrCreate`
* `otps.sms.send`
* `otps.whatsapp.loginOrCreate`
* `otps.whatsapp.send`
* `passwords.authenticate`
* `passwords.create`
* `passwords.resetByEmail`
* `passwords.resetByEmailStart`
* `passwords.resetByExistingPassword`
* `passwords.resetBySession`
* `totps.authenticate`
* `totps.recover`
* `webauthn.authenticate`
Protected Auth is a ready-made feature provided in the [frontend](/docs/api-reference/consumer/frontend-sdks/react/overview) and [mobile](/docs/api-reference/consumer/mobile-sdks/react-native/overview) SDKs.
***
## New device notifications
Alert your users if a login is detected from a new device, IP address, or location so they can take immediate action if needed:
Detect new devices to trigger user notifications.
***
## Additional ways to use Device Fingerprinting
Both features integrate Stytch's Device Fingerprinting, which offers a device intelligence layer that can be used to prevent fraud and risk or provide device information as an additional user identifier.
More examples on how to use Device Fingerprinting:
Add an invisible bot protection for a smooth user experience.
Stop fake account creation abuse.
Use IP-based geographic filters for access control.
Add a remembered device flow.
# Authenticate an M2M client
Source: https://stytch.com/docs/consumer-auth/authentication/m2m/authenticate-client
How to implement M2M authentication.
Stytch's M2M Authentication enables your services to communicate and authenticate without any human involvement. Use our M2M Auth API to create clients, retrieve access tokens, and define permissions with scopes to securely communicate between machines.
In this guide, you'll learn how to create and authenticate an M2M Client. By the end, you'll have:
* Created an M2M Client.
* Issued an `access_token`.
* Authenticated the M2M Client by validating the `access_token`.
* Deactivated or deleted an M2M Client.
## Before you start
In order to complete this guide, you'll need the following:
* A Stytch project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select **Consumer Authentication**.
* The `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the **Project ID & API keys** section of the Stytch Dashboard. You'll need to pass these values into the `Authorization` request header for most Stytch API calls.
## Authenticating an M2M client
An M2M (machine-to-machine) client represents a machine identity. To create one, call the [Create M2M Client endpoint](/docs/api-reference/consumer/api/m2m/client/create-m2m-client) and provide optional attributes like `client_name`, `client_description`, `scopes`, and `trusted_metadata` that describe the client use case.
If you prefer, you also have a UI option for creating M2M Clients in the [Dashboard](https://stytch.com/dashboard/m2m-clients). For the purposes of this guide, we'll be using the API.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
-d '{
"client_name": "Example client",
"client_description": "Following the Stytch guide.",
"scopes": ["read:settings", "write:settings"],
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
}'
```
After a successful API call, the response will look like this.
```javascript theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret": "FXvejQZKicBl7Lq...QqNU",
"client_secret_last_four": "QqNU",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
This is the only time you will be able to view the generated `client_secret` in the API response. Stytch stores a hash of the `client_secret` and cannot recover the value if lost. Be sure to persist the `client_secret` in a secure location. If the `client_secret` is lost, you will need to trigger a [secret rotation flow](/docs/consumer-auth/authentication/m2m/rotate-secrets) to receive another one.
After creating an M2M client, you'll need to issue and retrieve an access token.
Call the [Get Access Token endpoint](/docs/api-reference/consumer/api/m2m/token/get-access-token) and provide the following parameters in the request body and URL path:
* `project_id` from the Basic Auth header in **Step 1**
* `client_secret` from the response in **Step 1**
* `client_id` from the response in **Step 1**
* `grant_type` set to the value `"client_credentials"`
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/public/${projectId}/oauth2/token \
-H 'Content-Type: application/json' \
-d '{
"client_id": "m2m-client-test-a50053..",
"client_secret": "FXvejQZKicBl7Lq...QqNU",
"grant_type": "client_credentials"
}'
```
The response will contain the `access_token`, a JWT signed with your project's [JWKS](/docs/api-reference/consumer/api/sessions/get-jwks). The lifespan of the `access_token` is one hour.
```javascript theme={null}
{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600
}
```
At this point, the M2M Client you've created from **Step 1** will communicate with your services with the issued `access_token` from **Step 2**.
Your services will need to authenticate the `access_token` locally with one of Stytch's backend SDKs (available in Node, Python, Go, and Ruby). You can also alternatively use a library that supports JWT signature validation.
Using the backend Node SDK as an example, your services will call the [Authenticate Access Token method](/docs/api-reference/consumer/api/m2m/token/authenticate-access-token) with the provided `access_token`.
As an option for enforcing permissions, you can also specify `required_scopes` the `access_token` must contain in order to be valid: any missing scope will return an error.
```javascript theme={null}
const stytch = require('stytch');
const client = new stytch.Client({
project_id: '${projectId}',
secret: '${secret}',
});
const params = {
access_token: 'eyJ...',
required_scopes: ['write:settings'],
};
client.m2m
.authenticateToken(params)
.then((resp) => {
console.log(resp);
})
.catch((err) => {
console.log(err);
});
```
A successful response will contain the M2M Client's `client_id`, `scopes`, and `custom_claims` (Check out our [custom claims guide](/docs/consumer-auth/authentication/m2m/custom-claims) to learn how to pass customized claims to every issued JWT for M2M Clients.)
```javascript theme={null}
{
"client_id": "m2m-client-test-a50053....",
"scopes": ["read:settings","write:settings"],
"custom_claims": {}
}
```
At this point, the M2M Client has successfully been authenticated.
If the M2M Client needs to be deactivated, call the [Update M2M Client endpoint](/docs/api-reference/consumer/api/m2m/client/update-m2m-client) and set the `status` to `inactive`, which will prevent the M2M Client from receiving new access tokens.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/m2m-client-test-d73... \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
-d '{
"status": "inactive"
}'
```
M2M Clients can be reactivated by setting the `status` to `active`.
If the M2M Client needs to be deleted, call the [Delete M2M Client endpoint](/docs/api-reference/consumer/api/m2m/client/delete-m2m-client).
```sh theme={null}
curl --request DELETE \
--url https://test.stytch.com/v1/m2m/clients/m2m-client-test-d73... \
-u '${projectId}:${secret}'
```
Deactivating or deleting an M2M Client will not invalidate any existing JWTs issued to the client, only prevent it from receiving new ones.
## What's next
Keep your M2M Clients' identities secure and learn how to [rotate client secrets](/docs/consumer-auth/authentication/m2m/rotate-secrets).
Also, take a look at our comprehensive [M2M Authentication example app tutorial](https://stytch.com/blog/how-to-implement-m2m-authentication-and-authorization-in-node-js-using-stytch/), which includes an open-source [GitHub repo](https://github.com/stytchauth/stytch-m2m-node-example) utilizing the Node.js SDK.
# Adding custom claims to M2M tokens
Source: https://stytch.com/docs/consumer-auth/authentication/m2m/custom-claims
Add custom claims to M2M access tokens.
Custom claims allow you to encode additional information into your Stytch M2M access tokens, similar to session custom claims.
## How to use
First, pass in extra parameters to the [Get Access Token](/docs/api-reference/consumer/api/m2m/token/get-access-token) API endpoint. For example, to add a custom `user_id` claim:
```json theme={null}
{
"client_id": "m2m-client-test-cxxxxxx",
"client_secret": "xxxxxx",
"grant_type": "client_credentials",
"user_id": "123"
}
```
Most backend SDKs also support this via the `extra` param in the corresponding method.
Next, create an M2M custom claims template, either via [the Dashboard](https://stytch.com/dashboard/custom-claim-templates) or [the API](/docs/api-reference/pwa/api/v3/jwt-templates/set-jwt-template) and include this claim using the variable syntax `request.[claim]`:
```json theme={null}
{
"user_id": {{ request.user_id }}
}
```
You should see your custom `user_id` claim in newly minted M2M tokens.
# Import M2M clients from Auth0
Source: https://stytch.com/docs/consumer-auth/authentication/m2m/import-clients
Migrating M2M clients from other platforms like Auth0 to Stytch.
Stytch's M2M Authentication supports importing clients from other platforms like Auth0. Use Stytch to import pre-existing `client_id`s, `client_secret`s, and assigned `scopes` without any data transformations or downtime.
## Before you start
In order to complete this guide, you'll need the following:
* A Stytch project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select **Consumer Authentication**.
* The `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the **API keys** section of the Stytch Dashboard. You'll need to pass these values into the `Authorization` request header for most Stytch API calls.
## Importing M2M clients
First, access your existing client wherever it is stored. For this guide, we'll use Auth0 as an example.
On Auth0 and under **Applications**, select an already created M2M client.
You'll need to find and copy the client's values that correspond to the following Stytch M2M Client fields:
* `client_id`
* `client_secret`
In this guide, you'll use the Dashboard UI to do the import.
In the [Dashboard](https://stytch.com/dashboard), navigate to **M2M Clients** and then click the **Import** button. A popup modal will appear prompting you to enter the client ID and secret.
Enter the client's values you copied from **Step 1** and click **Import**.
After the import, you can update the M2M Client with additional metadata like `client_name` and `client_description`.
More importantly, you can transfer any previously defined scopes during this step. In Auth0, navigate to your client's permissions and record the scopes you want to transfer.
Then in Stytch, scroll down to the **Scopes** section of the newly imported M2M Client and click **+ Add scopes**. A popup modal will appear prompting you to type in the scopes and separate them by a single space.
Click **Save** when you've finished editing the M2M Client.
To confirm a successful import, issue an access token for the M2M Client and decode the JWT.
```javascript theme={null}
{
"aud": [
"${projectId}"
],
"exp": 1693492107,
"iat": 1693488507,
"iss": "stytch.com/${projectId}",
"nbf": 1693488507,
"scope": "read:users update:users",
"sub": "lJJsWpAmd42q0EEJHJoSAxTPzdgpzF0i"
}
```
The access token's payload and standard claims should show the following data:
* The `iss` and `sub` match or contain your `project_id`.
* The `scope` matches the scopes you entered in **Step 3**.
* The `sub` matches the `client_id` you imported from **Steps 1 and 2**.
If the criteria are met, your M2M Client has successfully been imported to Stytch.
# Rotate client secrets
Source: https://stytch.com/docs/consumer-auth/authentication/m2m/rotate-secrets
Managing M2M client secret rotation.
Stytch's M2M Authentication supports secret rotation to ensure security best practices with zero downtime. Regular rotation can prevent the prolonged misuse of any compromised credentials by updating the secrets for an M2M Client.
In this guide, you'll learn how to rotate secrets for an M2M Client. By the end, you'll have:
* Started a secret rotation.
* Completed a secret rotation.
* Canceled a secret rotation.
## Before you start
In order to complete this guide, you'll need the following:
* A Stytch project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select **Consumer Authentication**.
* The `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the **Project ID & API keys** section of the Stytch Dashboard. You'll need to pass these values into the `Authorization` request header for most Stytch API calls.
* An active M2M Client. If you haven't created one yet, you can follow the [Authenticate an M2M Client guide](/docs/consumer-auth/authentication/m2m/authenticate-client).
## Rotating client secrets
If you completed the [Authenticate an M2M Client guide](/docs/consumer-auth/authentication/m2m/authenticate-client), you should have an active M2M Client. To confirm, call the [Get M2M Client endpoint](/docs/api-reference/consumer/api/m2m/client/get-m2m-client) with its `client_id`.
```sh theme={null}
curl --request GET \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID} \
-u '${projectId}:${secret}'
```
The response should look similar to this:
```javascript theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "QqNU",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
The important field to evaluate is `next_client_secret_last_four`. If the value is `null`, proceed to the next step. If the field is already populated, the client is already in a secret rotation flow. Create a new M2M Client instead.
Take the `client_id` of your active M2M Client and call the [Start Secret Rotation endpoint](/docs/api-reference/consumer/api/m2m/rotate-secret/start-secret-rotation).
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID}/secrets/rotate/start \
-u '${projectId}:${secret}'
```
```javascript theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "QqNU",
"next_client_secret":"NHQhc7ZqsXJ...DzJj",
"next_client_secret_last_four": "DzJj",
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
After a successful API call, the response will return the M2M Client with two newly populated fields: `next_client_secret` and `next_client_secret_last_four`.
This is the only time you will be able to view the generated `next_client_secret` in the API response. Stytch stores a hash of the `next_client_secret` and cannot recover the value if lost. Be sure to persist the `next_client_secret` in a secure location. If the `next_client_secret` is lost, you will need to cancel the secret rotation (Step 4) and start the rotation all over.
At this point, the active M2M Client has **two** valid secrets associated with it. Both the `client_secret` and `next_client_secret` can be used to issue access tokens.
This is an opportune time to update your secret store to use the `next_client_secret` and replace the `client_secret` for your existing services.
Once updated with `next_client_secret`, your services can now retire the `client_secret` by completing the secret rotation.
Call the [Rotate Secret endpoint](/docs/api-reference/consumer/api/m2m/rotate-secret/rotate-secret) with the `client_id`.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID}/secrets/rotate \
-u '{PROEJCT_ID}:{SECRET}'
```
```javascript theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "DzJj",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
A successful API call completes the secret rotation and results in the following updates:
* The M2M Client's `client_secret` from **Steps 1 and 2** will be invalidated.
* The M2M Client's `client_secret` from **Steps 1 and 2** will be replaced by the `next_client_secret`. To confirm, check the M2M Client's `client_secret_last_four` updated value.
* The M2M Client's `next_client_secret_last_four` will be set back to `null`.
If for any reason after **Step 2** you need to stop or revert the secret rotation process, call the [Cancel Secret Rotation endpoint](/docs/api-reference/consumer/api/m2m/rotate-secret/cancel-secret-rotation) with the `client_id`.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/m2m/clients/{CLIENT_ID}/secrets/rotate/cancel \
-u '{PROEJCT_ID}:{SECRET}'
```
```javascript theme={null}
{
"m2m_client": {
"client_description": "Following the Stytch guide.",
"client_id": "m2m-client-test-a50053...",
"client_name": "Example client",
"client_secret_last_four": "DzJj",
"next_client_secret_last_four": null,
"scopes": [
"read:settings",
"update:settings"
],
"status": "active",
"trusted_metadata": {
"billing_tier": "standard",
"api_version": "v2"
}
},
"request_id": "request-id-test-c50eb06f-69b4-41be-8640-0d9fd2f561ae",
"status_code": 200
}
```
A successful API call cancels the secret rotation and results in the following updates:
* The M2M Client's `client_secret` from **Steps 1 and 2** will not be updated and will remain valid.
* The M2M Client's `next_client_secret` and `next_client_secret_last_four` will be invalidated and set back to `null`.
## What's next
Learn how to [import M2M Clients from other platforms like Auth0](/docs/consumer-auth/authentication/m2m/import-clients).
Also, take a look at our comprehensive [M2M Authentication example app tutorial](https://stytch.com/blog/how-to-implement-m2m-authentication-and-authorization-in-node-js-using-stytch/), which includes an open-source [GitHub repo](https://github.com/stytchauth/stytch-m2m-node-example) utilizing the Node.js SDK.
# Adding magic links to a custom auth flow
Source: https://stytch.com/docs/consumer-auth/authentication/magic-links/adding-magic-links-to-custom-auth-flow
Integrate with Stytch’s headless or backend SDKs for complete control over your authentication flow.
## Overview
Managing user login with Stytch enables you to authenticate users on an ongoing basis via one-click, passwordless login. When a user signs up for your app, you'll add them to Stytch. Then, whenever a user goes to login to your app, authenticate them via a magic link.
## Implementation
This guide walks through integrating magic links using Stytch’s backend SDKs. This approach handles all authentication logic on your backend, giving you complete control over the user experience.
If you'd like to follow our code in this guide, you'll need to install stytch first.
```bash theme={null}
npm install stytch
```
If you don't already have one, you'll need to build a sign up or login view for your app. You can collect any information you want, but you'll need to include an email field so the user can login with their email later.
Create a route that will act as your magic link endpoint. Users will be directed there from the login email. The route should accept a token as a query parameter that you will use to authenticate. For example, your route should be something like [https://example.com/authenticate](https://example.com/authenticate). Users will be directed to [https://example.com/authenticate?token=abc](https://example.com/authenticate?token=abc), where abc is the token you'll pass to our authentication endpoint.
Add the login and signup URLs to the project's list of predefined redirect URLs in the [Dashboard](https://stytch.com/dashboard/redirect-urls). For more information on why this step is necessary, please check out the documentation [here](/docs/api-reference/consumer/api/resources/url-validation).
By default, all redirect URLs are set to [http://localhost:3000](http://localhost:3000) for Test environments.
Send either a login or create magic link to the user based on if the email is associated with a user already. If the user was created, use the `user_id` returned in the response to manage that user within Stytch. Save this `user_id` with your user's record in your app's storage.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET"
});
const loginOrCreateWithMagicLink = async () => {
const params = {
email: "sandbox@stytch.com",
login_magic_link_url: "https://example.com/authenticate",
signup_magic_link_url: "https://example.com/authenticate",
};
const response = await client.magicLinks.email.loginOrCreate(params);
console.log(response);
};
loginOrCreateWithMagicLink().then().catch(err => console.log(err));
```
Once the user clicks the magic link from the email and is directed to either the login\_magic\_link\_url or signup\_magic\_link\_url, you'll use the token from the URL to call the authenticate endpoint to log the user in. You'll need to manage the user session from your app.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET"
});
const authenticateMagicLink = async (token) => {
const params = {
token,
session_duration_minutes: 60
};
const response = await client.magicLinks.authenticate(token);
console.log(response);
};
// Replace with token from request
const token = "SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4="
authenticateMagicLink(token).then().catch(err => console.log(err));
```
# Adding magic links to Stytch Login
Source: https://stytch.com/docs/consumer-auth/authentication/magic-links/adding-magic-links-to-stytch-login
Integrate magic links with Stytch's pre-built UI components.
This guide shows how to enable Email Magic Links in Stytch Login, then authenticate Magic Link tokens on your backend. For broader SDK details, see the [frontend SDK docs](/docs/api-reference/consumer/frontend-sdks/vanilla-js/overview).
1. Create **Login** and **Sign-up** redirect URLs on the [redirect URL configuration page](https://stytch.com/dashboard/redirect-urls).
2. In [Frontend SDK](https://stytch.com/dashboard/sdk-configuration), authorize your domain and enable **Email magic links** → **Enable the LoginOrCreate Flow**.
3. From [Project ID & API keys](https://stytch.com/dashboard), copy your `public_token`.
Install the frontend package:
```bash theme={null}
npm install @stytch/react --save
```
```js theme={null}
import { createStytchClient, StytchProvider } from '@stytch/react';
const stytch = createStytchClient('PUBLIC_TOKEN');
const root = ReactDOM.createRoot(document.getElementById("root")!);
root.render(
,
);
```
```js theme={null}
import { Products, StytchLogin } from '@stytch/react';
const config = {
emailMagicLinksOptions: {
loginExpirationMinutes: 30,
loginRedirectURL: 'https://example.com/authenticate',
signupExpirationMinutes: 30,
signupRedirectURL: 'https://example.com/authenticate',
},
products: [Products.emailMagicLinks],
};
export const Login = () => {
return ;
};
```
```js theme={null}
const presentation = { // [!code ++:3]
theme: { 'font-family': '"Helvetica Neue", Helvetica, sans-serif' },
};
export const Login = () => {
return (
);
};
```
Install the backend SDK:
```bash theme={null}
npm install stytch --save
```
When a user logs in for the first time, the SDK creates a Stytch User and returns a `user_id`. Save it in your user store.
```js theme={null}
app.post('/users', function (req, res) {
var stytchUserId = req.body.userId;
// TODO: Save stytchUserId with your user record in your app's storage
res.send(`Created user with stytchUserId: ${stytchUserId}`);
});
```
Handle Magic Link authentication on a route that receives the `token` query param:
```js theme={null}
const stytch = require('stytch');
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test
});
app.get('/authenticate', function (req, res) {
var token = req.query.token;
client.magicLinks.authenticate(token)
.then(response => {
req.session.authenticated = true;
req.session.save(function(err) {
console.log(err);
});
res.redirect('/home')
})
.catch(error => {
console.log(error);
res.send('There was an error authenticating the user.');
});
});
```
If authentication is successful, create a user session and log them into your app. To keep users logged in longer, set `session_duration_minutes`. See the [session management guide](/docs/consumer-auth/manage-sessions/overview).
# Adding PKCE to a magic link flow
Source: https://stytch.com/docs/consumer-auth/authentication/magic-links/adding-pkce
Add PKCE to magic link redirects to protect against bot traffic and replay.
## What is PKCE?
[Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636) is a specification built on OAuth 2.0 that was introduced to create a more secure login flow. We've adapted PKCE to work on magic link flows as well - increasing their security while preserving their usability.
You can read more about PKCE's role in OAuth in our [PKCE OAuth guide](/docs/consumer-auth/authentication/oauth/adding-pkce).
PKCE makes magic links more secure by cryptographically validating that the login flow starts and ends on the same device. In a standard magic link flow, the link contains a one time code in a redirect URL. The user clicks on the link, the app receives the code, and the code is exchanged for a session to log in the user. This setup means that an attacker would be able to impersonate a user if the attacker gained access to the one-time code. Codes in a URL can be leaked in many different ways - either through improper logging, HTTP Referrer headers, or even through another exploit like XSS.
PKCE improves on this by introducing a one-time secret for each authorization flow. This secret, called a `code_verifier`, is generated and stored by the app on the user's device. The verifier then is hashed to produce a `code_challenge`. This challenge is passed along with the user's email to Stytch when the user is asked to verify their identity.
Stytch sends the user an email containing a magic link. When the user clicks on that link, the user is redirected back to the app with a secure code. Next, the app submits the code along with the original `code_verifier`. Stytch hashes the submitted `code_verifier` and validates that it matches the `code_challenge` passed as a query parameter earlier. This check ensures that the token has not been leaked, and that it is being handled by the same user that was seen at the start of the flow.
This guide will show you how to add PKCE support to your Stytch magic link integration for added security. A PKCE integrations is required for applications that use native callback URLs, for example `appname://some/callback` on mobile. Stytch's support for PKCE differs slightly from the original specification - for example we do not permit plain verifiers to be used - but the original idea of strengthening app security with a unique secret on every request remains the same.
## Adding PKCE to your magic link flow
If you get stuck while working through this guide, feel free to ask questions by [contacting us](/docs/resources/get-support).
PKCE provides cryptographic assurance that a login flow starts and ends on the same device. While this is almost always a desired property, there is one notable exception to the rule: mobile webviews. Consider the following scenario:
1. A user sees a link to your application embedded in another mobile application, such as Twitter or Slack.
2. The user clicks the link and the application is opened inside a webview owned by the parent application.
3. The user is logged out by default, because cookies and session state are not shared between webviews and the native mobile browser.
4. The user attempts to log in via an email magic link. The PKCE code\_verifier is generated, and stored inside the webview.
5. The user leaves the initial webview and opens their email application. They click the magic link in the email, and your application is opened again, this time in the native mobile browser.
6. Your application attempts to submit the magic link token, but does not have access to the original code\_verifier. The user fails to log in with a pkce\_expected\_code\_verifier error, and needs to request a new magic link from the native mobile browser. Depending on your application's user base, mobile webviews could make up for a significant amount of traffic, and PKCE would add friction to that user experience. For cases like these, you might choose to omit PKCE and supplement your login flow with a second factor like [SMS](/docs/consumer-auth/authentication/otps/api) or [WebAuthn](/docs/consumer-auth/authentication/webauthn/api) instead.
If your application caters to desktop users, native apps, or is not shared on social media, you can implement PKCE with confidence.
First, enable PKCE for the JavaScript SDK. In order to do so, toggle the option in the [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration) of the Stytch Dashboard. Once this configuration is set, the SDK will automatically start applying PKCE to all magic link login flows it creates.
Call the [magicLinks.email.loginOrCreate()](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/email-magic-links/login-or-create) method to start the magic link flow. The SDK will generate a code\_verifier and store it in local storage before asking Stytch to send the email. If you use the SDK's built in [Magic Link UI](/docs/api-reference/consumer/frontend-sdks/vanilla-js/prebuilt-ui/login/configuration) this step is already handled for you.
```js theme={null}
function SendMagicLink({ email }) {
const stytch = useStytch();
const startMagicLinkFlow = useCallback(
() => stytch.magicLinks.email.loginOrCreate(email),
[stytch]);
return (
);
}
```
At the route that the user is redirected to after the Magic Link flow, pull the token out of the URL params and call [magicLinks.authenticate(token)](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/email-magic-links/authenticate) to exchange the token for a Stytch session. The SDK will automatically retrieve the code\_verifier created earlier from local storage and pass it on to the Stytch servers.
```js theme={null}
function MagicLinkCallback() {
const stytch = useStytch();
useEffect(() => {
stytch.authenticateByUrl({
session_duration_minutes: 60,
}).then((result) => {
if (result?.handled) {
// TODO: Redirect to final logged-in view
} else {
// TODO: Redirect back to logged-out view
}
})
}, [stytch]);
return (
Authenticating....
);
}
```
You just finished all the critical components to authenticate your users via magic links with PKCE! Have any feedback after integrating? Get in touch with us and tell us what you think by [contacting us](https://stytch.com/contact).
# Embeddable magic links
Source: https://stytch.com/docs/consumer-auth/authentication/magic-links/embeddable-magic-links
Embed magic links into your own communication channels.
Be sure to check out the important usage notes [here](/docs/api-reference/consumer/api/magic-links/embeddable/create) before using Embeddable Magic Links.
With our out-of-the-box Email Magic Links product, Stytch is able to fully handle the frustrating and complex pieces of email deliverability, latency, and inbox placement. However, we also offer a flexible way for you to embed magic links into any use case you can imagine. For example, you could send magic links from your own domain, embed them into customized templates, or leverage different communication channels, like SMS, to send them. By using our [Magic Link Create endpoint](/docs/api-reference/consumer/api/magic-links/embeddable/create), you can embed magic links into all of your end-user communications to reduce friction and improve conversion. If needed, you can always add more authentication requirements to a user's session to improve security by using the rest of Stytch's product suite.
Use the magicLinkCreate endpoint to generate a token for a user to embed in your chosen communication (e.g. a promotional email).
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
}
);
const params = {
user_id: "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6"
};
client.magicLinks.create(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
Send your customer communication with the magic link embedded in your main call-to-action button (you'll append the magic link token to the destination URL you're sending the user to).
When the user clicks the link, redirect the user and send a request to Stytch's authenticate endpoint in order to verify the user. You can optionally set a session for the user by using the session\_duration\_minutes in the authenticate call.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
}
);
// Replace with token from request
const token = "SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4="
client.magicLinks.authenticate(token)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
After this step, it's up to you on how you want to handle the user interaction. Depending on the use case, you may not need additional authentication. However, if you want to layer on step-up authentication at any point throughout the user's session for additional assurance, you can always layer on more verification methods (e.g. SMS, etc.).
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
}
);
const params = {
phone_number: "+12025550162"
};
client.otps.sms.send(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
# Email magic links
Source: https://stytch.com/docs/consumer-auth/authentication/magic-links/overview
Learn about email magic links in Consumer authentication.
## API resources
Use the Send Magic Link API to start a magic link login flow.
Use Stytch's Frontend SDKs to start a magic link login flow.
Use Stytch's pre-built UI components to seamlessly add email magic links to your login flow.
Use the Embeddable Magic Link API to start a magic link login flow by generating a magic link.
## How it works
Email magic links are a secure, seamless, passwordless authentication option.
When a user logs in via Stytch's Email Magic Link (EML) product, Stytch generates a unique, one-time-use token embedded in a URL and sends it to the user's email address. The user authenticates their identity by successfully receiving and clicking on this link before the link expires, at which point Stytch will verify the token and issue a Session for the User.
With magic links, you can choose to allow login, signup, or login-or-create behavior.\
Magic links require [redirect URLs](/docs/resources/workspace-management/redirect-urls) to be configured in your Dashboard.
## Email template customization
Email templates control the subject line and body of the email a User receives. For Magic Link endpoints that send emails, there are three possible email templates a User can receive: **login**, **signup** or **invite**. Which email template a User receives is based on a combination of the User's state (e.g. `active`, `pending`) in the Stytch backend and whether the magic link is initiated via the send, login-or-create, or invite method.
Styling customizations like color and font are available by default, while full HTML customization is available to customers paying for our branding add-on. Learn more about customizing email templates [here](/docs/resources/branding/custom-email-templates).
## Security
Stytch's email magic links product has built-in protections that ensure seamless delivery of magic links in corporate environments without compromising on security.
### One-time use restrictions
Each Magic Link that is sent to the user is one-time use. Once the user clicks on the Magic Link and logs in, the embedded token that uniquely identifies the authentication request is “consumed” and cannot be used for subsequent logins. This is critical for security as it significantly reduces the chances of unauthorized access. Once the user has completed the authentication flow the token cannot be stolen or replayed by an attacker to gain access.
However, in corporate environments, where email security scanners are very prevalent, this security measure can sometimes make Email Magic Links unusable.
### Email security scanners
Email security scanners are tools that scan incoming emails for malicious content, such as suspicious links or attachments, in an effort to protect the user from phishing attacks or malware. Most security scanners will inspect and click on any links present in emails, in order to identify suspicious redirects, attempts to execute malicious scripts or download malware, or signs of a phishing attempt.
When the link in question is actually an email magic link, the scanner will end up consuming the token before the email actually makes its way into the user's inbox, preventing them from being able to use the link to login.
### Stytch's built-in solution
In order to not compromise on the security of our EML product, while accounting for the prevalence of email security scanners, particularly in corporate inboxes, Stytch leverages our device intelligence product to handle “clicks” differently depending on if it is a real human user, or an email security scanner.
When we identify that the Magic Link has been clicked by an email security scanner, we do not treat the click as an authentication attempt - meaning the token is not consumed, and a session is not granted. The security scanner is able to inspect the link and identify that it is not malicious, without interfering with the one-time-use restriction. Once the scan is complete and the email is passed through to the user's inbox, they are able to click on the link and successfully log in.
This protection comes out of the box for all Stytch customers, but if you are interested in learning more about other use cases of Stytch's device intelligence product (such as bot-protection on your login page, or identifying and blocking fraudulent real users) [contact sales](https://stytch.com/contact/sales?vertical=FRAUD).
# MFA backend integration
Source: https://stytch.com/docs/consumer-auth/authentication/mfa/adding-mfa-to-custom-auth-flow
Integrate MFA with Stytch's backend SDKs for complete control over your authentication flow.
In this guide, you'll learn how to create an MFA flow and enforce MFA in your application using the Stytch API or our backend SDKs.
First, you'll need to prompt users to complete their first method of authentication. For the purposes of this guide, we'll use Passwords as our primary authentication factor.
We'll also assume that the user already has an account with a password and a [verified email address](/docs/consumer-auth/authentication/passwords/email-verification/overview). If the user's email address has not yet been verified, you will receive a `too_many_unverified_factors` error upon adding a new phone number.
Create a UI to prompt the user for their email address and password. Upon submission, call the [Authenticate password endpoint](/docs/api-reference/consumer/api/passwords/authenticate). Include a `session_duration_minutes` parameter so that a new Stytch Session is created:
```js theme={null}
const stytch = require('stytch');
const stytchClient = new stytch.Client({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
// In minutes, accepted range of 5 to 527040 minutes (366 days)
const DESIRED_SESSION_LENGTH = 120;
const params = {
email: 'sandbox@stytch.com',
password: 'PASSWORD',
session_duration_minutes: DESIRED_SESSION_LENGTH
};
stytchClient.passwords.authenticate(params)
.then(resp => {
// Stytch Session object
const session = resp.session;
// Save the session_token for use in Step 2
const session_token = resp.session_token;
})
.catch(err => { console.log(err) });
```
In the Authenticate password response, you'll receive a `session` object with details about the user's new Stytch Session. Within the `session` object, you'll see an `authentication_factors` array with a `password` factor in it. Note that there is only one factor present in the `authentication_factors` array right now, indicating that the user has only authenticated their primary factor and has not yet completed MFA.
```js theme={null}
{
...
"session": {
...
"authentication_factors": [
{
"created_at": "2024-10-30T16:31:13Z",
"delivery_method": "knowledge",
"last_authenticated_at": "2024-10-30T16:31:13Z",
"type": "password",
"updated_at": "2024-10-30T16:31:13Z"
}
],
...
},
"session_token": "...",
"session_jwt": "...",
...
}
```
The Authenticate password response will also contain a `session_token` and a `session_jwt`. Save the `session_token` for use in **Step 2**.
In this guide, we'll use SMS OTP as our secondary factor.
Passwords and SMS OTP are a good combination of factors, because they confirm your user's identity in two different ways. Note that Passwords and an email-based authentication method are **not** a secure combination of factors, given that a user's password can be reset if a bad actor has access to the user's email inbox.
Create a UI for the user to enter their phone number if it's not already present on their Stytch User, and send the user an OTP code via the [Send OTP by SMS endpoint](/docs/api-reference/consumer/api/otp/via-sms/send). Include the `session_token` from **Step 1** in your request:
```js theme={null}
const params = {
phone_number: 'USER_PHONE_NUMBER',
session_token: 'SESSION_TOKEN_FROM_STEP_1'
};
stytchClient.otps.sms.send(params)
.then(resp => {
// Save the phone_id for use in the Authenticate OTP call
const method_id = resp.phone_id;
})
.catch(err => { console.log(err) });
```
Next, surface a UI for the user to input the OTP code that they receive, and call the [Authenticate OTP endpoint](/docs/api-reference/consumer/api/otp/authenticate) with the `phone_id` from the `stytchClient.otps.sms.send` response, the OTP code, and the `session_token` from **Step 1**:
```js theme={null}
const params = {
method_id: "PHONE_ID_FROM_SEND_OTP_RESPONSE",
code: "OTP_CODE",
session_token: "SESSION_TOKEN_FROM_STEP_1"
};
stytchClient.otps.authenticate(params)
.then(resp => {
// New Stytch Session object
const session = resp.session;
// New session token; update the session token stored on the frontend
const session_token = resp.session_token;
// New session JWT; update the session JWT stored on the frontend if you use JWTs
const session_jwt = resp.session_jwt;
})
.catch(err => { console.log(err) });
```
The new `session` object's `authentication_factors` array will now contain both a `password` factor and a new `otp` factor:
```js theme={null}
"session": {
...
"authentication_factors": [
{
"created_at": "2024-10-30T16:31:13Z",
"delivery_method": "knowledge",
"last_authenticated_at": "2024-10-30T16:31:13Z",
"type": "password",
"updated_at": "2024-10-30T16:31:13Z"
},
{
"created_at": "2024-10-30T16:32:11Z",
"delivery_method": "sms",
"last_authenticated_at": "2024-10-30T16:32:11Z",
"phone_number_factor": {
"phone_id": "phone-number-...",
"phone_number": "+15555555555"
},
"type": "otp",
"updated_at": "2024-10-30T16:32:11Z"
}
],
...
},
```
Remember to update any `session_token` and `session_jwt` values stored on the frontend to the new ones returned in the Authenticate OTP response. For security purposes, the original session will be revoked shortly after the user authenticates their second factor.
In order to enforce that users complete MFA before accessing protected content, you'll need to inspect the `authentication_factors` array after [authenticating the Stytch Session](/docs/api-reference/consumer/api/sessions/authenticate-session) in your application's authorization logic and make sure that the required factors are present. Below is a simplified example:
```js theme={null}
export const authorizeRequest = (session_token) => {
stytchClient.sessions.authenticate({session_token})
.then(resp => {
const authentication_factors = resp.session.authentication_factors;
if (
session.authentication_factors.length === 2 &&
session.authentication_factors.find((i) => i.delivery_method === 'knowledge') &&
session.authentication_factors.find((i) => i.delivery_method === 'sms')
) {
// User has completed MFA; can proceed with request
} else {
// User has not completed MFA; send to MFA flow
}
})
.catch(err => {
// Session could not be successfully authenticated; send to login flow
});
}
```
You may also choose to implement step-up authentication, where you prompt users to authenticate a second factor before allowing them to take particularly sensitive actions.
When using step-up authentication, we generally recommend inspecting the `last_authenticated_at` value on each authentication factor and prompting the user to reauthenticate their secondary factor if too much time has passed since they last did so:
```js theme={null}
export const authorizeSensitiveRequest = (session_token) => {
stytchClient.sessions.authenticate({session_token})
.then(resp => {
const authentication_factors = resp.session.authentication_factors;
if (
authentication_factors.length === 2 &&
authentication_factors.find((i) => i.delivery_method === 'knowledge') &&
authentication_factors.find((i) => (
i.delivery_method === 'sms' &&
// Check if SMS OTP was completed within the last 5 minutes
Date.now() - new Date(i.last_authenticated_at).getTime() < 5 * 60 * 1000
))
) {
// User has completed SMS OTP flow within the past 5 minutes; can proceed with request
} else {
// User has not completed SMS OTP flow within the past 5 minutes; deny request and trigger 2FA flow
}
})
.catch(err => {
// Session could not be successfully authenticated; send to login flow
});
}
```
## What's next?
You can now customize your MFA setup to use the primary and secondary factors of your choice using the same general strategy outlined above.
Check out the SMS MFA section of our Stytch Demo app for an example of Consumer MFA in practice:
* [Hosted demo](https://www.stytchdemo.com/recipes/smsmfa)
* [Source code](https://github.com/stytchauth/stytch-nextjs-integration/tree/main/pages/recipes/api-sms-mfa)
# MFA frontend integration
Source: https://stytch.com/docs/consumer-auth/authentication/mfa/adding-mfa-to-stytch-login
Integrate MFA with Stytch's pre-built UI components or headless JavaScript SDK.
In this guide, you'll learn how to create an MFA flow and enforce MFA in your application using Stytch's frontend JavaScript SDK. If you'd like to implement MFA, you'll need to use the frontend SDK's headless methods with your own UI, rather than the pre-built UI components.
First, you'll need to prompt users to complete their first method of authentication. For the purposes of this guide, we'll use Passwords as our primary authentication factor.
We'll also assume that the user already has an account with a password and a [verified email address](/docs/consumer-auth/authentication/passwords/email-verification/overview). If the user's email address has not yet been verified, you will receive a `too_many_unverified_factors `error upon adding a new phone number.
Create a UI to prompt the user for their email address and password. Upon submission, call the [Authenticate password method](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/passwords/authenticate):
```js theme={null}
import { StytchClient } from '@stytch/vanilla-js';
const stytchClient = new StytchClient('PUBLIC_TOKEN');
// In minutes, accepted range of 5 to 527040 minutes (366 days)
const DESIRED_SESSION_LENGTH = 120;
export const authenticatePassword = () => {
stytchClient.passwords.authenticate({
email: 'sandbox@stytch.com',
password: 'PASSWORD',
session_duration_minutes: DESIRED_SESSION_LENGTH
})
.then(resp => {
// Stytch Session object
const session = resp.session;
})
.catch(err => { console.log(err) });
};
```
In the Authenticate password response, you'll receive a `session` object with details about the user's new Stytch Session. Within the session object, you'll see an `authentication_factors` array with a `password` factor in it. Note that there is only one factor present in the `authentication_factors` array, indicating that the user has not yet completed MFA.
```js theme={null}
"session": {
...
"authentication_factors": [
{
"created_at": "2024-10-30T16:31:13Z",
"delivery_method": "knowledge",
"last_authenticated_at": "2024-10-30T16:31:13Z",
"type": "password",
"updated_at": "2024-10-30T16:31:13Z"
}
],
...
}
```
In this guide, we'll use SMS OTP as our secondary factor.
Passwords and SMS OTP are a good combination of factors, because they confirm your user's identity in two different ways. Note that Passwords and an email-based authentication method are **not** a secure combination of factors, given that a user's password can be reset if a bad actor has access to the user's email inbox.
Create a UI for the user to enter their phone number if it's not already present on their Stytch User, and send the user an OTP code via the [Send OTP by SMS method](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/otps/send-via-sms):
```js theme={null}
export const sendPasscode = () => {
stytchClient.otps.sms.send('+15555555555')
.then(resp => {
// Save the method_id for use in the Authenticate OTP call
const method_id = resp.method_id;
})
.catch(err => { console.log(err) });
};
```
Next, surface a UI for the user to input the OTP code that they receive, and call the [Authenticate OTP endpoint](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/otps/authenticate) with the `method_id` from the `stytchClient.otps.sms.send` response and the OTP code:
```js theme={null}
export const authenticate = () => {
stytchClient.otps.authenticate(
'OTP_CODE',
'METHOD_ID_FROM_SEND_OTP_RESPONSE'
)
.then(resp => {
// Stytch Session object
const session = resp.session;
})
.catch(err => { console.log(err) });
};
```
The new `session` object's `authentication_factors` array will now contain both a `password` factor and a new `otp` factor:
```js theme={null}
"session": {
...
"authentication_factors": [
{
"created_at": "2024-10-30T16:31:13Z",
"delivery_method": "knowledge",
"last_authenticated_at": "2024-10-30T16:31:13Z",
"type": "password",
"updated_at": "2024-10-30T16:31:13Z"
},
{
"created_at": "2024-10-30T16:32:11Z",
"delivery_method": "sms",
"last_authenticated_at": "2024-10-30T16:32:11Z",
"phone_number_factor": {
"phone_id": "phone-number-...",
"phone_number": "+15555555555"
},
"type": "otp",
"updated_at": "2024-10-30T16:32:11Z"
}
],
...
},
```
If you authenticate either factor on the backend, make sure to hydrate the frontend SDK with the updated session data by passing the newest `session_token` into the frontend [Update session method](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/sessions/update-session), followed by a call to the frontend [Authenticate session method](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/sessions/authenticate-session). For security purposes, the original session will be revoked shortly after the user authenticates their second factor.
In order to enforce that users complete MFA before accessing protected content, you'll need to inspect the `authentication_factors` array after [authenticating the Stytch Session](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/sessions/authenticate-session) in your application's backend authorization logic and make sure that the required factors are present. Below is a simplified example of a backend authorization endpoint:
```js theme={null}
const stytch = require('stytch');
const stytchClient = new stytch.Client({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
export const authorizeRequest = (session_token) => {
stytchClient.sessions.authenticate({session_token})
.then(resp => {
const authentication_factors = resp.session.authentication_factors;
if (
session.authentication_factors.length === 2 &&
session.authentication_factors.find((i) => i.delivery_method === 'knowledge') &&
session.authentication_factors.find((i) => i.delivery_method === 'sms')
) {
// User has completed MFA; can proceed with request
} else {
// User has not completed MFA; send to login flow
}
})
.catch(err => {
// Session could not be successfully authenticated; send to login flow
});
}
```
You can also gate frontend content based on the Session's authentication factors in order to achieve your desired UI. Below is a simplified example:
```javascript theme={null}
import { StytchClient } from '@stytch/vanilla-js';
const stytchClient = new StytchClient('PUBLIC_TOKEN');
let session = stytchClient.session.getSync();
const unsubscribe = stytchClient.session.onChange((newSession) => {
session = newSession;
if (session &&
session.authentication_factors.length === 2 &&
session.authentication_factors.find((i) => i.delivery_method === 'knowledge') &&
session.authentication_factors.find((i) => i.delivery_method === 'sms')
) {
// Can display gated content
} else {
// Display login flow
}
});
window.addEventListener('beforeunload', () => {
unsubscribe && unsubscribe();
});
```
# Overview
Source: https://stytch.com/docs/consumer-auth/authentication/mfa/overview
Enable multi-factor authentication (MFA) in your application to add an extra layer of security for your users.
Multi-factor authentication (MFA) requires users to complete more than one method of authentication before granting access to your application's protected content. MFA in your application should prove ownership of multiple, distinct authentication factors, like a password (something you know) and a phone number (something you have).
This section of our Docs will walk you through setting up MFA using a Consumer Stytch project.
At a high level, you'll need to complete the following steps in order to implement MFA:
1. Prompt the user to authenticate their primary factor.
2. Prompt the user to authenticate their secondary factor, leveraging the Stytch Session from Step 1 to create a new Session that contains both authentication factors.
3. Enforce MFA by inspecting the `authentication_factors` array on the Stytch Session object in your application's authorization logic.
For a step-by-step implementation walkthrough, check out the MFA guide for your integration type. Note that our pre-built frontend UI components do not currently support MFA.
* [Backend Stytch integrations](/docs/consumer-auth/authentication/mfa/adding-mfa-to-custom-auth-flow)
* [Headless frontend JavaScript SDK Stytch integrations](/docs/consumer-auth/authentication/mfa/adding-mfa-to-stytch-login)
## MFA factor combinations
You can customize which primary and secondary authentication methods you'd like to offer your users:
| **Product** | **Valid as primary factor** | **Valid as secondary factor** | **Requirements** |
| - | - | - | - |
| Crypto Wallets | Yes | Yes | |
| Magic Links | Yes | Yes | |
| OAuth | Yes | Yes | |
| OTP (Email, SMS, and WhatsApp) | Yes | Yes | |
| Passwords | Yes | Yes | The user's email address [must be verified](/docs/consumer-auth/authentication/passwords/email-verification/overview) before adding a secondary factor to a Stytch User with a password. Otherwise, a password reset will be required. |
| Passkeys | Yes | Yes | The user must already have another primary factor before registering a passkey. After a passkey is registered, it can be used as a primary or secondary factor. |
| TOTP | No | Yes | Can only be used as a secondary factor. |
| WebAuthn | Yes | Yes | The user must already have another primary factor before creating a WebAuthn registration. After a WebAuthn registration is created, it can be used as a primary or secondary factor. |
The primary and secondary factors that you choose should confirm your user's identity in two different ways – for example, Email Magic Links and Email OTP are not a secure primary and secondary factor combination, because they both prove ownership of the same factor (an email address). Email Magic Links and TOTP are a secure combination, because they prove ownership of an email address and ownership of the TOTP authenticator app.
If a user has multiple factors set up, our frontend SDKs will require a secure combination of factors to be authenticated before allowing the user to take sensitive actions, like adding or removing auth factors. For additional information about this, see [here](/docs/api-reference/consumer/frontend-sdks/react/resources/multi-factor-authentication).
## Step-up authentication vs. MFA at login
You can choose to require MFA every time a user logs into your application, or you can choose to only prompt users for MFA before they take sensitive actions, which is referred to as “step-up auth”. Step-up auth requires less user friction up front, though it does make your application's authorization logic slightly more complex. The strategy you choose is up to you. Check out our [Step-up versus multi-factor authentication](https://stytch.com/blog/step-up-auth-vs-mfa/) blog post for additional information.
Step-up authentication is straightforward to implement when using Stytch Sessions. See **Step 4** in the [Backend integrations](/docs/consumer-auth/authentication/mfa/adding-mfa-to-custom-auth-flow) or [Headless frontend JavaScript SDK integrations](/docs/consumer-auth/authentication/mfa/adding-mfa-to-stytch-login) MFA guides for implementation instructions.
# Mobile Biometrics
Source: https://stytch.com/docs/consumer-auth/authentication/mobile-biometrics
Biometrics have become a popular way for apps to layer low-friction step-up auth into user workflows — for good reason. Biometrics ensure a user demonstrates both possession of an original device and a unique biometric trait.
Between our iOS SDK, Android SDK, and React Native SDKs, Stytch supports Mobile Biometrics natively on all Apple and Android devices. Check out our [iOS SDK Docs](https://stytchauth.github.io/stytch-ios/latest/StytchCore/documentation/stytchcore/stytchclient/biometrics-swift.struct), [React Native SDK docs](/docs/api-reference/consumer/mobile-sdks/react-native-legacy/methods/biometrics/overview), or [Android SDK docs](https://stytchauth.github.io/stytch-android/com/stytch/sdk/consumer/biometrics/package-summary.html) to integrate native biometrics into your app.
# Add Google One Tap
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-google-one-tap
Enable Google One Tap in your application to let users authenticate with a single prompt.
Google One Tap lets users authenticate with a single prompt instead of a full OAuth consent screen.
When a user signs in via One Tap, you receive a JWT but **no access\_token or refresh\_token**, so you cannot call Google APIs for that user.
## Prerequisites
* Configure Google as an OAuth provider in Stytch.
* Add your application's domain as an **Authorized JavaScript Origin** in the Google Cloud Console.
If you haven't set up the Google provider yet, start with the Google provider setup guide:
[Google OAuth setup](/docs/consumer-auth/authentication/oauth/adding-providers/google-oauth)
## Enable One Tap in the Dashboard
In the [Stytch Dashboard](https://stytch.com/dashboard/oauth), navigate to **OAuth > Providers > Google** and enable **One Tap**.
## Update your SDK config
Enable One Tap in your OAuth providers config. Example (React/Next.js):
```jsx theme={null}
import { Products } from "@stytch/react";
const config = {
products: [Products.oauth],
oauthOptions: {
providers: [{ type: "google", one_tap: true }],
},
};
```
## Next steps
* [Add OAuth to Stytch Login](/docs/consumer-auth/authentication/oauth/adding-oauth-to-stytch-login)
* [OAuth overview](/docs/consumer-auth/authentication/oauth/overview)
# Adding OAuth to Stytch Login
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-oauth-to-stytch-login
Integrate OAuth providers with Stytch's pre-built login UI for Consumer Authentication.
This guide walks through adding OAuth to Stytch's pre-built login UI. If you want a fully custom OAuth flow, see [Adding OAuth to a custom auth flow](/docs/consumer-auth/authentication/oauth/adding-to-custom-auth-flow).
## Prerequisites
If you haven't done so already:
1. Complete the steps in the [OAuth provider setup guide](/docs/consumer-auth/authentication/oauth/adding-providers/google-oauth).
2. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration) and add your application's domain to **Authorized Domains**.
3. Configure your [Redirect URLs](https://stytch.com/dashboard/redirect-urls).
## Add OAuth providers to your UI config
To surface the OAuth providers you want to support in the UI, add them to the `oauthOptions.providers` array in your config.
```jsx React icon="react" theme={null}
import { Products, StytchLogin } from "@stytch/react";
const config = {
products: [Products.oauth],
oauthOptions: {
providers: [{ type: "google" }, { type: "microsoft" }],
},
};
export const Login = () => {
return ;
};
```
```jsx Next.js icon="triangle" theme={null}
import { Products, StytchLogin } from "@stytch/nextjs";
const config = {
products: [Products.oauth],
oauthOptions: {
providers: [{ type: "google" }, { type: "microsoft" }],
},
};
export const Login = () => {
return ;
};
```
```html Vanilla JS icon="square-js" theme={null}
```
You can add multiple OAuth providers by including additional provider objects in the `providers` array. See the [OAuth providers guide](/docs/consumer-auth/authentication/oauth/adding-providers/google-oauth) for provider-specific setup steps.
## Render the login UI
Render the Stytch Login UI on the Redirect URL(s) you configured in the Stytch Dashboard. When rendered, the UI component will automatically handle the OAuth redirect and complete the authentication flow.
## Test your integration
1. Navigate to your login page.
2. Click an OAuth provider button (e.g., "Continue with Google").
3. Complete the OAuth flow with the provider.
4. Verify that you're redirected back to your application and authenticated.
Use test accounts for your OAuth providers during development to avoid affecting production data.
## Next steps
Learn how to customize the appearance and behavior of the pre-built login UI.
Configure additional OAuth providers like GitHub, Slack, or Apple.
# Adding PKCE to an OAuth flow
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-pkce
Enable PKCE in OAuth flows for stronger protection on web and native redirects.
[Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636) is a specification that builds on OAuth 2.0 to create a more secure login flow.
In the original OAuth authorization code flow, an IdP issues a one-time use code to the app, which is exchanged along with a `client_id` and `client_secret` for an auth token. For certain types of apps (i.e. SPAs, mobile apps, or native apps) there was no mechanism to hide the `client_secret`, which must be included in the app's source code and reused on every request. This meant that an attacker would be able to impersonate a user if the attacker gained access to the one-time use code. Codes in a URL can be leaked in many different ways - either through improper logging, HTTP Referrer headers, or even through another exploit like XSS.
PKCE introduces a one-time secret for each authorization flow. This secret, called a `code_verifier`, is generated and stored by the app on the user's device. Before the user is redirected to the IdP, the verifier is hashed to produce a `code_challenge`. This challenge is passed as a query parameter during the authentication flow, e.g. `/oauth/authenticate?code_challenge=E9Mel....`
After the IdP has validated the user's credentials, the user is redirected back to the app with a secure code. Now, the app must submit the code along with the original `code_verifier`. The IdP hashes the submitted `code_verifier` and validates that it matches the `code_challenge` passed as a query parameter earlier. This check ensures that the token has not been leaked, and that it is being handled by the same user that was seen at the start of the flow.
This guide will show you how to add PKCE support to your Stytch OAuth integration for added security. A PKCE integration is required for applications that use native callback URLs, for example `appname://some/callback` on mobile. Stytch's support for PKCE differs slightly from the original specification - for example we do not permit plain verifiers to be used - but the original idea of strengthening app security with a unique secret on every request remains the same.
If you get stuck while working through this guide, feel free to ask questions via [support@stytch.com](mailto:support@stytch.com) or in our [Slack community](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw).
Enable PKCE for all OAuth logins performed by the JavaScript SDK. By flipping this setting, you tell the Javascript SDK to use PKCE for all UI components and to reject any OAuth logins that do not use PKCE. Toggle the option in the [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration) of the Stytch Dashboard. This makes your application resistant to attacks like [Login CSRF](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site%5FRequest%5FForgery%5FPrevention%5FCheat%5FSheet.html#login-csrf), in which an attacker can trick a victim into logging in to the wrong account.
**Note:** The legacy oauth.\$provider.getURL() method is not compatible with PKCE. If you are using this method already in your codebase, complete the next step before enabling PKCE.
Call one of the [oauth.\$provider.start()](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/oauth/start) methods to start the OAuth flow. The SDK will generate a `code_verifier` and store it in local storage before redirecting the user to Stytch's OAuth start endpoint. If you use the SDK's built-in [OAuth UI](/docs/api-reference/consumer/frontend-sdks/vanilla-js/prebuilt-ui/web-component) this step is already handled for you.
```js theme={null}
function OAuthStartButton() {
const stytch = useStytch();
const startOAuthFlow = useCallback(() => stytch.oauth.google.start({
login_redirect_url: 'https://example.com/callback',
signup_redirect_url: 'https://example.com/callback'
}), [stytch])
return (
);
}
```
At the route that the user is redirected to after the OAuth flow, pull the token out of the URL params and call [oauth.authenticate(token)](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/oauth/authenticate) to exchange the token for a Stytch session. The SDK will automatically retrieve the `code_verifier` created earlier from local storage and pass it on to the Stytch servers.
```js theme={null}
function OAuthCallback() {
const stytch = useStytch();
useEffect(() => {
stytch.authenticateByUrl({
session_duration_minutes: 60,
}).then((result) => {
if (result?.handled) {
// TODO: Redirect to final logged-in view
} else {
// TODO: Handle redirect back to logged-out view
}
})
}, [stytch]);
return (
Authenticating....
);
}
```
Create a `code_verifier` and `code_challenge` pair. To create the `code_verifier`, generate at least 256 random bits of entropy. This can be done by having a suitable random number generator create a 32-octet sequence. To create the `code_challenge`, hash the `code_verifier` using SHA-256 and then base64url encode both to produce ASCII strings.
```js theme={null}
const pkceChallenge = require("pkce-challenge");
const { code_verifier, code_challenge } = pkceChallenge();
console.log("code_verifier", code_verifier);
console.log("code_challenge", code_challenge);
```
Associate the `code_verifier` with the user's device before starting the OAuth flow. A common approach is to store it in an HTTP-only cookie. Be sure to include the `code_challenge` as a query param when you start the OAuth flow.
```http theme={null}
HTTP/2 302
content-type: application/json
location: https://api.stytch.com/v1/public/oauth/google/start?public_token=PUBLIC_TOKEN&code_challenge=CODE_CHALLENGE
set-cookie: code_verifier=CODE_VERIFIER; Path=/; HttpOnly; Secure; SameSite=None
```
After the user is redirected back from Stytch with a valid token, exchange the token along with the original `code_verifier` to complete the flow and be issued a valid Stytch session. Retrieve the `code_verifier` from the cookie where it was stored in step 2, and send it to the [Authenticate OAuth token](/docs/api-reference/consumer/api/oauth/authenticate) endpoint.
```js theme={null}
const stytch = require("stytch");
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
});
client.oauth.authenticate("OAUTH_TOKEN", {
code_verifier,
})
.then((resp) => {
console.log(resp);
})
.catch((err) => {
console.log(err);
});
```
You have all the critical components to authenticate users via OAuth with PKCE.
# Amazon
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/amazon
Set up Amazon OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Amazon OAuth client yet.*
1. Navigate to [Login with Amazon OAuth Applications](https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html). For more information, check out Amazon's **Login With Amazon** guide [here](https://developer.amazon.com/docs/login-with-amazon/web-docs.html).
2. Click **Create a New Security Profile** with your application name, description, and privacy notice URL. Then click the gear icon and select **web settings** for your security profile.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Allowed Return URLs** section by clicking **Edit**.
***
*Follow these steps if you already have an existing Amazon OAuth client.*
1. Navigate to [Login with Amazon OAuth Applications](https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html).
2. Click the gear icon and select **web settings** for your security profile.
3. Navigate to **web settings** for your application.
4. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Allowed Return URLs** section by clicking **Edit**.
# Apple
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/apple
Set up Apple OAuth for Consumer Authentication.
## Setup
If you don't already have an Apple Developer account with an app set up, begin at Step 1. If you already have an account and an App ID, skip ahead to Step 5.
1. Create an [Apple Developer account](https://developer.apple.com/enroll/identity/edit) if you have not already done so. You will need an Apple ID with two-factor authentication turned on.
2. Log in to your [App Store Connect](https://appstoreconnect.apple.com/login) account with your Apple ID.
3. In the developer portal, go to your [Identifiers](https://developer.apple.com/account/resources/identifiers/list) page. Click the blue plus sign next to the "Identifiers" header to create a new identifier. Register this identifier as an App ID and click Continue. Then, select the type App and click Continue.
4. Create a description for this App ID that allows you to identify it easily, and a bundle ID that's a reverse-domain style string. For example, if your domain is yourcompany.com, and your app is called **your app**, your reverse-domain bundle ID should be com.yourcompany.yourapp. Under the Capabilities section, check the box for "Sign in with Apple" and make sure that “Enable as a primary App ID” is selected. Click Continue and then Register to create your App ID.
5. Navigate to the [Identifiers](https://developer.apple.com/account/resources/identifiers/list) page and click the blue plus sign next to the "Identifiers" header. Register this identifier as a Service ID and click Continue.
6. Create a description for this Service ID that allows you to identify it easily, and an Identifier that's a reverse-domain style string (e.g. com.yourcompany.yourservice). The identifier must be different from any identifier you've used for a Service ID or App ID in the past. Click Continue, and if everything looks good on the next screen, click Register.
7. Upon registering your new Service ID, you will be taken back to your developer portal's [Service IDs list](https://developer.apple.com/account/resources/identifiers/list/serviceId). Click on the Service ID that you just created. Enable "Sign in with Apple" and click Configure.
8. Select the relevant App ID for the Primary App ID field. Enter a list of domains and subdomains for your app in the "Website URLs > Domains and Subdomains" section. These should contain just the domain, not the “https\://” in front of it (e.g. just "example.com"). Enter all domains that your app will redirect to here.
9. In the "Website URLs > Return URLs" section, copy and paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth). When you're finished, click Done, then Continue, then Save.
10. Navigate to the [Keys](https://developer.apple.com/account/resources/authkeys/list) page and create a new key by clicking on the blue plus button.
11. Register a new key. Add a Key name and description. Enable 'Sign in with Apple' and click 'Configure'.
12. Configure the key by selecting your Primary App ID from the dropdown list. Click 'Save', which will redirect you back to the 'Register a New Key' page, from there click 'Continue' and then 'Register'.
13. Download the .p8 key file. After downloading your key, be sure to save a backup of your key in a secure place as it cannot be re-downloaded as the server copy is removed. Click 'Done'.
14. In the Stytch [Dashboard](https://stytch.com/dashboard/oauth), navigate to "OAuth > Providers > Apple" and click on 'Configure Provider'. Fill out the configuration details:
* **Team ID** can be found in the Apple [Account page](https://developer.apple.com/account) within your Membership details.
* **App ID** references the ID from step 4.
* **Service ID** references the ID from step 6.
* **Key ID**: references the ID from step 11.
* **Private key** is the .p8 file, which you downloaded in step 13.
# Bitbucket
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/bitbucket
Set up Bitbucket OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Bitbucket OAuth client yet.*
1. Navigate to [Bitbucket Workspace Applications](https://bitbucket.org/account/apps). For more information, check out Bitbucket's **Creating an OAuth App** guide [here](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/).
2. Click **OAuth consumers** under **Apps and features** on the left navigation.
3. Click **Add consumer** with your application name.
4. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callback URL**.
5. Select at least Account **Email** and **Read** permissions.
***
*Follow these steps if you already have an existing Bitbucket OAuth client.*
1. Navigate to [Bitbucket Workspace Applications](https://bitbucket.org/account/apps).
2. On the application you'd like to use click the three dots on the right-hand side and press edit.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callback URL**.
# Coinbase
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/coinbase
Set up Coinbase OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Coinbase OAuth client yet.*
1. Navigate to your [OAuth Applications](https://www.coinbase.com/settings/api). For more information, check out Coinbase's [Sign In with Coinbase guide](https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/welcome).
2. Click **New OAuth2 Application** with your application name, URL, description, and icon.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Permitted Redirect URLs** section.
4. Click yes or no to each of the remittance questions and accept the terms and conditions.
5. Click **Create Application**.
***
*Follow these steps if you already have an existing Coinbase OAuth client.*
1. Navigate to your [OAuth Applications](https://www.coinbase.com/settings/api).
2. Click on the application you'd like to use and click **Edit**.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Permitted Redirect URLs** section and save.
# Discord
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/discord
Set up Discord OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Discord OAuth client yet.*
1. Navigate to [Discord OAuth Applications](https://discord.com/developers/applications). For more information, check out Discord's **Creating an OAuth App** guide [here](https://discord.com/developers/docs/topics/oauth2).
2. Create a **New Application** with your application name. Then navigate to **OAuth2 > General** in the left hand sidebar.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Redirect** section by clicking **Add Redirect**.
***
*Follow these steps if you already have an existing Discord OAuth client.*
1. Navigate to [Discord OAuth Applications](https://discord.com/developers/applications).
2. Click on the application you'd like to use and press edit.
3. Navigate to OAuth2 > General for your application.
4. Update the Redirects by copy-pasting the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth).
# Facebook
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/facebook
Set up Facebook OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Facebook OAuth client yet.*
1. Navigate to your Facebook (Meta) [applications](https://developers.facebook.com/apps/).
2. Click **Create App**.
3. Enter a name for the app and click **Next**.
4. Choose **Other** from the app type list and click **Next**.
5. Select **Consumer** and click **Next**, then click **Create app**.
6. In the list of products, find the **Facebook Login** product and click **Set up**.
7. Ignore the quickstart menu. Instead, on the left pane under **Facebook Login**, click on **Settings**.
8. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the list of Valid OAuth Redirect URIs. Fill in remaining fields. Then click **Save changes**.
9. In the left pane under **App Review**, navigate to **Permissions and Features**.
10. Click **Request advanced access** for both email and `public_profile` and follow the instructions shown.
***
*Follow these steps if you already have an existing Facebook OAuth client.*
1. Navigate to your Facebook (Meta) [applications](https://developers.facebook.com/apps/).
2. Click on the application you'd like to use.
3. In the list of products, find the **Facebook Login** product and click **Set up**.
4. Ignore the quickstart menu. Instead, on the left pane under **Facebook Login**, click on **Settings**.
5. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the list of Valid OAuth Redirect URIs. Fill in remaining fields. Then click **Save changes**.
6. In the left pane under **App Review**, navigate to **Permissions and Features**.
7. Click **Request advanced access** for both email and `public_profile` and follow the instructions shown.
# Figma
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/figma
Set up Figma OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Figma OAuth client yet.*
1. Navigate to [Figma Workspace Applications](https://www.figma.com/developers/apps). For more information, check out Figma's **Authentication** guide [here](https://www.figma.com/developers/api#authentication).
2. **Create a new app** with your application name, url, and logo.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callbacks** section by clicking **Add callback**.
4. **Save** your app.
5. Copy the **Client ID** and **Client Secret** - this is the only time you can save the client secret for your app.
***
*Follow these steps if you already have an existing Figma OAuth client.*
1. Navigate to [Figma Workspace Applications](https://www.figma.com/developers/apps).
2. Click **edit** for your application.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callbacks** section by clicking **Add callback**.
# GitHub
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/github
Set up GitHub OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Github OAuth client yet.*
1. Navigate to [Github OAuth Applications](https://github.com/settings/developers). For more information, check out Github's **Creating an OAuth App** guide [here](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app).
2. Click **New OAuth App** or **Register a new application** depending on if you have other applications or not.
3. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Authorization callback URL** field Fill in remaining fields how you see fit.
***
*Follow these steps if you already have an existing Github OAuth client.*
1. Navigate to [Github OAuth Applications](https://github.com/settings/developers). If you're using an organization OAuth Application, navigate to the organization's **Developer Settings** tab. Note: this requires admin privileges.
2. Click on the application you'd like to use.
3. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Authorization callback URL** field.
# GitLab
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/gitlab
Set up GitLab OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Gitlab OAuth client yet.*
1. Navigate to [Gitlab OAuth Applications](https://gitlab.com/-/profile/applications). For more information, check out GitLab's **Creating an OAuth App** guide [here](https://docs.gitlab.com/ee/integration/oauth%5Fprovider.html).
2. Create a **New Application** with the name, redirect uri, and desired scopes. You must include the openid, email, and profile scopes.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Redirect URI** field. Fill in remaining fields how you see fit.
***
*Follow these steps if you already have an existing Gitlab OAuth client.*
1. Navigate to [Gitlab OAuth Applications](https://gitlab.com/-/profile/applications).
2. Click on the application you'd like to use and press edit.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Redirect URL**. Under Scopes, make sure openid, email, and profile are selected.
# Google
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/google-oauth
Set up Google OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Google OAuth client yet.*
1. [Create a Google Cloud account and project](https://support.google.com/googleapi/answer/6251787#zippy=%2Ccreate-a-project) if you have not already done so.
2. [Configure your OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) if you have not already done so. [More info](/docs/consumer-auth/authentication/oauth/adding-to-custom-auth-flow)
3. [Go to the Google Credentials page](https://console.cloud.google.com/apis/credentials) for your application. Click on **CREATE CREDENTIALS** and then select **OAuth Client ID**.
4. Select **Web Application** as the application type (regardless of if it is a mobile or non-web application) and enter a name for your OAuth client.
5. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into your OAuth configuration under the **Authorized redirect URIs** section.
6. Click **create** to save the configuration.
7. **Optional**: If you want the Stytch SDK to support Google One Tap, add your application's URI as an Authorized JavaScript Origin. Read more about [One Tap](/docs/consumer-auth/authentication/oauth/adding-oauth-to-stytch-login).
***
*Follow these steps if you already have an existing Google OAuth client.*
1. Go to the Google Credentials page for your application. You should see your OAuth client under the **OAuth 2.0 Client IDs** section.
2. Click on the OAuth client to edit its configuration.
3. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into your OAuth configuration under the **Authorized redirect URIs** section.
4. Click to save the configuration.
5. **Optional**: If you want the Stytch SDK to support Google One Tap, add your application's URI as an Authorized JavaScript Origin. Read more about [One Tap](/docs/consumer-auth/authentication/oauth/adding-oauth-to-stytch-login).
# Google One Tap
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/google-one-tap
Set up Google One Tap OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Google OAuth client yet.*
1. [Create a Google Cloud account and project](https://support.google.com/googleapi/answer/6251787#zippy=%2Ccreate-a-project) if you have not already done so.
2. [Configure your OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) if you have not already done so. [More info](/docs/consumer-auth/authentication/oauth/adding-to-custom-auth-flow)
3. [Go to the Google Credentials page](https://console.cloud.google.com/apis/credentials) for your application. Click on **CREATE CREDENTIALS** and then select **OAuth Client ID**.
4. Select **Web Application** as the application type (regardless of if it is a mobile or non-web application) and enter a name for your OAuth client.
5. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into your OAuth configuration under the **Authorized redirect URIs** section.
6. Click **create** to save the configuration.
7. **Optional**: If you want the Stytch SDK to support Google One Tap, add your application's URI as an Authorized JavaScript Origin. Read more about [One Tap](/docs/consumer-auth/authentication/oauth/adding-oauth-to-stytch-login).
***
*Follow these steps if you already have an existing Google OAuth client.*
1. Go to the Google Credentials page for your application. You should see your OAuth client under the **OAuth 2.0 Client IDs** section.
2. Click on the OAuth client to edit its configuration.
3. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into your OAuth configuration under the **Authorized redirect URIs** section.
4. Click to save the configuration.
5. **Optional**: If you want the Stytch SDK to support Google One Tap, add your application's URI as an Authorized JavaScript Origin. Read more about [One Tap](/docs/consumer-auth/authentication/oauth/adding-oauth-to-stytch-login).
# LinkedIn
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/linkedin
Set up LinkedIn OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a LinkedIn OAuth client yet.*
1. Navigate to [LinkedIn OAuth Applications](https://www.linkedin.com/developers/apps). For more information, check out LinkedIn's **Creating an OAuth App** guide [here](https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?tabs=HTTPS).
2. Click **Create app** with your application name. Then navigate to **Auth** in the application menu bar.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Authorized redirect URLs for your app** section by clicking the pencil icon.
***
*Follow these steps if you already have an existing LinkedIn OAuth client.*
1. Navigate to [LinkedIn OAuth Applications](https://www.linkedin.com/developers/apps).
2. Click on the application you'd like to use.
3. Navigate to **Auth** in the application menu bar.
4. Update the **Authorized redirect URLs for your app** by copy-pasting the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth).
# Microsoft
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/microsoft
Set up Microsoft OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Microsoft OAuth client yet.*
1. Create a [Microsoft Azure account](https://login.microsoftonline.com/) if you have not already done so.
2. Navigate to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft%5FAAD%5FIAM/ActiveDirectoryMenuBlade/RegisteredApps) to create a Microsoft application.
3. Click **New registration**.
4. Enter a name for your OAuth client.
5. Select "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)".
6. Under the **Redirect URI** section, select **Web** and then copy-paste the Stytch redirect URI from the [OAuth page](https://stytch.com/dashboard/oauth) into the URI field.
7. Click **Register** to save the configuration.
***
*Follow these steps if you already have an existing Microsoft OAuth client.*
1. Navigate to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft%5FAAD%5FIAM/ActiveDirectoryMenuBlade/RegisteredApps) and select your Microsoft application.
2. Ensure that the **Supported account types** section is set to "All Microsoft account users". If it's not, navigate to the **Manifest** tab, set the "signInAudience" value to "AzureADandPersonalMicrosoftAccount", and save.
3. In your OAuth client configuration, select the link next to the **Redirect URIs** section. Then select **Add a platform** and finally select **Web**.
4. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the URI field.
5. Click **Configure** to save the URI.
# Salesforce
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/salesforce
Set up Salesforce OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Salesforce OAuth client yet.*
1. Navigate to your [Applications](https://stytchinc.lightning.force.com/lightning/setup/NavigationMenus/home) or by going to Setup > App Manager. For more information, check out Salesforce's **Authentication** guide [here](https://help.salesforce.com/s/articleView?id=sf.remoteaccess%5Fauthenticate.htm\&type=5).
2. Create a **New Connected App** with your application name and contact email and phone number.
3. Toggle **Enable OAuth Settings** in the API menu
4. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callback URL** section.
5. Make sure to include at least **Access the identity URL service (id, profile, email, address, phone)** and **Access unique user identifiers (openid)** scopes.
6. Check the **Enable Client Credentials Flow** checkbox.
***
*Follow these steps if you already have an existing Salesforce OAuth client.*
1. Navigate to your project by clicking your application from your organizations [app manager page](https://stytchinc.lightning.force.com/lightning/setup/NavigationMenus/home).
2. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callback URL** section by clicking **Enable OAuth Settings**.
# Slack
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/slack
Set up Slack OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Slack OAuth client yet.*
1. Navigate to [Slack OAuth Applications](https://api.slack.com/apps). For more information, check out Slack's **Creating an OAuth App** guide [here](https://api.slack.com/authentication/oauth-v2).
2. Select **Create New App** and choose to create it **From scratch**. Enter desired App Name and Slack Workspace.
3. You will be redirected to the configuration page for your new App. On the left-hand sidebar, navigate to **OAuth & Permissions**.
4. Scroll down to **Redirect URLs** and click **Add New Redirect URL**. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth), then click **Save URLs**.
5. In the **Scopes** section under **User Token Scopes** add at least the following scopes: users:read, users:read.email.
***
*Follow these steps if you already have an existing Slack OAuth client.*
1. Navigate to [Slack OAuth Applications](https://api.slack.com/apps) and select the OAuth App you will be using.
2. On the left-hand sidebar, navigate to **OAuth & Permissions**.
3. Scroll down to **Redirect URLs** and click **Add New Redirect URL**. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth), then click **Save URLs**.
4. In the **Scopes** section under **User Token Scopes** add at least the following scopes: users:read, users:read.email.
# Snapchat
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/snapchat
Set up Snapchat OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Snapchat OAuth client yet.*
1. Navigate to [Snapchat Applications](https://kit.snapchat.com/manage/). For more information, check out Snapchat's **Authentication** guide [here](https://docs.snap.com/snap-kit/login-kit/overview/).
2. Create a **New Project** with your application name.
3. Toggle **Login Kit** on at the bottom of the page. Select at least **Display Name** and **Bitmoji Avatar**.
4. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Redirect URI** section and click **Add URI**.
***
*Follow these steps if you already have an existing Snapchat OAuth client.*
1. Navigate to your project by clicking your application from your organizations [manage page](https://kit.snapchat.com/manage/).
2. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **OAuth Redirect URLs** section by clicking **Add**.
# Spotify
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/spotify
Set up Spotify OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Spotify OAuth client yet.*
1. Navigate to your [Spotify Applications](https://developer.spotify.com/dashboard/applications). For more information, check out Spotify's Creating an OAuth App [guide](https://developer.spotify.com/documentation/general/guides/authorization/).
2. Click **Create an App** with your application name and description. Then navigate to **Edit settings** in the application menu bar.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the Redirect URLs section.
***
*Follow these steps if you already have an existing Spotify OAuth client.*
1. Navigate to your [Spotify Applications](https://developer.spotify.com/dashboard/applications). Click on the application you'd like to use.
2. Navigate to **Edit settings** in the application menu bar.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the Redirect URLs section.
# TikTok
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/tiktok
Set up TikTok OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a TikTok OAuth client yet.*
1. Navigate to your [TikTok Applications](https://developers.tiktok.com/apps). For more information, check out TikTok's [Authentication guide](https://developers.tiktok.com/doc/getting-started-create-an-app/).
2. **Connect an app** with your name, category, app icon, and description. Select your platforms.
3. On the left-hand sidebar **Add products**, including at least **Login Kit** and **TikTok API**.
4. Add links to your terms of service and privacy policy pages.
5. Under **Scopes** we require at least **user.info.basic**. Add any additional scopes needed for your application.
6. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Redirect domain** in the **Login Kit section**.
7. **Save changes** for your app configuration, and then **Submit** for review. TikTok usually reviews new apps in 12-36 hours.
***
*Follow these steps if you already have an existing TikTok OAuth client.*
1. Navigate to your application's manage pane, by clicking your application.
2. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Redirect domain** in the **Login Kit section**.
# Twitch
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/twitch
Set up Twitch OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Twitch OAuth client yet.*
1. Navigate to your [Twitch Console](https://dev.twitch.tv/console). For more information, check out Twitch's [Authentication guide](https://dev.twitch.tv/docs/authentication).
2. **Register Your Application** with your application name.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **OAuth Redirect URLs** section by clicking **Add**.
***
*Follow these steps if you already have an existing Twitch OAuth client.*
1. Navigate to your application's manage pane, available in the right hand side bar of Twitch under Applications > Name of your application.
2. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **OAuth Redirect URLs** section by clicking **Add**.
# Twitter
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/twitter
Set up Twitter OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Twitter OAuth client yet.*
1. Signup for the [Twitter API](https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api).
2. Navigate to your [Twitter Dashboard](https://developer.twitter.com/en/portal/dashboard). For more information, check out Twitter's Authentication [guide](https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code).
3. **Create App** with your application name.
4. Under **User authentication settings**, click **Set up**.
5. Enable **OAuth 2.0 Authentication** by selecting **Web App**, **Automated App** or **Bot**.
6. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callback URI / Redirect URL** section.
7. Add your website, terms of service, and privacy policy links, and click **Save**.
***
*Follow these steps if you already have an existing Twitter OAuth client.*
1. Navigate to your application's **User authentication settings**, available in the right hand side bar of Twitter under Projects & Apps > Name of your application.
2. Under **User authentication settings**, click **Edit**.
3. Enable **OAuth 2.0 Authentication** by selecting **Web App**, **Automated App** or **Bot**.
4. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the **Callback URI / Redirect URL** section.
5. Add your website, terms of service, and privacy policy links, and click **Save**.
# Yahoo
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-providers/yahoo
Set up Yahoo OAuth for Consumer Authentication.
## Setup
*Follow these steps if you have not created a Yahoo OAuth client yet.*
1. After creating a Yahoo developer account, you can create a Yahoo OAuth application [here](https://developer.yahoo.com/apps/create/). For more information, check out Yahoo's guide to receive your OAuth 2.0 credentials [here](https://developer.yahoo.com/sign-in-with-yahoo/#step-one).
2. Create a **New App** with your application name and optional description and homepage URL.
3. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the Redirect URLs section.
4. Make sure to include **OpenID Connect Permissions** and ensure both **Email** and **Profile** are checked.
***
*Follow these steps if you already have an existing Yahoo OAuth client.*
1. Navigate to your project by clicking your application from your organization's [app page](https://developer.yahoo.com/apps/).
2. Copy-paste the Redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the Redirect URLs section.
# Integrate Google OAuth via the API
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/adding-to-custom-auth-flow
Use the Consumer API to add Google OAuth to a custom login flow.
The first three steps involve setting up a Google Cloud Project to get OAuth credentials. Skip those steps if you already have your Google OAuth credentials.
**Note**: This guide assumes you are in a Test environment in Stytch. If you are using a Live environment, be sure to use credentials from your Live project, and replace test.stytch.com with api.stytch.com.
Before you can use the Stytch Google OAuth flow, you need to enter a Google OAuth **client ID** and **client secret** into the Stytch Dashboard. Google requires you to create a Google Cloud project to get a **client ID** and **client secret**.
Please follow the steps [here](https://support.google.com/googleapi/answer/6251787?ref%5Ftopic=7014522#zippy=%2Ccreate-a-project) to create one.
Google also requires developers to fill out an OAuth consent form to create OAuth credentials. Google uses this consent form to make sure that an application does not abuse its data.
If you have not filled out the OAuth Consent yet, please visit [here](https://console.cloud.google.com/apis/credentials/consent) to start the process. You should see an form like the following:
The Google consent page requires you to select whether your application is an **external** or **internal** one. **Internal** applications limit access to accounts within a Google workspace, and you cannot select internal if your Google Cloud project is not a part of a Google Workspace. **External** applications allow users outside your Google workplace domain to sign up and possibly require you to verify your application with Google. Most use cases pick external over internal.
Once you select internal or external, you need to fill out some information on your company. Under the Authorized domains section, you need to add **stytch.com**.
After filling out your company's information, you can click **save and continue** for the scopes and test users pages without any changes.
Once you are on the summary page, go back to **Google OAuth Consent Dashboard**. You need to publish your application for prod to avoid the rate limit of 100 users.
**NOTE**: If you added any restrictive scopes or added a logo, your application needs to be ***manually verified***. This verification can take a couple of days to be reviewed. You can read more about Googles OAuth verification [here](https://support.google.com/cloud/answer/9110914).
1. After setting up the Google project and OAuth consent, go to [this page](https://console.cloud.google.com/apis/credentials) to create the OAuth credentials. There is a **create credentials** button at the top of the page with an option to create an OAuth client id when selected.
2. When the create OAuth credentials page loads, select **Web application** (regardless if it is a mobile or non-web application) for the application type and enter a name for your credentials. Under the **Authorized redirect URIs** section, add the following URL.
```curl theme={null}
https://test.stytch.com/v1/oauth/callback/REPLACE_WITH_OAUTH_CALLBACK_ID
```
3. After you finish creating the OAuth client, Google displays **client ID** and **client secret** in a popup. You will need to enter them into the Stytch Dashboard [here](https://stytch.com/dashboard/oauth).
Add the login and signup URLs to the project's list of predefined redirect URLs in the [Dashboard](https://stytch.com/dashboard/redirect-urls). For more information on why this step is necessary, please check out the documentation [here](/docs/api-reference/consumer/api/resources/url-validation).
By default, all redirect URLs are set to [http://localhost:3000](http://localhost:3000) for Test environments.
Now that Stytch and Google are configured, you need to add Stytch Google OAuth to your application.
The Stytch OAuth flow is started by having a user click the **Google OAuth Start URL**.
```curl theme={null}
https://test.stytch.com/v1/public/oauth/google/start?public_token=PUBLIC_TOKEN
```
This URL redirects the user to the Google OAuth sign-in page with all the required OAuth fields pre-filled. Your frontend needs to display Stytch Google OAuth Start URL as a button and send the user to that URL when clicked.
After Google redirects the user back to Stytch's API, the Stytch API redirects the user to your backend server with a token to either the **login redirect URL** or the **signup redirect URL** to verify Stytch redirected the user.
If the response is a 200, the user is verified and can be logged in.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET"
}
);
// Replace with token from request
const params = {
token: "SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4=",
};
client.oauth.authenticate(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
# Email address behavior
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/email-behavior
Learn about how Stytch handles email addresses returned by OAuth providers during authentication.
When a user authenticates via OAuth, we **only** add an email address to the resulting Stytch User when we receive an email address from the OAuth provider that the provider guarantees has been verified by the user. This email address will be marked as `verified: true`.
If the OAuth provider does not guarantee that the returned email address has been verified, we cannot safely make account deduplication decisions and protect against potential account takeover vectors, so we will not add the email address to the resulting Stytch User and cannot automatically deduplicate accounts even if the user has previously authenticated using a matching email address.
You can leverage our [Attach OAuth factor](/docs/api-reference/consumer/api/oauth/attach) functionality to explicitly associate an OAuth factor with an existing Stytch User when a user is already logged into your application. When a user completes an OAuth flow initiated with an `oauth_attach_token`, we will add the new OAuth factor to the specified Stytch User. Note that this flow *cannot* be used to merge two existing Stytch users; the OAuth factor being attached must not already be associated with any other Stytch Users.
Additionally, if using an unverified email address is acceptable in the context of your application, you can retrieve a user's email addresses by either parsing the JWT contained within the `idp` object we return in our [Authenticate OAuth token](/docs/api-reference/consumer/api/oauth/authenticate) response or by querying most OAuth providers' APIs with the `access_token` that we also return in the `idp` object.
### Email address support across OAuth providers
| Provider | Email address behavior |
| - | - |
| Amazon | We **do not** return email addresses provided by Amazon. Amazon does not explicitly state that they verify user email addresses, and they suggest linking accounts [only after verifying "local credentials"](https://developer.amazon.com/docs/login-with-amazon/integrate-with-account-system-websites.html#obtain-and-secure-amazon-customer-profile-data) (or, in other words, after verifying that the user owns the target account by other means). |
| Apple | We **do** return email address when Apple marks them as verified. |
| Bitbucket | We **do** return email address when Bitbucket marks them as verified. |
| Coinbase | We **do not** return email addresses provided by Coinbase. Coinbase does not explicitly state that they verify user email addresses. |
| Discord | We **do** return email address when Discord marks them as verified. |
| Facebook | We **do not** return email addresses provided by Facebook. Facebook states that applications that use email addresses returned by Facebook should verify the email addresses themselves. |
| Figma | We **do** return email address returned by Figma. |
| GitHub | We **do** return email address when GitHub marks them as verified. |
| GitLab | We **do** return email address when GitLab marks them as verified. |
| Google | We **do** return email addresses when Google marks them as verified. |
| LinkedIn | We **do** return email addresses provided by LinkedIn. |
| Microsoft | We **do not** return email addresses provided by Microsoft. Microsoft does not attest to email ownership, and [recommends against using them for authentication purposes](https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims#v10-and-v20-optional-claims-set). |
| Slack | We **do** return email addresses when Slack marks them as verified. |
| Snapchat | We **do not** return email addresses for Snapchat users. Snapchat does not return user email addresses. |
| Spotify | We **do not** return email addresses provided by Spotify. Spotify explicitly states that [they do not verify user email addresses](https://developer.spotify.com/documentation/web-api/reference/get-current-users-profile). |
| TikTok | We **do not** return email addresses for TikTok users. TikTok does not return user email addresses. |
| Twitch | We **do** return email addresses when Twitch marks them as verified. |
| Twitter | We **do not** return email addresses for Twitter users. Twitter does not return user email addresses. |
| Yahoo | We **do** return email addresses when Yahoo marks them as verified. |
# OAuth overview
Source: https://stytch.com/docs/consumer-auth/authentication/oauth/overview
Identity providers (IdPs), like Google, Apple, and GitHub, maintain user identity information and offer authentication services to developers and applications, so called relying parties.
This lets your users use a Single Sign-On (SSO) to login to your app without going through the extra step of creating an identity directly with your app.
In addition to offering identity management, IdPs often expose extensive functionality in their platform via API, e.g. reading and creating Google Calendar events or creating repos within GitHub. When you use Stytch OAuth, we automatically initiate a session with the IdP and return the necessary tokens to build on top of the provider's API.
## Identity provider details
Even though OAuth is a standard, IdPs can sometimes vary in data models and nomenclature within their own implementations. Stytch normalizes values and behavior as much as possible to ensure that you have a smooth integration with whichever providers you use.
The list below lists each OAuth provider we support and lets you know which values you should expect to see returned for each provider and the default session length. "—" indicates that the value is not applicable to the provider in question.
| Provider | `access_token` | `refresh_token` | `jwt` | Session length |
| - | - | - | - | - |
| Google | Returned | Returned | Returned\* | 1 hour |
| Amazon | Returned | Returned | — | 24 hours |
| Apple | Returned | Returned | — | Until revoked |
| Bitbucket | Returned | Returned | — | 2 hours |
| Coinbase | Returned | Returned | — | 2 hours |
| Discord | Returned | Returned | — | 7 days |
| Facebook (Meta) | Returned | — | — | 2 months |
| GitHub | Returned | — | — | Until revoked |
| GitLab | Returned | Returned | — | 2 hours |
| LinkedIn | Returned | Returned | — | 60 days |
| Microsoft | Returned | Returned\* | — | 60-90 minutes |
| Salesforce | Returned | — | — | Until revoked |
| Slack | Returned | — | — | Until revoked |
| Twitch | Returned | Returned | — | 60 days |
| Yahoo | Returned | Returned | Returned | 1 hour |
Google offers two pathways for a user to authenticate, a traditional OAuth flow with a consent pane, or via Google One Tap. If a user enters through the Google One Tap flow, you will only receive a `jwt` and no `access_token` or `refresh_token`; you will not be able to leverage Google's API.
**Default Scopes**
* `open_id`
* `email`
* `profile`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/google)
* [Gmail API documentation](https://developers.google.com/gmail/api/reference/rest)
Log in with Amazon lets users log in with their Amazon customer accounts and offers data like name, email address, and zip code to build a more personalized experience.
**Default Scopes**
* `profile`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/amazon)
* [Amazon API documentation](https://developer.amazon.com/docs/login-with-amazon/customer-profile.html)
Allow your users to log in with their Apple accounts. Apple does not offer any additional scopes. Note that Apple allows users to obfuscate their email address, and you may receive an email address like `h79gps7k78@privaterelay.appleid.com`. See the Resources section below to learn more.
**Default Scopes**
* `name`
* `email`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/apple)
* [Apple API documentation](https://developer.apple.com/documentation/sign_in_with_apple)
Log in with Bitbucket let's you build exciting tools on top of Atlassian's Bitbucket source control product.
**Default Scopes**
* `account`
* `email`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/bitbucket)
* [Bitbucket API documentation](https://developer.atlassian.com/cloud/bitbucket/rest/intro/)
Log in with Coinbase let's users easily and securely sign in to your app and lets you integrate Coinbase supported cryptocurrencies into your applications.
**Default Scopes**
* `account`
* `email`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/coinbase)
* [Coinbase API documentation](https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/permissions-scopes)
Discord OAuth login allows users to log into your app with their Discord account.
**Default Scopes**
* `identify`
* `email`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/discord)
* [Discord API documentation](https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes)
Facebook OAuth login allows users to log into your app with their Facebook account.
**Default Scopes**
* `public_profile`
* `email`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/facebook)
* [Facebook API documentation](https://developers.facebook.com/docs/graph-api/overview)
GitHub OAuth login allows users to log into your app with their GitHub account.
**Default Scopes**
* `user:email`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/github)
* [GitHub API documentation](https://docs.github.com/en/developers/overview/about-githubs-apis)
GitLab OAuth login allows users to log into your app with their GitLab account.
**Default Scopes**
* `open_id`
* `email`
* `profile`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/gitlab)
* [GitLab API documentation](https://docs.gitlab.com/ee/api/)
LinkedIn OAuth login allows users to log into your app with their LinkedIn account.
**Default Scopes**
* `r_liteprofile`
* `r_emailaddress`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/linkedin)
* [LinkedIn API documentation](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin?context=linkedin%2Fconsumer%2Fcontext)
Log in with Microsoft helps your users sign in easily with their corporate or person Microsoft accounts and lets you leverage Microsoft's Graph API.
**Default Scopes**
* `open_id`
* `email`
* `profile`
**Resources**
* [Stytch API reference](/docs/api-reference/consumer/api/oauth/start/microsoft)
* [Microsoft API documentation](https://docs.microsoft.com/en-us/graph/)
Log in with Salesforce let's your users log in with their Salesforce accounts.
**Default Scopes**
* `id`
* `openid`
Log in with Slack let's your users log in with their Slack accounts.
**Default Scopes**
* `openid`
* `email`
* `profile`
Log in with Twitch let's your users log in with their Twitch accounts.
**Default Scopes**
* `account`
* `email`
Log in with Yahoo let's your users log in with their Yahoo accounts.
**Default Scopes**
* `account`
* `email`
* `profile`
# Integrate OTPs via the API
Source: https://stytch.com/docs/consumer-auth/authentication/otps/api
Build SMS one-time passcode signup and login flows with the Consumer API.
Integrate Stytch one-time passcodes as your authentication solution.
Here's an example of an authentication flow. One screen accepts the user's phone number and the other accepts their one-time passcode.
The LoginOrCreateUserBySMS endpoint will be used to log in or sign up a user. This request will send a one-time passcode to the provided phone number. By default, the code will expire in 2 minutes. You can alter the expiration with the ExpirationMinutes request field. If the phone number isn't associated with a user yet, a user will be created. If the user\_created attribute in the response is true, save the user ID and phone ID from the response. We recommend saving these IDs in new columns of your users table or within a new table linking your users with their Stytch IDs.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
}
);
const params = {
phone_number: "+12025550162"
};
client.otps.sms.loginOrCreate(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
The AuthenticateOTP endpoint will be used in conjunction with all LoginOrCreateUserBySMS requests. The user should be prompted to enter the one-time passcode sent to them via SMS. After the user enters their code, send an AuthenticateOTP request with the code along with the phone ID used. If the response is a 200, the user is verified and can be logged in. If you'd like to keep this user logged-in for a while, include "session\_duration\_minutes": 60 (an hour, for example). Check out the [session management guide](/docs/consumer-auth/manage-sessions/overview) to learn how to handle the session.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
}
);
const params = {
method_id: "phone-number-test-d5a3b680-e8a3-40c0-b815-ab79986666d0",
code: "123456"
};
client.otps.authenticate(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
# One-time passcodes (OTP) overview
Source: https://stytch.com/docs/consumer-auth/authentication/otps/overview
Use SMS or WhatsApp one-time passcodes to verify users during login and signup.
One-time passcodes (OTP) let you verify users by sending a short code over SMS or WhatsApp. OTPs are a good fit for passwordless login, phone verification, and step-up authentication when you need an additional factor.
OTPs can be targeted by toll fraud. Review the [Toll fraud resources](/docs/resources/policies/messaging/toll-fraud) before enabling SMS at scale.
## How OTPs work
1. Collect the user's phone number and request a passcode.
2. The user enters the code they receive.
3. Your app verifies the code and optionally establishes a session.
## Next steps
Send and authenticate OTPs from your backend.
Add OTPs to Stytch Login with the frontend SDK.
# Add OTPs to Stytch Login
Source: https://stytch.com/docs/consumer-auth/authentication/otps/sdk
Add One-Time Passcodes (OTP) to the Stytch Login UI with the Consumer frontend SDK.
Use Stytch One-Time Passcodes (OTP) with the pre-built login UI. This guide uses the Next.js SDK, but the same configuration applies to other frontend SDKs.
1. Navigate to [Frontend SDK](https://stytch.com/dashboard/sdk-configuration) and click **Enable SDK**.
2. In [Project ID & API keys](https://stytch.com/dashboard), copy your `public_token`.
**Note:** Stytch has [Test and Live environments](/docs/api-reference/consumer/api/overview). Projects use **Test** by default. To call SMS OTP endpoints in **Live**, configure a payment method in [Workspace Settings > Billing](https://stytch.com/dashboard/settings/billing).
Install @stytch/nextjs:
```bash theme={null}
npm install @stytch/nextjs
```
Or with Yarn:
```bash theme={null}
yarn add @stytch/nextjs
```
Initialize the Stytch client with your `public_token`:
```jsx layout.tsx theme={null}
import { createStytchClient, StytchProvider } from "@stytch/nextjs";
const STYTCH_PUBLIC_TOKEN = 'YOUR_PUBLIC_TOKEN';
const stytch = createStytchClient(STYTCH_PUBLIC_TOKEN);
export default function RootLayout({ children }) {
return (
{children}
);
}
```
Create a component for your signup/login page and pass the OTP config. The Stytch UI accepts a config object; learn more [here](/docs/api-reference/consumer/frontend-sdks/vanilla-js/prebuilt-ui/login/configuration).
```jsx theme={null}
import { Products, StytchLogin } from '@stytch/nextjs';
const config = {
otpOptions: {
methods: ["sms"],
expirationMinutes: 10,
},
products: [
Products.otp,
],
};
export const Login = () => {
return ;
};
```
You now have an OTP signup and login flow with the pre-built UI config. The SDK handles the code authentication and manages the session automatically. Learn more about sessions [here](/docs/consumer-auth/manage-sessions/overview).
Have feedback after integrating? Reach out via [support@stytch.com](mailto:support@stytch.com) or in our [Slack community](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw).
# Add Passkeys to Stytch Login
Source: https://stytch.com/docs/consumer-auth/authentication/passkeys/login-sdk
Set up Passkeys in Stytch Login using the Consumer frontend SDKs.
Passkeys let users sign in with device-bound credentials and biometrics instead of passwords. This guide shows how to configure Stytch Login and Passkey registration using the frontend SDK.
## Before you start
In order to complete this guide, you'll need the following:
* A Stytch Consumer Authentication project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your projects in the top nav, click **Create Project**, and then select **Consumer Authentication**.
* The public token for the **Test environment** you would like to use. These values can be accessed from the **Project ID & API keys** section of the Project Overview, or from the "Copy Public API Key" in the command bar (⌘-K / Ctrl-K). You'll need to pass the public token into the initialized SDK client.
* The SDK enabled. In the Dashboard, navigate to the [Frontend SDK](https://stytch.com/dashboard/sdk-configuration) page. Then click **Enable SDK in Test**.
* **WebAuthn** enabled in the SDK settings under **Authentication products**.
* A web app that serves HTML pages.
## Passkeys require a primary factor
In order to ensure account recovery, Passkeys can only be used for login and not signup. Stytch Users are required to have a verified email or phone from primary auth factors like [Magic Links](/docs/consumer-auth/authentication/magic-links/adding-magic-links-to-custom-auth-flow), [OTP](/docs/consumer-auth/authentication/otps/api), and [OAuth](/docs/consumer-auth/authentication/oauth/overview) before they can register a Passkey with Stytch's API.
Additionally, Passkey registration requires the User to have authenticated with at least two factors if possible. Please refer to the [MFA SDK resource](/docs/api-reference/consumer/frontend-sdks/vanilla-js/resources/multi-factor-authentication) for more details.
Stytch provides [frontend SDK options](/docs/api-reference/consumer/frontend-sdks/vanilla-js/overview) for vanilla JavaScript, React, and Next.js. The essential steps to set up Passkeys are the same regardless of which SDK implementation you decide to use.
In this guide, we'll use the vanilla JavaScript SDK.
Install the [frontend SDK](/docs/api-reference/consumer/frontend-sdks/vanilla-js/overview) locally via npm or yarn.
```bash theme={null}
npm install @stytch/vanilla-js --save
```
The next step is to create a login page with two elements:
1. A `
```
After the initializing the Stytch Client, create a config object with the necessary auth settings. Refer to the [UI Config](/docs/api-reference/consumer/frontend-sdks/vanilla-js/prebuilt-ui/login/configuration) SDK reference for all available options and style customizations.
Since Passkeys cannot be used for signup, include [One-Time Passcodes](/docs/consumer-auth/authentication/otps/api) as an auth factor in the config object. Then mount the Login component by calling the stytch.mountLogin() method and passing in an object that has the config object and the DOM element id from **Step 2**.
```html theme={null}
```
With the login form working, create a callbacks object that redirects the user upon successful authentication to a logged-in location (like a home page, dashboard, or account page). You can wire these functions to trigger on the `PASSKEY_AUTHENTICATE` and `OTP_AUTHENTICATE` events.
Refer to the [UI callbacks](/docs/api-reference/consumer/frontend-sdks/react/prebuilt-ui/login/callbacks) SDK reference for a full list of events you can use for asynchronous operations.
Once defined, pass the callbacks object into `stytch.mountLogin()` method.
```html theme={null}
```
The `login.html` page should render a Login component UI. The email input field should trigger an **autofill** behavior for Passkey credentials when clicked on. Please make sure you're using a [supported browser](/docs/get-started/overview).
With the `login.html` page redirecting to `home.html` upon successful authentication, the next step is to build a page where users can register Passkeys after signing up.
Create a `home.html` page that initializes the Stytch Client and mounts the Passkey Registration component. The Passkey Registration component accepts all the same props as the Stytch Login component from **Steps 3 and 4**:
* config object.
* target DOM element id.
* callbacks object.
You'll need to also create a logout mechanism using the `stytch.session.revoke()` method.
The `home.html` page should have the following structure:
```html theme={null}
```
The `home.html` page should render a Passkey Registration component with this UI:
At this point, the `login.html` and `home.html` should have fully functional Login and Passkey Registration components. Now we can test the end-to-end flow for signing up and logging in to the application with Passkeys.
First, start on the `login.html` page and enter an email address to sign up with an Email OTP. You should receive an email with an OTP code. Complete the authentication flow and you should be redirected to `home.html` after receiving a 200 network response with a Session.
On the `home.html` page, click **Create a Passkey** on the Passkey Registration component. A browser dialog will appear with a prompt to create a Passkey with multiple options for storage such as:
* Mobile devices
* Tablets
* Laptops
* Security hardware like Yubikeys
* iCloud
* Chrome password manager
* Windows Hello
* 1Paswsword
The available options are determined by the end user's specific setup of devices, operating systems, and browsers. For example, someone who has a MacBook laptop with Chrome browser and 1Password installed may see the following dialogs appear for registering Passkeys:
Select any option and follow all prompted steps to create a Passkey. The end result should be a 200 network response with a Session and the Passkey Registration component displaying a success message:
Your User record should have a verified email from signing up with OTP in **Step 6** and a registered Passkey / WebAuthn factor from **Step 7**.
```js theme={null}
{
"user_id": "user-test-c872...",
"status": "active",
"created_at": "2023-11-21T17:05:05Z",
"emails": [
{
"email": "example@email.com",
"email_id": "email-test-8c...",
"verified": true
}
],
"webauthn_registrations": [
{
"authenticator_type": "",
"domain": "localhost",
"name": "WebAuthN Registration 531ca878",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"verified": true,
"webauthn_registration_id": "webauthn-registration-test-a71f6..."
}
],
"email": "example@email.com",
"phone_number": "",
"name": {
"first_name": "",
"last_name": "",
"middle_name": ""
},
"password": null,
"phone_numbers": [],
"providers": [],
"totps": [],
"trusted_metadata": {},
"untrusted_metadata": {}
}
```
Log out of the `home.html` page and revoke the session with the `logout()` function created in **Step 5**.
The final step is to log back in by clicking the email input field which should trigger the autofill and Passkeys browser dialog.
Select the same Passkey you created in **Step 7** which will successfully authenticate into the application.
## What's next
Clone the [React](https://github.com/stytchauth/stytch-passkey-react-node-example-app) or [Next.js](https://github.com/stytchauth/stytch-passkey-nextjs-example-app) example app to get hands-on with a Passkeys-powered application.
# Passkeys overview
Source: https://stytch.com/docs/consumer-auth/authentication/passkeys/overview
Passkeys are a biometrics-based, phishing-resistant, replacement for passwords.
They were developed by the [Fast Identity Online (FIDO)](https://fidoalliance.org/passkeys/), an alliance of cybersecurity experts dedicated to creating secure, frictionless authentication standards that eliminate the world's over-reliance on passwords.
## What are Passkeys exactly
Passkeys are FIDO credentials that utilize local biometric verifications like FaceID or TouchID to generate asymmetric public-private keys for authentication. The public key is registered and stored with the application, or relying party, the end user is authenticating into. The private key is stored on the device, or client, that was used for biometric verification.
During authentication, the client signs a message with its private key pair, which is protected by some local privileged verification like FaceID, TouchID, PIN, or swipe pattern. The message's signature is then verified by the relying party's server using the public key obtained during the registration.
Due to its cryptographic nature, Passkeys have a strong security posture. Not only is the private key never exposed, but they are also scoped to a single relying party. End users must create a Passkey per website.
## Passkeys are multi-device
Passkeys are stored in primarily two ways:
1. A passkey can be **device-bound** to a mobile device, computer, or an external device like a Yubikey.
2. A passkey can be **synced in the cloud** by a provider and easily accessible from any connected device.
With cloud support from platforms like Apple, Google, and Microsoft, and credential managers like 1Password, Passkeys can be synced and made available across multiple devices, operating systems, and browsers just like other cloud-synced features like a user's photos, contacts, emails, and documents.
In cases where a passkey is device-bound, users can still authenticate across multiple devices by utilizing a "hybrid protocol" or "hybrid transport", which can involve scanning a QR code with a mobile device and transmitting a one-time Passkey in close proximity via Bluetooth or connecting with a YubiKey via NFC or Bluetooth.
## Passkeys and WebAuthn
The two technologies are inherently related.
Passkeys are built on top of the [WebAuthn](https://www.w3.org/TR/webauthn-3/) standard, a web specification that defines the use of strong, attested, scoped, public key credentials by web applications. The term Passkeys refers to the multi-device usage of FIDO credentials, rather than the restricting of the private key to a single device.
In other words, Passkeys are multi-device credentials created by the WebAuthn standard.
## Stytch Passkeys
Stytch enables developers to implement [Passkeys](/docs/api-reference/consumer/api/passkeys-webauthn/register/start) while conveniently using the same [WebAuthn endpoints](/docs/api-reference/consumer/api/passkeys-webauthn/register/start). By integrating atop the WebAuthn API, developers have full flexibility and control when creating FIDO credentials.
Set the `return_passkey_credential_options` field to true when calling the [Start WebAuthn Registration endpoint](/docs/api-reference/consumer/api/passkeys-webauthn/register/start) or [Start WebAuthn Authentication endpoint](/docs/api-reference/consumer/api/passkeys-webauthn/authenticate/start).
```bash theme={null}
curl --request POST \
--url https://test.stytch.com/v1/webauthn/register/start \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6",
"domain": "example.com",
"return_passkey_credential_options": true
}'
```
## What's next
Check out our [SDK guide](/docs/consumer-auth/authentication/passkeys/login-sdk) to start building Passkeys into your application.
# Integrate Passwords via the API
Source: https://stytch.com/docs/consumer-auth/authentication/passwords/api
Implement Password signup, login, and reset flows with the Consumer API.
This guide walks through building password signup, login, and reset flows using the Consumer API.
Design a signup experience where the user sets a password. Then call [Create user with password](/docs/api-reference/consumer/api/passwords/create) with the email and password.
A successful call creates a user and can start a session when `session_duration_minutes` is provided.
Handle these errors:
* `duplicate_email`: A user with this email already exists.
* `weak_password` or `breached_password`: See the next step for guidance.
Password strength is determined by [zxcvbn](/docs/consumer-auth/authentication/passwords/strength-policy) (score 0–4) and by checking the password against the HaveIBeenPwned dataset. A score of `<= 2` yields a `weak_password` error; a leaked password yields a `breached_password` error.
Use Stytch's Strength check endpoint to provide actionable feedback to users.
Collect the user's email and password and call Authenticate user with password. A successful call authenticates the user and can start a session.
Handle these errors:
* `unauthorized_credentials` and `email_not_found`: Ask the user to try again.
* `reset_password`: The password must be reset (for example, due to a breached password).
Provide a reset password flow for users who forget their password or require a reset.
Use [Start email password reset](/docs/api-reference/consumer/api/overview) to begin the flow. If the account doesn't exist, you'll receive `email_not_found`.
Build a Reset Password page and add the URL as a Reset Password redirect in the [Stytch Dashboard](https://stytch.com/dashboard/redirect-urls). On that page, collect the new password and the token from the URL and call [Password reset by email](/docs/api-reference/consumer/api/overview).
For “Log in without password”, support [Email Magic Links](/docs/consumer-auth/authentication/magic-links/adding-magic-links-to-custom-auth-flow) and authenticate the token. This does not resolve a `reset_email` error.
You now have password signup, login, and reset flows. Have feedback after integrating? Reach out via [support@stytch.com](mailto:support@stytch.com) or in our [community Slack](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyF%5F~zHw).
# Email verification after password creation
Source: https://stytch.com/docs/consumer-auth/authentication/passwords/email-verification/after-password-creation
Verify email addresses after users create a password.
In this email verification flow, your users will first be asked to create a password and will then be prompted to verify their email address. One benefit of this flow is that you can still allow access to certain logged-in content before the user completes email verification, though some functionality (like adding a second factor) will be limited during this time period.
If you're more interested in a flow where email verification occurs *before* the user creates a password, see our [Email verification before creating a password](/docs/consumer-auth/authentication/passwords/email-verification/before-password-creation) guide instead.
Stytch's email verification flows are intentionally flexible so that you can choose the combination of products that best fits your use case. In this guide, we'll be using our [Email OTP product](/docs/api-reference/consumer/api/otp/via-email/send) in order to verify email addresses, but you may choose to use [Email Magic Links](/docs/api-reference/consumer/api/magic-links/via-email/send-magic-link) instead by replacing the below Email OTP endpoints with Email Magic Links endpoints.
Create a Stytch Consumer project via [the Stytch Dashboard](https://stytch.com/dashboard) if you don't have one already. To do so, click on your existing project name in top left corner of the Dashboard, click **Create Project**, and then select **Consumer Authentication**.
Copy your `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the [Project ID & API keys](https://stytch.com/dashboard) section of the Project Overview. You'll need to include these values in every backend Stytch API call.
First, prompt your user for their email address and ask them to create a password. Then, call our [Create User with Password endpoint](/docs/api-reference/consumer/api/passwords/create) with that information in order to create a new Stytch User.
Be sure to add a `session_duration_minutes` parameter to that call so that a new Stytch session is started. We'll use 30 minutes for the purposes of this guide, but feel free to tailor the session length to your own use case:
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/passwords \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email": "USER_EMAIL_ADDRESS",
"password": "NEW_PASSWORD_FROM_USER",
"session_duration_minutes": 30
}'
```
Save the `session_token` from the Create User with Password response for use in the next steps.
At this point, you'll have a new Stytch User with a password and an unverified email address, along with a newly-created Stytch session.
Send a verification email by calling our [Send one-time passcode by email endpoint](/docs/api-reference/consumer/api/otp/via-email/send). Include the `session_token` from the previous step – otherwise, the user will be required to reset their password in order to prevent an account takeover attack vector:
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/otps/email/send \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email": "USER_EMAIL_ADDRESS",
"session_token": "SESSION_TOKEN_FROM_STEP_1"
}'
```
Save the `email_id` from the Send one-time passcode by email response for use in the next step.
Once your user submits the one-time passcode from the email that they received, call our [Authenticate one-time passcode](/docs/api-reference/consumer/api/otp/authenticate) endpoint with the code and the `email_id` from the previous step (known in this next call as the `method_id`). As in the previous step, include the `session_token` from **Step 1** in your API call:
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/otps/authenticate \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"method_id": "EMAIL_ID_FROM_STEP_2",
"code": "ONE_TIME_PASSCODE_FROM_USER",
"session_token": "SESSION_TOKEN_FROM_STEP_1"
}'
```
## What's next
Upon receiving a successful Authenticate one-time passcode response, your Passwords signup flow with email verification is complete! You'll have a new Stytch User with both a verified email address and a password that your user will be able to use to authenticate in the future.
## See also
Check out our [Passwords API reference](/docs/api-reference/consumer/api/passwords/create) and our [One-time passcodes API reference](/docs/api-reference/consumer/api/otp/via-email/send), which also include sample code for all of our backend SDKs.
# Email verification before password creation
Source: https://stytch.com/docs/consumer-auth/authentication/passwords/email-verification/before-password-creation
Verify email addresses before users create a password.
In this email verification flow, your users will be asked to verify their email addresses before they're prompted to create a password. One benefit of this verification flow is that if the user mistypes their email address (and thus can't verify it), they'll be able to correct their mistake before setting a password.
If you're more interested in a flow where email verification occurs *after* the user creates a password, see our [Email verification after creating a password](/docs/consumer-auth/authentication/passwords/email-verification/after-password-creation) guide instead.
Stytch's email verification flows are intentionally flexible so that you can choose the combination of products that best fits your use case. In this guide, we'll be using our [Email OTP product](/docs/api-reference/consumer/api/otp/via-email/send) in order to verify email addresses, but you may choose to use [Email Magic Links](/docs/api-reference/consumer/api/magic-links/via-email/send-magic-link) instead by replacing the below Email OTP endpoints with Email Magic Links endpoints.
Create a Stytch Consumer project via [the Stytch Dashboard](https://stytch.com/dashboard) if you don't have one already. To do so, click on your existing project name in top left corner of the Dashboard, click **Create Project**, and then select **Consumer Authentication**.
Copy your `project_id` and `secret` for the **Test environment** you would like to use. These values can be accessed from the [Project ID & API keys](https://stytch.com/dashboard) section of the Project Overview. You'll need to include these values in every backend Stytch API call.
First, prompt your user for their email address, and then send them a verification OTP code using our [Log in or create User by email endpoint](/docs/api-reference/consumer/api/otp/via-email/login-or-create-user). Here's an example cURL request:
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/otps/email/login_or_create \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email": "USER_EMAIL_ADDRESS"
}'
```
Save the `email_id` from the Log in or create User response for use in the next step.
Once your user submits the one-time passcode from the email that they received, call our [Authenticate one-time passcode endpoint](/docs/api-reference/consumer/api/otp/authenticate) with the code and the `email_id` from the previous step (known in this next call as the `method_id`).
Be sure to add a `session_duration_minutes` parameter so that a new Stytch session is started. We'll use 30 minutes for the purposes of this guide, but feel free to tailor the session length to your own use case:
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/otps/authenticate \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"method_id": "EMAIL_ID_FROM_STEP_1",
"code": "ONE_TIME_PASSCODE_FROM_USER",
"session_duration_minutes": 30
}'
```
Save the `session_token` from the Authenticate one-time passcode response for use in the next step.
At this point, you'll have a new Stytch User with a verified email address. You'll now need to prompt your user to create a password and add it to the User via our [Password reset by existing session endpoint](/docs/api-reference/consumer/api/passwords/reset-options/password-reset-by-session), using the session that was created in the previous step:
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/passwords/session/reset \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"password": "NEW_PASSWORD_FROM_USER",
"session_token": "SESSION_TOKEN_FROM_STEP_2"
}'
```
Note that for security purposes, you'll need to complete this step **within 5 minutes** of creating the session in the previous step.
## What's next
Upon receiving a successful Password reset by existing session response, your Passwords signup flow with email verification is complete! You'll have a new Stytch User with both a verified email address and a password that your user will be able to use to authenticate in the future.
## See also
Check out our [Passwords API reference](/docs/api-reference/consumer/api/passwords/create) and our [One-time passcodes API reference](/docs/api-reference/consumer/api/otp/via-email/send), which also include sample code for all of our backend SDKs.
# Email verification overview
Source: https://stytch.com/docs/consumer-auth/authentication/passwords/email-verification/overview
Why email verification matters and how to verify addresses with Stytch Passwords.
While integrating with Stytch's Passwords product, it's good practice to implement a flow that either allows or requires your users to verify their email addresses during signup.
## Why verify users' email addresses?
Email verification isn't strictly required in order to use our Passwords product, but we strongly recommend doing so. There are many reasons why email verification is considered best practice, including:
* **Reducing the number of fraudulent accounts**: It's harder for bad actors to create fake accounts if they're required to verify the email address that they use to sign up for your application.
* **Reducing the number of mistyped email addresses**: If your users are required to verify their email addresses upon signup, you can be confident that they'll receive any future email communications from you.
* **Preventing an account takeover vector**: Stytch automatically deduplicates accounts by email address, but we can only safely do so without requiring a password reset if the user's email address has already been verified. Otherwise, a fraudulent actor could create an account using an email address that they do not own and then gain access to the legitimate owner of that email address's account should the legitimate owner sign up via an email-based authentication method in the future. We also do not allow the addition of a new email address to an existing Stytch User with an unverified email address in order to prevent this same account takeover vector.
In summary, we believe that requiring email verification is the right choice for most applications, and not doing so will limit certain Stytch functionality like automatic account deduplication and the ability to add new email addresses to existing accounts.
## How to determine if an email address is verified
To determine whether or not a user's email address has been verified, check the verified value inside the emails array in our [Get User endpoint](/docs/api-reference/consumer/api/users/get-user) response:
```js theme={null}
"emails": [
{
"email_id": "email-test-81bf03a8-86e1-4d95-bd44-bb3495224953",
"email": "test@example.com",
"verified": true
}
]
```
## How to implement an email verification flow
Stytch email verification logic is flexible, so you can customize your email verification flow to fit the specific needs of your application.
Any authentication flow where the user proves ownership of their email address can be used to verify your Stytch Users' email addresses. This includes our [Email Magic Links](/docs/api-reference/consumer/api/magic-links/via-email/send-magic-link), [Email OTP](/docs/api-reference/consumer/api/otp/via-email/send), and [OAuth](/docs/api-reference/consumer/api/oauth/start/google) products (as long as the OAuth provider itself [guarantees email verification](/docs/consumer-auth/authentication/oauth/email-behavior)). Successful completion of our [Password reset by email](/docs/api-reference/consumer/api/passwords/reset-options/password-reset-by-email) flow will also cause email addresses to be marked as verified.
Check out our [Email verification before password creation](/docs/consumer-auth/authentication/passwords/email-verification/before-password-creation) and [Email verification after password creation](/docs/consumer-auth/authentication/passwords/email-verification/after-password-creation) implementation guides that will introduce you to two different email verification flows and will provide step-by-step implementation instructions for each.
# Add Passwords to Stytch Login
Source: https://stytch.com/docs/consumer-auth/authentication/passwords/sdk
Add Password signup, login, and reset flows with the Consumer frontend SDKs.
Use the pre-built Stytch Login UI to handle password signup, login, and reset flows in your frontend SDK.
1. Create **Login** and **Reset Password** redirect URLs on the [redirect URL configuration page](https://stytch.com/dashboard/redirect-urls).
2. In [Frontend SDK](https://stytch.com/dashboard/sdk-configuration), authorize your domain and enable **Passwords** and **Email magic links → LoginOrCreate**.
3. From [Project ID & API keys](https://stytch.com/dashboard), copy your `public_token`.
Install the SDK packages and initialize the client with your `public_token` at the top level of your app.
Create a component for your signup/login page and render the Stytch UI with a config object. You'll need the **Login** and **Password Reset** redirect URLs you configured in Step 1. See the [config reference](/docs/api-reference/consumer/frontend-sdks/vanilla-js/prebuilt-ui/login/configuration).
Build a Reset Password page for the link in the reset email. Add that page as a Reset Password redirect in your Dashboard, then render the password reset UI there.
The reset email also includes a "Login without a password" option powered by [Magic Links](/docs/consumer-auth/authentication/magic-links/overview). Capture the token from the Login redirect URL and authenticate it.
You now have password signup, login, and reset flows with magic link fallback. The SDK manages sessions automatically. Learn more about sessions [here](/docs/consumer-auth/manage-sessions/overview).
Have feedback after integrating? Reach out via [support@stytch.com](mailto:support@stytch.com) or in our [Slack community](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw).
# Password strength policy
Source: https://stytch.com/docs/consumer-auth/authentication/passwords/strength-policy
Configure password strength rules with zxcvbn, LUDS, and breach detection.
Stytch supports two password strength strategies, [zxcvbn](https://github.com/dropbox/zxcvbn) and Lowercase Uppercase Digits Special Characters (LUDS). On top of these two strategies, you can also enable breach detection, powered by [HaveIBeenPwned](https://haveibeenpwned.com/) to protect your users from password breaches.
You may choose any mix of these strategies for your Stytch Project. For example, you may choose to use LUDS for password complexity plus breach detection, or zxcvbn without breach detection.
By default, our Passwords product uses zxcvbn for password strength and automatically protects your users against password breaches. If that doesn't fit your needs, you may configure your desired strategy by creating a custom policy [in the Dashboard](https://stytch.com/dashboard/password-strength-config) or [via the API](/docs/api-reference/pwa/api/v3/password-strength-config/set-password-strength-config).
Passwords at Stytch may contain any UTF8 character, e.g. spaces, emojis, non-English characters, etc. Any of these types of characters may contribute to fulfilling the complexity requirements you choose.
## zxcvbn
[zxcvbn](https://github.com/dropbox/zxcvbn) is a password strength estimation library developed originally by Dropbox. It is a complex password strength strategy that checks for the presence of dictionary words, common passwords, and common patterns. It does not explicitly check for lowercase letters, uppercase letters, digits, or special characters.
Some end users find zxcvbn to be too strict, and may be unable to create a password that meets the requirements. If you find that your users are unable to create a password that meets the requirements, you may want to consider using LUDS instead.
You can find examples of zxcvbn passwords and an interactive tool to test passwords at this hosted [zxcvbn checker](https://lowe.github.io/tryzxcvbn/).
### Example
If a user provides a commonly used password like, "p\@ssword1", the response from our [Strength check endpoint](/docs/api-reference/consumer/api/passwords/strength-check) will look like the following:
```json theme={null}
{
"breach_detection_on_create": true,
"breached_password": false,
"feedback": {
"luds_requirements": null,
"suggestions": [
"Add another word or two. Uncommon words are better.",
"Predictable substitutions like '@' instead of 'a' don't help very much."
],
"warning": "This is a top-100 common password."
},
"request_id": "request-id-test-86779128-d4e7-4dd4-a588-88faa570f638",
"score": 0,
"status_code": 200,
"strength_policy": "zxcvbn",
"valid_password": false
}
```
You may use the warning and suggestions fields to provide feedback directly to your users to help them craft a stronger password that will pass your requirements.
## LUDS
LUDS stands for Lowercase Uppercase Digits Special Characters. It is a simple password strength strategy that checks for the presence of lowercase letters, uppercase letters, digits, and special characters. It does not check for dictionary words or common passwords.
### Example
For this example, we will use the following password strength policy:
* Strength policy: LUDS
* LUDS minimum length: 8
* LUDS complexity: 4
If a user provides a password of, "Lorem1!", the password will be considered invalid. The password contains the necessary complexity, symbols, numbers, and upper and lower case letters, but is not of sufficient length. The response from our [Strength check endpoint](/docs/api-reference/consumer/api/passwords/strength-check) would look like the following:
```json theme={null}
{
"breach_detection_on_create": true,
"breached_password": false,
"feedback": {
"luds_requirements": {
"has_digit": true,
"has_lower_case": true,
"has_symbol": true,
"has_upper_case": true,
"missing_characters": 2,
"missing_complexity": 0
},
"suggestions": null,
"warning": null
},
"request_id": "request-id-test-86779128-d4e7-4dd4-a588-88faa570f638",
"score": 0,
"status_code": 200,
"strength_policy": "luds",
"valid_password": false
}
```
You may use the `missing_characters` and `missing_complexity` fields to provide feedback to your users to help them craft a stronger password that will pass your requirements.
## How to set a password strength policy
You may set or update the password strength policy for your Stytch Project via the [Passwords Policy page](https://stytch.com/dashboard/password-strength-config) in the Stytch Dashboard.
### Password strength policy options
* Strength policy type: The type of password validation policy to use - either one of our default zxcvbn policies or a custom policy using LUDS.
* Minimum password length: When using a custom LUDS policy, the minimum number of characters required for a password. We require a minimum of at least 8 characters.
* Number of character types: When using a custom LUDS policy, the minimum number of character types required for a password. Allowable values are 1-4. This value determines how many LUDS checks must be passed - for example, 2 means that a password with a symbol and a number is valid because two of the four LUDS requirements are met. You cannot choose *which* LUDS requirements are required, only how many.
* Password strength enforcement during authentication: Determines whether or not password strength checks are applied during password authentication. During user migrations from another service with a password policy that is incompatible with Stytch's password strength policy options, you may choose to disable password strength checks during authentication.
* Breach detection: Determines whether passwords are checked against the HaveIBeenPwned dataset during password creation and authentication, during password creation only, or never.
## Breach detection
Stytch’s Passwords product automatically protects your users against password breaches. We do this by checking the password against a list of known passwords that have been leaked in data breaches from [HaveIBeenPwned](https://haveibeenpwned.com/). If the password is found in this list, we will reject the password and ask the user to choose a different one or reset their password if they have already used it previously.
Protecting your users against password breaches is a critical part of your security strategy. Password breaches are a common attack vector for hackers, and can be used to gain access to your users’ accounts and compromise your app.
### Enabling breach detection
Breach detection can be configured on the [Password and user policies](https://stytch.com/dashboard/password-strength-config) page in the Stytch Dashboard, and can be enabled during password creation, logins, or both.
If configured to detect breaches on logins, you must also enable **Require password reset for non-compliant passwords** under the **Enforce password policy** section in order for breaches to be detected during password authentication.
# Integrate TOTPs via the API
Source: https://stytch.com/docs/consumer-auth/authentication/totps/api
Implement TOTP enrollment and verification with the Consumer API.
Time-based one-time passcodes (TOTP) provide a second factor using a shared secret with an authenticator app. Users enter a time-based code (typically 30 seconds), and your backend verifies it with Stytch.
If the user isn't already associated with a Stytch `user_id`, create a User with the `/users/create` endpoint. Store the returned `user_id` so you can enroll and authenticate the user with TOTP.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
}
);
const params = {
email: "sandbox@stytch.com",
};
client.users.create(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
Create a TOTP by passing the `user_id` into `/totps/create`. The `expiration_minutes` value sets how long the enrollment is valid; the user must authenticate at least once before it expires.
The response includes `secret`, `totp_id`, `qr_code`, and `recovery_codes`. You can embed the `qr_code` to display a scannable QR code, and store recovery codes for account recovery.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
}
);
const params = {
user_id: "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6",
expiration_minutes: 60
};
client.totps.create(params)
.then(resp => { console.log(resp) })
.catch(err => { console.log(err) });
```
Prompt the user to enter a TOTP code, then call `/totps/authenticate` with the `user_id` and `totp_code`. This endpoint allows codes from the previous and next 30-second windows to accommodate delays.
You can optionally pass `session_duration_minutes` (new session) or `session_token` (reuse session). See the [session management guide](/docs/consumer-auth/manage-sessions/overview) for more details.
```js theme={null}
const stytch = require("stytch")
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
env: stytch.envs.test,
}
);
const params = {
user_id: "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6",
totp_code: "576831"
};
client.totps.authenticate(params)
.then(resp => { console.log(resp) })
.catch(err => { console.log(err) });
```
You now have a second factor of authentication via TOTP. Have feedback after integrating? Reach out via [support@stytch.com](mailto:support@stytch.com) or in our [community Slack](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyF%5F~zHw).
# Time-based one-time passcodes (TOTPs) overview
Source: https://stytch.com/docs/consumer-auth/authentication/totps/overview
Understand how TOTP works and when to use it as a second factor.
Time-based one-time passcodes (TOTP) add a second factor by requiring users to enter a short-lived code from an authenticator app. This is ideal for high-risk actions like money movement or account changes where you want stronger assurance than SMS alone.
## How TOTP works
1. Your app enrolls the user and displays a QR code.
2. The user scans it with an authenticator app (like Google Authenticator or Authy).
3. The app generates a time-based code every \~30 seconds, which your backend verifies.
## Next steps
Create, enroll, and authenticate TOTPs from your backend.
# Custom auth factors
Source: https://stytch.com/docs/consumer-auth/authentication/trusted-auth-tokens/custom-factors
Use your own JWTs to attest custom authentication factors with Trusted Auth Tokens.
Trusted Auth Tokens let you represent custom authentication factors in Stytch by signing your own JWTs and exchanging them for sessions.
Typically, your existing identity infrastructure will provide a JWT that can be used for this purpose such as an `access_token` or `id_token`. Stytch will use the JWKS endpoint hosted by your existing infrastructure to validate these JWTs and provision users automatically as required -- no additional setup is needed.
If your identity infrastructure does not provide a JWT, you can create your own JWT using a private key.
## Minting a JWT and logging in
Use a library such as OpenSSL to generate an RSA public-private keypair.
The public key can be added to your Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/dashboard/trusted-auth-tokens); (in the JWKS URL row, click "Use public keys instead").\
The private key should be kept secret, and exposed to your application using an environment variable.\
It is often useful to base64 encode the private key for easy addition to `.env` files.
```sh theme={null}
# Generate private key
openssl genrsa -out "private_key.pem" 2048
# Extract public key
openssl rsa -in "private_key.pem" -pubout -out "public_key.pem"
# Base64 encode the private key
base64 < ./private_key.pem
```
Use any compatible JWT library to sign your JWT with your private key.\
Here is an example using [jose](https://github.com/panva/jose), a popular library in the Node ecosystem.\
Be sure to set the `iss` and `aud` claims to match the issuer and audience configured in your Trusted Auth Token Profile.\
Choose a short expiry for the JWT - 5 minutes is a good default.
Other claims (`email`, `sub`, `role_ids`, `jti`, etc... ) should be set according to your previously configured [attribute mapping](/docs/multi-tenant-auth/authentication/trusted-auth-tokens/overview#setting-up-a-trusted-auth-token-profile).\
Remember, an email and a token ID are required. All other fields are optional.
```js theme={null}
import { importPKCS8, SignJWT } from 'jose';
import { randomBytes } from 'node:crypto';
async function createJWT({email, role_assignments, user_id, token_id}) {
// Expose the private key as an environment variable
// If you base64 encoded it before, remember to decode it here as well
const privateKeyString = Buffer.from(process.env.PRIVATE_KEY, 'base64')
.toString('utf8').trim();
const privateKey = await importPKCS8(privateKeyString, 'RS256');
// details to encode in the token
const claims = {
// Required - the user's email address
'email': email,
// Optional - RBAC roles to assign to the user
'assignments': role_assignments,
};
// Required - a token ID used to uniquely identify the credential
// This can be defined by your application or a random string
const tokenID = token_id || randomBytes(30).toString('base64');
const jwt = await new SignJWT(claims)
.setProtectedHeader({ alg: 'RS256' })
.setIssuedAt()
.setSubject(user_id) // sub claim, used as the user's external ID
.setJti(tokenID)
.setIssuer('https://auth.example.com') // iss claim
.setAudience('https://api.example.com') // aud claim
.setExpirationTime('5 minutes') // token expiration time
.sign(privateKey);
return jwt;
};
createJWT({
email: 'ada.lovelace@example.com',
role_assignments: ['editor', 'reader'],
user_id: 'user_123456'
}).then(jwt => console.log(jwt))
```
After you have minted your JWT, call the [Attest Session](/docs/api-reference/consumer/api/sessions/attest-session) API endpoint to exchange the JWT for a Stytch Session.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.Client({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
createJWT({
email: 'ada.lovelace@example.com',
role_assignments: ['editor', 'reader'],
user_id: 'user_123456'
})
.then(jwt => {
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...",
};
return client.sessions.attest(params)
})
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
## Adding custom auth factors
To add custom auth factors to an existing flow, call the [Attest Session](/docs/api-reference/consumer/api/sessions/attest-session) API endpoint with a `session_token` or `session_jwt` to attach the attestation to an existing session instead of creating a new one.
It may be useful to pass in a unique `token_id` to the JWT to tie the custom authentication factor you validated to the session for later reference.
```js theme={null}
validateCustomFactor()
.then(validation => createJWT({
email: 'ada.lovelace@example.com',
role_assignments: ['editor', 'reader'],
user_id: 'user_123456',
// ID of the validation event - will be passed to the Stytch session as
// trusted_auth_token_factor.token_id
token_id: validation.validation_id,
}))
.then(jwt => {
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...",
// Pass a session token in to add your custom factor to an existing session
session_token: "SESSION_TOKEN",
};
return client.sessions.attest(params)
})
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
# External identity providers
Source: https://stytch.com/docs/consumer-auth/authentication/trusted-auth-tokens/external-idps
Exchange third-party JWTs for Stytch sessions using Trusted Auth Tokens.
Trusted Auth Tokens let you attest end-user identities by exchanging signed JWTs for Stytch sessions.
Many existing identity infrastructure tools will provide a JWT that can be used for this purpose, such as an
`access_token` or `id_token`.
Stytch will use the JWKS endpoint hosted by your existing infrastructure to validate these JWTs.
This guide uses Supabase as an example, but the same pattern works for any provider with a public JWKS.
## Logging a user in
[In the Stytch Dashboard](https://stytch.com/dashboard/trusted-auth-tokens), create a Trusted Auth Token Profile for your external provider. In order to validate JWTs from Supabase, set the following values:
| Name | Value |
| - | - |
| Issuer | `https://$project-id.supabase.co/auth/v1/` |
| Audience | `authenticated` |
| JWKS | `https://$project-id.supabase.co/auth/v1/.well-known/jwks.json` |
Add an attribute mapping for the claims you want to import. To map Supabase user information to Stytch, set the following:
| Name | Value |
| - | - |
| email | `email` |
| token\_id | `session_id` |
After you mint or retrieve the provider JWT, send it to your backend so it can be exchanged for a Stytch session. For Supabase, use the [Supabase SDK](https://supabase.com/docs/reference/javascript/auth-getsession) to retrieve an access token JWT:
```js theme={null}
// On the frontend
const { data } = await supabase.auth.getSession()
fetch('/api/exchange-supabase-session', {
method: 'POST',
data: JSON.stringify({ access_token: data.session.access_token })
})
```
Use the [Attest Session endpoint](/docs/api-reference/consumer/api/sessions/attest-session) to exchange the token for a Stytch session.
```js theme={null}
// On the backend
const client = new stytch.Client({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...", // access token from Supabase
};
client.sessions.attest(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
You now have a Stytch session linked to the external user identity.
# Trusted auth tokens overview
Source: https://stytch.com/docs/consumer-auth/authentication/trusted-auth-tokens/overview
Exchange signed JWTs for Stytch sessions to support external identity providers and custom auth factors.
Trusted Auth Tokens let you attest end-user identities by exchanging signed JWTs for Stytch sessions. Use them to integrate external identity providers or custom authentication factors without building bespoke OIDC/OAuth support.
## Use cases
Trusted auth tokens support a range of powerful patterns:
* **3rd-party SSO integrations**: Exchange external identity provider tokens (like Vercel or Zendesk) for Stytch sessions.
* **Bring-your-own-auth**: Accept JWTs your product already issues.
* **Custom auth factors**: Add external factors (biometrics, device attestation) and represent them in the Stytch session.
***
## How it works
Define the JWT issuer and audience, add public key(s) or a JWKS URL, and map JWT claims to Stytch attributes.
Mint a JWT in your backend or accept one from a 3rd-party identity provider.
Call the [Attest Session endpoint](/docs/api-reference/consumer/api/sessions/attest-session) to create or extend a Stytch session.
### Configuring a Trusted Auth Token profile
In the Stytch Dashboard, navigate to the [Trusted Auth Tokens](https://stytch.com/dashboard/trusted-auth-tokens) page.
Here you can create a new Trusted Auth Token Profile for the provider of the tokens that you want to attest, or view and edit existing profiles.
The issuer (`iss`) and audience (`aud`) should match the corresponding values in the JWTs that you are trying to attest.
Attribute mappings are used to tie per-user claims within the JWT to Stytch platform attributes.
The following attribute mappings are available today:
| Attribute | Required | Purpose |
| - | - | - |
| email | Yes | The email address of the user identified by the JWT |
| token\_id | Yes | A unique identifier for the JWT |
| external\_user\_id | No | Optional [external user ID](/docs/resources/migrations/external-ids) to attach to the user |
| role\_ids | No | Array of RBAC [Role IDs](/docs/consumer-auth/authorization/create-rbac-policy) to assign |
### JIT provisioning
By default, Trusted Auth Tokens cannot be used to create new users, and can only authenticate existing users.\
To allow tokens to create new users, enable **Allow JIT Provisioning** in the dashboard.
### Exchanging a Trusted Auth Token for a Session
Once you have a profile set up for the source of your trusted auth tokens, you can use the backend API to exchange a
token for a Stytch session, or add it as an auth factor for an existing session using
the [Attest Session](/docs/api-reference/consumer/api/sessions/attest-session) API endpoint.
The API endpoint is available in all Stytch Backend and Frontend SDKs.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.Client({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
const params = {
profile_id: "trusted-auth-token-profile-...",
token: "eyJhb...",
session_duration_minutes: 60,
};
client.sessions.attest(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
Attested sessions are not mutable and cannot have new authentication factors added, and cannot have their duration extended.
## Putting it all together
Suppose you configure a Trusted Auth Token Profile with an issuer of `https://auth.example.com`, an audience of
`https://api.example.com` and the following attribute mapping:
* `email`: `email`
* `token_id`: `jti`
* `external_user_id`: `sub`
* `roles`: `assignments`
A JWT with the following claims:
```json theme={null}
{
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"sub": "user_123456",
"email": "ada.lovelace@example.com",
"jti": "tok_654321",
"assignments": [
"editor",
"reader"
]
}
```
Can be exchanged to log in or create a User with the following properties:
```json theme={null}
{
"user_id": "user-...",
"external_id": "user_123456",
"emails": [
{
"email": "ada.lovelace@example.com",
"email_id": "email-...",
"verified": false
}
],
"roles": [
"stytch_user",
"editor",
"reader"
]
}
```
as well as a User session with the following properties:
```json theme={null}
{
"user_id": "user-...",
"authentication_factors": [
{
"delivery_method": "trusted_token_exchange",
"trusted_auth_token_factor": {
"token_id": "tok_654321"
}
}
]
}
```
***
## What's next
* Accept credentials from an [External Identity Provider](/docs/consumer-auth/authentication/trusted-auth-tokens/external-idps)
* Create Trusted Auth Tokens for [custom auth factors](/docs/consumer-auth/authentication/trusted-auth-tokens/custom-factors)
* Add [custom claims](/docs/consumer-auth/manage-sessions/custom-claims) to session JWTs
# User impersonation
Source: https://stytch.com/docs/consumer-auth/authentication/user-impersonation
Learn about Stytch's built-in user impersonation functionality.
Stytch offers user impersonation functionality that allows you to log into your application as a specific end user
in order to troubleshoot and debug securely.
## Enabling user impersonation for your Stytch project
User impersonation is disabled by default for each of your Stytch projects. In order to enable it, a member of your team with the
Admin role in the Stytch Dashboard should navigate to the [Password and User Policies page](https://stytch.com/dashboard/password-strength-config)
and toggle on the **Allow user impersonation** setting. Be sure to click **Save** after modifying the setting.
## Generating a user impersonation token
Once user impersonation has been enabled for your project, team members with the Admin, Developer, or Support Manager [roles](/docs/resources/workspace-management/dashboard-roles-and-permissions)
can begin the user impersonation flow by selecting the User they'd like to impersonate on the [Users page](https://stytch.com/dashboard/user-management),
scrolling down, and clicking **Impersonate user**.
Your team member will be asked to provide a reason for impersonating the user, which will be available later on for auditing purposes. They'll then click
**Launch in new tab**, which initiates the user impersonation flow in a new browser tab at your project's default login redirect URL
with an impersonation token appended to the query parameters. Here's an example:
```
https://yourdomain/authenticate?stytch_token_type=impersonation&token=O3c65CBJXdCrej_PdwuNM7npoUzzCBVKgIxxpqMpB1oF
```
Note that it is **not** possible to generate a user impersonation token programmatically. If that is a requirement for your
use case, see the [Programmatic user impersonation](/docs/consumer-auth/authentication/user-impersonation#programmatic-user-impersonation) section below.
## Authenticating a user impersonation token
Your application should then retrieve the impersonation token from your login redirect URL's query parameters and authenticate it via the
[Authenticate impersonation token endpoint](/docs/api-reference/consumer/api/impersonation/authenticate-token).
If you're using our frontend JavaScript SDK, you can authenticate the token via the [authenticateByUrl method](/docs/api-reference/consumer/frontend-sdks/vanilla-js/methods/impersonation/authenticate).
However, you'll first need to enable the **Authenticate user impersonation tokens** setting on the [Frontend SDK Configuration page](https://stytch.com/dashboard/sdk-configuration) in the Stytch Dashboard.
At this point, you'll receive a new Stytch Session for the target user. Stytch Sessions created through user impersonation always have a duration of one hour, and can be explicitly revoked via the
[Revoke Session endpoint](/docs/api-reference/consumer/api/sessions/revoke-session). They contain a single factor in their `authentication_factors` array of type `impersonated` that includes the impersonator's email address.
Here's an example Stytch Session that was created through user impersonation:
```json theme={null}
"session": {
"attributes": {...},
"authentication_factors": [
{
"created_at": "2026-04-28T16:14:26Z",
"delivery_method": "impersonation",
"impersonated_factor": {
"impersonator_email_address": "email@example.com",
"impersonator_id": "member-live-..."
},
"last_authenticated_at": "2026-04-28T16:14:26Z",
"type": "impersonated",
"updated_at": "2026-04-28T16:14:26Z"
}
],
"custom_claims": {...},
"expires_at": "2026-04-28T17:14:26Z",
"last_accessed_at": "2026-04-28T16:15:27Z",
"roles": [...],
"session_id": "session-live-...",
"started_at": "2026-04-28T16:14:26Z",
"user_id": "user-live-..."
}
```
Sessions created through user impersonation can be authenticated via the [Authenticate session](/docs/api-reference/consumer/api/sessions/authenticate-session)
endpoints or SDK methods using the `session_token` or `session_jwt` values from the Authenticate user impersonation token response.
## Audit logging
After a user impersonation token is authenticated, you'll find an event in the Stytch Dashboard's [Activity tab](https://stytch.com/dashboard/activity) with
an `action` value of `AuthenticateImpersonationToken`. This event will contain additional details about the user impersonation,
such as the `user_id`, the impersonator's email address, and the reason that the impersonator provided for the
impersonation. If you take advantage of our [Event log streaming](/docs/resources/workspace-management/event-log-streaming) feature,
the user impersonation logs are available there as well.
We also emit a [webhook](https://stytch.com/dashboard/webhooks) with an event type of `direct.user.impersonate` whenever
a User is impersonated.
## Programmatic user impersonation
For security purposes, we do not provide an API endpoint that generates user impersonation tokens. However, programmatic user
impersonation may be desirable for some use cases.
If you understand and accept the security implications, it is possible to use our Trusted Auth Tokens feature to programmatically
generate a Stytch Session on behalf of a User. See our [Custom auth factors](/docs/consumer-auth/authentication/trusted-auth-tokens/custom-factors)
guide for implementation instructions.
If you choose to use Trusted Auth Tokens for user impersonation, you will be responsible for maintaining your own access controls, audit logs,
and other security guardrails.
If implemented improperly or without sufficient protections against misuse, user impersonation can introduce serious security
risks in your application.
# Integrate WebAuthn via the API
Source: https://stytch.com/docs/consumer-auth/authentication/webauthn/api
Register and authenticate WebAuthn credentials with the Consumer API.
WebAuthn has two phases: registration and authentication. Registration attaches a WebAuthn device to a user; authentication verifies a login attempt using that device.
This guide describes how to implement both WebAuthn registration and authentication when using a backend Stytch integration. It also provides simplified example code for both the backend and frontend pieces of a WebAuthn integration.
We also recommend checking out our backend WebAuthn example app to accompany this guide:
* [Hosted demo](https://www.stytchdemo.com/recipes/api-webauthn)
* [WebAuthn registration code](https://github.com/stytchauth/stytch-nextjs-integration/blob/7d2f2b8575d300900419cd4808305788db38b5f3/components/EmailWebAuthn/WebAuthnRegister.tsx)
* [WebAuthn authentication code](https://github.com/stytchauth/stytch-nextjs-integration/blob/7d2f2b8575d300900419cd4808305788db38b5f3/components/EmailWebAuthn/WebAuthnAuthenticateButton.tsx)
If the user attempting to register isn't yet associated with a Stytch `user_id`, you'll have to create a new Stytch User via the [Create User endpoint](/docs/api-reference/consumer/api/users/create-user). The resulting `user_id` will be used to register a new WebAuthn device.
```js theme={null}
const stytch = require("stytch");
const client = new stytch.Client({
project_id: "${projectId}",
secret: "${secret}",
}
);
const params = {
email: "${user_email}",
};
client.users.create(params)
.then(resp => {
console.log(resp)
})
.catch(err => {
console.log(err)
});
```
To authenticate with WebAuthn, you first need to register a WebAuthn device.
First, you'll make a request to the [Start WebAuthn registration](/docs/api-reference/consumer/api/passkeys-webauthn/register/start) endpoint. You need two fields for the request: a Stytch `user_id` and your login page's domain. When using built-in browser methods like `navigator.credentials.create()` and `navigator.credentials.get()`, as we will in this guide, you'll also need to set the `use_base64_url_encoding` option to `true`.
Next, you'll create a public key using the `public_key_credential_creation_options` from the Start WebAuthn registration response and use that to call the browser's built-in `navigator.credentials.create()` method.
If the `navigator.credentials.create()` call is successful, pass the resulting public key credential into our [Register WebAuthn](/docs/api-reference/consumer/api/passkeys-webauthn/register/register) endpoint.
Here is simplified backend and frontend example code demonstrating the full WebAuthn registration flow:
```js theme={null}
// Backend code
export async function callWebauthnRegisterStart() {
const response = await stytchClient.webauthn.registerStart({
user_id: "${userId}",
domain: "${exampleDomain}",
use_base64_url_encoding: true,
});
return response.json();
}
export async function callWebauthnRegister(public_key_credential) {
const response = await stytchClient.webauthn.register({
user_id: "${userId}",
public_key_credential: public_key_credential,
});
return response.json();
}
```
```js theme={null}
// Frontend code
const webAuthnRegisterStartResponse = await callWebauthnRegisterStart();
const options = webAuthnRegisterStartResponse.public_key_credential_creation_options;
const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(JSON.parse(options));
const credential = (await navigator.credentials.create({publicKey})) as PublicKeyCredential;
await callWebauthnRegister(public_key_credential: JSON.stringify(credential.toJSON()))
.then(resp => { /* WebAuthn authenticator successfully registered */ })
.catch(err => { /* Registration error */ });
```
Now that user has an active WebAuthn registration, you can use it for authentication.
First, you'll make a request to the [Start WebAuthn authentication](/docs/api-reference/consumer/api/passkeys-webauthn/authenticate/start) endpoint. Similarly to in **Step 2**, when using built-in browser methods, you'll need to set the `use_base64_url_encoding` option to `true`.
Next, you'll create a public key using the `public_key_credential_request_options` from the Start WebAuthn authentication response and use that to call the browser's built-in `navigator.credentials.get()` method.
If the `navigator.credentials.get()` call is successful, pass the resulting public key credential into our [Authenticate WebAuthn](/docs/api-reference/consumer/api/passkeys-webauthn/authenticate/authenticate) endpoint. If the Authenticate WebAuthn call succeeds, your user has successfully authenticated.
Here is simplified backend and frontend example code demonstrating the full WebAuthn authentication flow:
```js theme={null}
// Backend code
export async function callWebauthnAuthenticateStart() {
const response = await stytchClient.webauthn.authenticateStart({
domain: "${exampleDomain}",
use_base64_url_encoding: true,
});
return response.json();
}
export async function callWebauthnAuthenticate(public_key_credential) {
const response = await stytchClient.webauthn.authenticate({
public_key_credential: public_key_credential,
session_duration_minutes: 60,
})
return response.json();
}
```
```js theme={null}
// Frontend code
const webAuthnAuthenticateStartResponse = await callWebauthnAuthenticateStart();
const options = webAuthnAuthenticateStartResponse.public_key_credential_request_options;
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(JSON.parse(options));
const credential = (await navigator.credentials.get({publicKey})) as PublicKeyCredential;
await callWebauthnAuthenticate(JSON.stringify(credential.toJSON()))
.then(resp => { /* User has successfully authenticated */ })
.catch(err => { /* Authentication error */ });
```
In the above registration and authentication steps, we serialized the public key credentials by simply calling `credential.toJSON()`. This works in most cases, but there are some known incompatibilities with certain password managers and the public key credential's `toJSON()` method.
To avoid these incompatibilities, you can manually serialize the public key credentials instead of calling `credential.toJSON()`. Public key credential serialization code will look something like this, where the `SerializedAttestationCredential` is used in the WebAuthn registration request and the `SerializedAssertionCredential` is used in the WebAuthn authentication request:
```js theme={null}
export interface SerializedAttestationCredential {
id: string;
rawId: string;
type: string;
response: {
clientDataJSON: string;
attestationObject: string;
};
authenticatorAttachment?: "platform" | "cross-platform";
}
export function serializeAttestationCredential(
credential: PublicKeyCredential,
): SerializedAttestationCredential {
const response = credential.response as AuthenticatorAttestationResponse;
return {
id: credential.id,
rawId: base64urlEncode(credential.rawId),
type: credential.type,
response: {
clientDataJSON: base64urlEncode(response.clientDataJSON),
attestationObject: base64urlEncode(response.attestationObject),
},
authenticatorAttachment:
(credential as any).authenticatorAttachment ?? undefined,
};
}
export interface SerializedAssertionCredential {
id: string;
rawId: string;
type: string;
response: {
clientDataJSON: string;
authenticatorData: string;
signature: string;
userHandle: string | null;
};
authenticatorAttachment?: "platform" | "cross-platform";
}
export function serializeAssertionCredential(
credential: PublicKeyCredential,
): SerializedAssertionCredential {
const response = credential.response as AuthenticatorAssertionResponse;
return {
id: credential.id,
rawId: base64urlEncode(credential.rawId),
type: credential.type,
response: {
clientDataJSON: base64urlEncode(response.clientDataJSON),
authenticatorData: base64urlEncode(response.authenticatorData),
signature: base64urlEncode(response.signature),
userHandle: response.userHandle
? base64urlEncode(response.userHandle)
: null,
},
authenticatorAttachment:
(credential as any).authenticatorAttachment ?? undefined,
};
}
function base64urlEncode(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
}
```
Here's updated frontend registration and authentication code that uses the above manual serialization methods instead of `toJSON()`:
```js theme={null}
// Registration
const webAuthnRegisterStartResponse = await callWebauthnRegisterStart();
const options = webAuthnRegisterStartResponse.public_key_credential_creation_options;
const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(JSON.parse(options));
const credential = (await navigator.credentials.create({publicKey})) as PublicKeyCredential;
const serializedCredential = serializeAttestationCredential(
credential as PublicKeyCredential,
);
await callWebauthnRegister(JSON.stringify(serializedCredential))
.then(resp => { /* WebAuthn authenticator successfully registered */ })
.catch(err => { /* Registration error */ });
```
```js theme={null}
// Authentication
const webAuthnAuthenticateStartResponse = await callWebauthnAuthenticateStart();
const options = webAuthnAuthenticateStartResponse.public_key_credential_request_options;
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(JSON.parse(options));
const credential = (await navigator.credentials.get({publicKey})) as PublicKeyCredential;
const serializedCredential = serializeAssertionCredential(
credential as PublicKeyCredential,
);
await callWebauthnAuthenticate(JSON.stringify(serializedCredential))
.then(resp => { /* User has successfully authenticated */ })
.catch(err => { /* Authentication error */ });
```
# Assign roles to users
Source: https://stytch.com/docs/consumer-auth/authorization/assigning-roles-to-users
Assign roles to Users to grant permissions in your application.
When a User is assigned a role, they are granted permission to perform the actions that role allows. Roles are additive, so Users receive the combined permissions of all roles they are assigned.
## Explicit role assignment
**Explicit role assignment** is when you directly assign a role to (or revoke from) a specific User. Once assigned, the User has that role until it is explicitly revoked.
You can assign roles when making calls to the following API endpoints:
* [Create User](/docs/api-reference/consumer/api/users/create-user)
* [Update User](/docs/api-reference/consumer/api/users/update-user)
* [Password Migrate](/docs/api-reference/consumer/api/passwords/migrate)
Or manage roles in the [Stytch Dashboard](https://stytch.com/dashboard/user-management).
### Example: assign a role via Update User
```curl theme={null}
curl --request PUT \
--url https://test.stytch.com/v1/users/user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6 \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"roles": ["admin"]
}'
```
Consumer RBAC supports explicit role assignment. If you need rule-based or attribute-based assignment, implement those rules in your application and apply roles via the Update User endpoint.
## What's next
Check out the [enforcement guide](/docs/consumer-auth/authorization/enforcing-permissions) to implement authorization checks in your app.
# Role-based access control overview
Source: https://stytch.com/docs/consumer-auth/authorization/create-rbac-policy
Understand RBAC concepts and set up your policy in the Stytch Dashboard.
Stytch's RBAC model streamlines the management and enforcement of permissions for Consumer Authentication.
Configure your policy in the Stytch Dashboard.
Gate UI with RBAC checks from the client.
Authorize requests on your backend with sessions.authenticate.
## The RBAC Policy
An RBAC policy is a governing document that stores all access controls. It is the canonical source for RBAC authorizations within a Stytch Project, made up of three core concepts:
* **Resources**: entities within your application
* Examples: `documents`, `images`, `invoices`
* **Actions**: valid operations on a resource
* Examples: `create`, `read`, `update`, `delete`
* **Roles**: named collections of permissions
* Examples: `admin`, `editor`, `viewer`
### Resources
A Resource represents an entity in your application and is identified by a unique `resource_id` string you define.
### Actions
Actions are the allowed operations on a Resource. Actions can be standard CRUD operations or application-specific actions like `share`, `invite`, or `export`.
### Roles and permissions
A permission is a combination of a Resource and an Action (for example, `read` on `documents`). Roles are named sets of permissions. A Role can also include the wildcard action `*` to grant all actions for a Resource.
```js theme={null}
{
"role_id": "reader",
"permissions": [
{
"actions": ["read"],
"resource_id": "documents"
}
],
"description": "Readers can view documents."
}
```
## Set up your policy in the Dashboard
In the [RBAC Dashboard](https://stytch.com/dashboard/rbac), select **Resources** and **Create New Resource**. Define a `resource_id` and the list of actions allowed for that Resource.
For example, a `documents` Resource might support `create`, `update`, `delete`, and `download`.
Navigate to **Roles**. You’ll see a default Role:
* **`stytch_user`**: automatically assigned to all Users and grants basic permissions like updating their own name.
You can edit this Role or create custom Roles. When you add permissions to a Role, select a Resource and choose which actions it grants.
To grant all actions for a Resource, use the wildcard action `*`.
Add server-side authorization checks before honoring requests. See the [backend enforcement guide](/docs/consumer-auth/authorization/enforcing-permissions).
Use the frontend SDKs to gate UI. See the [frontend authorization section](/docs/consumer-auth/authorization/enforcing-permissions).
## What's next
* [Assign roles to users](/docs/consumer-auth/authorization/assigning-roles-to-users)
* [Enforce permissions](/docs/consumer-auth/authorization/enforcing-permissions)
# Enforcing permissions
Source: https://stytch.com/docs/consumer-auth/authorization/enforcing-permissions
Enforce RBAC permissions in your application's frontend and backend.
To enforce RBAC permissions in your application, it is critical to:
* Gate UI and User actions in the frontend
* Confirm authentication and authorization on your backend before honoring requests
## Frontend authorization checks
Use Stytch's frontend SDKs to check permissions client-side.
### Conditionally render UI based on permissions
Adapt UI based on permissions, even when your backend will enforce them.
```jsx React icon="react" theme={null}
import { useEffect, useState } from 'react';
import { useStytch } from '@stytch/react';
const DocumentActions = () => {
const stytch = useStytch();
const [canReadDocument, setCanReadDocument] = useState(false);
const [canWriteDocument, setCanWriteDocument] = useState(false);
useEffect(() => {
const fetchPermissions = async () => {
setCanReadDocument(await stytch.rbac.isAuthorized('documents', 'read'));
setCanWriteDocument(await stytch.rbac.isAuthorized('documents', 'write'));
};
fetchPermissions();
}, [stytch]);
return (
);
};
```
```js Vanilla JS icon="square-js" theme={null}
import { StytchClient } from '@stytch/vanilla-js';
const stytch = new StytchClient('PUBLIC_TOKEN');
const canReadDocument = await stytch.rbac.isAuthorized('documents', 'read');
const canWriteDocument = await stytch.rbac.isAuthorized('documents', 'write');
```
### Check permissions before making API requests
Pre-emptive checks avoid unnecessary requests and prevent users from entering flows they cannot complete.
```js theme={null}
const canReadDocument = await stytch.rbac.isAuthorized('documents', 'read');
if (!canReadDocument) {
throw new Error('You do not have permission to read documents');
}
loadDocument();
```
## Backend authorization checks
Always perform server-side authorization checks by authenticating a valid Session Token or Session JWT.
If you use **Session Tokens**, call the [Authenticate Session endpoint](/docs/api-reference/consumer/api/sessions/authenticate-session) with an authorization check.
```python theme={null}
resp = stytch_client.sessions.authenticate(
session_token='mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q',
authorization_check={
'resource_id': 'documents',
'action': 'edit',
}
)
if resp.status_code != 200:
return 'not authenticated', 401
if not resp.authorized:
return 'unauthorized', 403
```
You can also authorize using **Session JWTs**. The same endpoint accepts a `session_jwt` and returns a refreshed JWT if needed.
```python theme={null}
resp = stytch_client.sessions.authenticate(
session_jwt='eyJ...',
authorization_check={
'resource_id': 'documents',
'action': 'edit',
}
)
if resp.status_code != 200:
return 'not authenticated', 401
if not resp.authorized:
return 'unauthorized', 403
```
## What's next
* [Create an RBAC policy](/docs/consumer-auth/authorization/create-rbac-policy)
* [Assign roles to users](/docs/consumer-auth/authorization/assigning-roles-to-users)
# Implement RBAC with metadata (legacy)
Source: https://stytch.com/docs/consumer-auth/authorization/legacy-rbac-metadata
Use trusted metadata to store roles when you can’t adopt native RBAC yet.
This legacy approach uses `trusted_metadata` on the User object to store role information and enforce authorization in your application. For new integrations, use native [RBAC policies](/docs/consumer-auth/authorization/create-rbac-policy) instead.
This guide is provided for backward compatibility. Native RBAC provides a dedicated policy model, Dashboard management, and SDK support.
Decide on the roles your application needs (for example `admin`, `editor`, `viewer`) and what actions each role should allow.
Use `trusted_metadata` to store roles when creating or updating a User. Only your backend can write trusted metadata.
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/users \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"trusted_metadata": {
"roles": ["admin"]
}
}'
```
After authenticating a session, fetch the User and read roles from `trusted_metadata`.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.Client({
project_id: 'PROJECT_ID',
secret: 'SECRET',
});
const sessionResp = await client.sessions.authenticate({
session_token: 'SESSION_TOKEN',
});
const userResp = await client.users.get({
user_id: sessionResp.user_id,
});
const roles = userResp.trusted_metadata?.roles || [];
```
Use the roles stored in `trusted_metadata` to decide whether to honor requests.
```js theme={null}
if (!roles.includes('admin')) {
return res.status(403).send('unauthorized');
}
```
## What's next
* [Create an RBAC policy](/docs/consumer-auth/authorization/create-rbac-policy)
* [Assign roles to users](/docs/consumer-auth/authorization/assigning-roles-to-users)
# Feathery
Source: https://stytch.com/docs/consumer-auth/integrations/feathery
Use Stytch and Feathery to build powerful forms with a visual editor, custom logic, and extensible code.
Use Stytch and Feathery to build powerful forms with a visual editor, custom logic, and extensible code. With Feathery's Stytch integration, you can build forms that handle authenticated login and signup.
Check out the [Stytch and Feathery integration guide](https://www.feathery.io/integrations/stytch) to get started.
# Planetscale for user authentication
Source: https://stytch.com/docs/consumer-auth/integrations/planetscale
Integrate Stytch Consumer Authentication with PlanetScale for user storage.
Use Stytch and PlanetScale to build out your entire user authentication flow and database.
Create your user database with PlanetScale's `pscale` command line utility, then create your first database branch.
```bash theme={null}
$ pscale auth login
$ pscale database create
$ pscale branch create
```
This query creates your users table where you'll store a record for each user.
```bash theme={null}
$ pscale shell // Open a MySQL shell in your database.
CREATE TABLE users (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(255),
email varchar(255) NOT NULL
);
```
```bash theme={null}
$ pscale deploy-request create
$ pscale deploy-request deploy
```
Establish a connection to PlanetScale, then create the functions to add, remove, and get users from the DB.
```js theme={null}
const URL = process.env.DATABASE_URL as string;
const sqlConn = mysql.createConnection(URL);
sqlConn.connect();
// getUsers retrieve all users.
async function getUsers(conn: PSDB, req: NextApiRequest, res: NextApiResponse) {
try {
var query = 'select * from users';
const [getRows, _] = await conn.query(query, '');
res.status(200).json(getRows);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'an error occurred' });
}
return;
}
// addUser create a new user.
async function addUser(conn: PSDB, req: NextApiRequest, res: NextApiResponse) {
var user = JSON.parse(req.body);
try {
var query = 'INSERT INTO users (name, email) VALUES (?,?)';
var params = [name, email];
var insertID;
const result = sqlConn.query(query, params, (err, result) => {
if (err) {
throw err;
}
insertID = (result).insertId;
});
res.status(201).json({ id: insertID });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'an error occurred' });
}
return;
}
// deleteUser remove a single user.
async function deleteUser(conn: PSDB, req: NextApiRequest, res: NextApiResponse) {
try {
var query = 'DELETE from users WHERE id=?';
var params = [req.query['uid']];
var status = 200;
const result = await sqlConn
.promise()
.query(query, params)
.then(([row]) => {
if ((row).affectedRows == 0) {
status = 304;
}
});
res.status(status).json({ message: 'success' });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'an error occurred' });
}
return;
}
```
Instantiate the Stytch client via our [stytch-node Backend SDK](https://github.com/stytchauth/stytch-node); you'll use this SDK to make any calls to the Stytch API. After the user has been authenticated, you will redirect them to the logged-in page where they can take authenticated action.
```js theme={null}
import * as stytch from 'stytch';
let client: stytch.Client;
const loadStytch = () => {
if (!client) {
client = new stytch.Client({
project_id: process.env.STYTCH_PROJECT_ID || '',
secret: process.env.STYTCH_SECRET || '',
env: process.env.STYTCH_PROJECT_ENV === 'live' ? stytch.envs.live : stytch.envs.test,
});
}
return client;
};
```
We would like to be able to invite and add new users to our dashboard, we'll leverage our stytch-node Backend SDK to send Email Magic Links to users to do that.
```js theme={null}
async function inviteUser(req: NextApiRequest, res: NextApiResponse) {
const client = loadStytch();
var email = req.body.email;
// Params are of type stytch.LoginOrCreateRequest.
const params = {
email: email,
login_magic_link_url: `${BASE_URL}/api/authenticate_magic_link`,
signup_magic_link_url: `${BASE_URL}/api/authenticate_magic_link`,
};
try {
await client.magicLinks.email.loginOrCreate(params);
res.status(200).json({"message":"magic link sent"});
} catch (error) {
res.status(400).json({ error });
}
return;
}
```
Next you'll want a way to authenticate the magic links that are being sent by the Stytch SDK.
```js theme={null}
import type { NextApiRequest, NextApiResponse } from 'next';
import loadStytch from '../../lib/loadStytch';
import { serialize } from 'cookie';
async function authenticate(req: NextApiRequest, res: NextApiResponse) {
const client = loadStytch();
const { token } = req.query;
try {
// Authenticate request and create 7 day session.
const resp = await client.magicLinks.authenticate(token as string, { session_duration_minutes: 10080 });
// Send user to profile with cookies in response.
res.setHeader(
'Set-Cookie',
serialize(process.env.COOKIE_NAME as string, resp.session_token as string, { path: '/' }),
);
res.redirect('/profile');
return;
} catch (error) {
res.status(400).json({ error });
return;
}
}
```
Now that we've got authentication in place, what happens when a user leaves and then comes back? We want that returning user to come back to an authenticated experience, so we will use Stytch's [Session Management](/docs/consumer-auth/manage-sessions/overview) API to make that happen.
```js theme={null}
export async function validSessionToken(token: string): Promise {
// Authenticate the session.
try {
const sessionAuthResp = await client.sessions.authenticate({
session_token: token
});
if (sessionAuthResp.status_code != 200) {
console.error('Failed to validate session');
return false;
}
return true;
} catch (error) {
console.error(error);
return false;
}
}
// Add this session helper to each DB handler so we can prevent unauthenticated API calls.
var isValidSession = await validSessionToken(token);
if (!isValidSession) {
res.status(401).json({ error: 'user unauthenticated' });
return;
}
```
Now we'll want to add logic to clear the client state once a session has expired. Doing so will allow us to gracefully re-prompt the user to log in when their session expires; when we initiated the session, we chose a session\_duration\_minutes of 10080 minutes, 7 days, so that users won't be prompted to re-login if they return within that 7 day period.
```js theme={null}
export async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
try {
// You can add logic to destroy other sessions or cookies as well.
res
.status(200)
.setHeader('Set-Cookie', [
serialize(process.env.COOKIE_NAME as string, '', { path: '/', maxAge: -1 }),
serialize(STYTCH_SESSION_NAME, '', { path: '/', maxAge: -1 }),
]).json({"message":"logged out"});
} catch (error) {
res.status(400).json({ error });
}
return;
}
}
```
You just finished all the critical backend components in our example. Your app can now login with Stytch Email Magic Links, manage sessions via Sessions Management, and maintain your user database via PlanetScale. You can find an example of this implementation in our [Stytch + PlanetScale example app](https://github.com/stytchauth/stytch-planetscale-integration).
We can't wait to see what you build with Stytch! Get in touch with us and tell us what you think at [support@stytch.com](mailto:support@stytch.com) or in our [community Slack](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw).
# Supabase
Source: https://stytch.com/docs/consumer-auth/integrations/supabase
Check out the Stytch integration guide in the Supabase documentation.
Check out the [Stytch integration guide](https://supabase.com/docs/guides/integrations/stytch) in the Supabase documentation. In this guide you will build a simple expense tracker application using Stytch for authentication and Supabase as a database.
# Unit
Source: https://stytch.com/docs/consumer-auth/integrations/unit
Use Stytch JWTs to satisfy Unit customer token requirements.
With Unit as a banking provider in your app, you'll be required to use the customer token mechanism for certain card related user actions. Creating a customer token requires the end user to complete a two factor authentication. If you are using Stytch as your authentication provider, you can complete this two factor authentication challenge via Stytch and pass the Stytch JWT into Unit's API.
In this guide you will configure Stytch JWT information in your Unit application and pass Stytch JWTs to Unit to prove user authentication.
Check out the [Stytch integration information](https://guides.unit.co/custom-authentication/#stytch) in the Unit documentation.
In the Unit dashboard, under **Org Settings** > **JWT Settings**, ensure that "Stytch" is selected under **Provider**, and add your Stytch JWKS URL to the "JWKS Path" field:
```curl theme={null}
https://[live|test].stytch.com/v1/sessions/jwks/
```
Your project ID can be found in the Stytch Dashboard on the [Project Overview](https://stytch.com/dashboard) page under **Project ID & API keys**, or by using the command bar in the upper-right corner (⌘-K / Ctrl-K). Use a **Test environment** for pre-production integration and testing.
When you create an Application for a new user in Unit, the Application object must include the Stytch user\_id in the jwtSubject field.
**Key actions:**
1. Sign up user via a Stytch API or SDK flow - [here's](/docs/consumer-auth/authentication/magic-links/adding-magic-links-to-stytch-login) an example of how to get started with a sign up flow using email magic links.
2. Receive Stytch user\_id from session or API response.
3. Pass Stytch user\_id into the user Application via Unit's APIs, using one of these options:
* [ApplicationFormPrefill](https://docs.unit.co/types#applicationformprefill)
* [Application creation](https://docs.unit.co/applications/#create-individual-application)
* [Patch Customer](https://docs.unit.co/customers/#update-individual-customer)
See Unit's docs for this step [here](https://docs.unit.co/customer-api-tokens/#customers-create-customer-bearer-token-jwt).
**Key actions:**
1. Step up user with a second factor of authentication using a Stytch API or SDK flow - [here's](/docs/consumer-auth/authentication/totps/api) an example of how to get started with 2FA using an authenticator app.
2. Upon successful 2FA, pass the returned or cached Stytch JWT into the Unit customers/token API (as seen in Unit's docs linked above).
3. The Unit API will return a customerBearerToken that can then be used to perform sensitive Unit actions.
You just finished all the critical components to integrate your Stytch authentication with Unit's banking APIs. Have any feedback or questions? Get in touch with us at [support@stytch.com](mailto:support@stytch.com) or in our [community Slack](https://join.slack.com/t/stytch/shared%5Finvite/zt-3aqo03e10-afWXyLzRIAlzGWJyF%5F~zHw).
# Add custom claims
Source: https://stytch.com/docs/consumer-auth/manage-sessions/custom-claims
Encode additional information into your session JWTs using custom claims.
## Use case
* **Store or pass user information:** Pass temporary session state or user metadata to your application.
* **Integrate with 3rd-party services:** Integrate with providers that support JWT-based authentication.
* **Implement custom authorization logic:** Use custom claim templates to pass RBAC information to sessions.
***
## Add custom claims to a session
Include the `session_custom_claims` argument on any authenticate method to add custom claims to a session. This argument takes in an arbitrary JSON object—which may be represented as a `map`, `dictionary`, or object:
```python theme={null}
# initial claims object {}
stytch.session.authenticate(token, session_custom_claims={"key_1": 1, "key_2": 2})
# current claims object {"key_1": 1, "key_2": 2}
stytch.session.authenticate(token, session_custom_claims={"key_1": 9})
# resulting claims object {"key_1": 9, "key_2": 2}
```
### Remove claims
Supply a `null` value to delete a key:
```python theme={null}
# current claims object {"key_1": 1, "key_2": 2}
stytch.session.authenticate(token, session_custom_claims={"key_1": null})
# resulting claims object {"key_2": 2}
```
If the value of a custom claim is a JSON object, the nested values can be updated or deleted using the same methods as above:
```python theme={null}
sessions.authenticate(token, session_custom_claims={
"b": null,
"c": 3.5,
"e": {
"nested1": "val1",
"nested2": "val2"
}
})
# resulting claims object
# {
# "c": 3.5,
# "d": 4,
# "e": {
# "nested1": "val1",
# "nested2": "val2"
# }
# }
stytch.session.authenticate(token, session_custom_claims={
"e": {
"nested1": nil,
"nested3": "val3"
}
)
# resulting claims object
# {
# "c": 3.5,
# "d": 4,
# "e": {
# "nested2": "val2",
# "nested3": "val3"
# }
# }
```
***
## Custom claim templates
Use [custom claim templates](https://stytch.com/dashboard/custom-claim-templates) to add the same set of claims to **every** session.
* The JSON object output for a particular user will be used as the initial set of custom claims for all of that user's sessions.
* Claims from templates can still be added, updated, or deleted by passing in a `session_custom_claims` argument in an API request.
* Updates to the custom claim template or user metadata propagate to existing sessions the next time a JWT is minted.
* Previously minted JWTs are immutable and cannot be updated.
### Markup syntax
Custom claim template markup language uses `{{ variable }}` syntax to denote information that should be passed in at runtime.
### User metadata
Use dot notation to access nested fields when handling User metadata. For example, `{{ user.trusted_metadata.subscription.level }}` accesses the `level` field of the `subscription` object in the `trusted_metadata` on a User object.
### User RBAC values
The variable `{{ user.rbac.roles }}` is evaluated as an array of all role IDs assigned to the user, e.g. `["stytch_user", "editor", "support_admin"]`.
The variable `{{ user.rbac.$RESOURCE_ID.actions }}` is evaluated as an array of all actions the user can perform on the specific resources. For example, `{{ user.rbac.documents.actions }}` might be evaluated as `["create", "read", "delete"]`.
### List of supported template variables
* `{{ user.external_id }}`
* `{{ user.user_id }}`
* `{{ user.full_name }}`
* `{{ user.rbac.roles }}`
* `{{ user.rbac.$RESOURCE_ID.actions }}`
* `{{ user.trusted_metadata.$PATH }}`
The template:
```json theme={null}
{
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "reader",
"x-hasura-allowed-roles": {{ user.rbac.roles }},
"x-hasura-user-id": {{ user.user_id }},
"x-hasura-custom-key": {{ user.trusted_metadata.custom_key }},
}
}
```
could be combined with the user's information:
```json theme={null}
{
"user_id": "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6",
"trusted_metadata": {
"custom_key": "custom-value"
}
}
```
to produce the final set of custom claims:
```json theme={null}
{
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "reader",
"x-hasura-allowed-roles": ["admin", "reader"],
"x-hasura-user-id": "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6",
"x-hasura-custom-key": "custom-value",
}
}
```
***
## Limitations
Both API-driven custom claims and custom claim templates share these limitations:
### List of reserved claims
These claims are reserved and will result in an error if set:
* `iss`
* `sub`
* `aud`
* `exp`
* `nbf`
* `iat`
* `jti`
* `https://stytch.com/*`
### Size limit
Total claims size cannot exceed 4kb.
### JWT immutability
A JWT cannot be modified once it is minted. You must authenticate again to update claims.
# Expire or extend sessions
Source: https://stytch.com/docs/consumer-auth/manage-sessions/lifecycle/extend-or-expire-session
Session duration and extending the lifetime of a session.
## Session expiration
Once a session has expired, it can't be reactivated or extended. The user must re-authenticate to create a new session.
The `session_duration_minutes` parameter sets the lifetime of a session from the time it is created:
* **Minimum:** 5 minutes
* **Maximum:** 527,040 minutes (366 days)
* **Default (if blank):**
* When there is no existing session: No session created
* When extending an existing session: No change to session expiry
***
## Extend a session
By default, the frontend SDK keeps the `session_jwt` updated, but it does **not** automatically extend the lifetime of the underlying session.
For example, if `session_duration_minutes` is set to 60 minutes, the session will expire 60 minutes after it was created regardless of user activity.
Pass a `session_duration_minutes` value to the `session.authenticate()` [method](/docs/api-reference/consumer/frontend-sdks/react/methods/sessions/authenticate-session) or [endpoint](/docs/api-reference/consumer/api/sessions/authenticate-session). The session will be set to expire that many minutes from the time of the call.
Techniques could be to set an interval to extend the session's lifetime periodically so the session is automatically extended as long as the user keeps the application open, or setting the expiration based on last user activity.
```javascript lines Via SDK theme={null}
const extendSession = () => {
if (stytch.session.getSync()) {
// Set the session's expiration to 60 minutes from the present
stytch.session.authenticate({
session_duration_minutes: 60,
});
}
};
// Extend the session's lifetime every 10 minutes
let interval = setInterval(extendSession, 600000);
window.addEventListener('beforeunload', () => {
clearInterval(interval);
});
```
```python lines Via API theme={null}
# While getting an authenticated user,
# set their session's expiration to 60 minutes from the present
def get_authenticated_user():
stytch_session = session.get('stytch_session')
if not stytch_session:
return None
try:
resp = stytch_client.sessions.authenticate(
session_token=stytch_session,
session_duration_minutes=60
)
return resp.user
except Exception as e:
return handle_exception(e)
```
# Revoke sessions
Source: https://stytch.com/docs/consumer-auth/manage-sessions/lifecycle/revoke-session
Revoking a session immediately invalidates the session token, effectively logging the user out. Session JWTs are not immediately revoked; they will still validate locally until they hit the 5-minute expiration mark.
## Using the Stytch SDK
The `sessions.revoke()` [method](/docs/api-reference/consumer/frontend-sdks/react/methods/sessions/revoke-session) clears the user and session objects from local storage unless the SDK cannot contact the Stytch servers.
```javascript lines theme={null}
export const logout = () => {
stytch.session.revoke();
};
```
## Using the Stytch API
The `sessions.revoke()` [endpoint](/docs/api-reference/consumer/api/sessions/revoke-session) allows you to revoke a **single** user Session by providing the `session_id`, `session_token`, or `session_jwt`.
```python lines theme={null}
# POST /v1/sessions/revoke
from stytch import Client
client = Client(
project_id="${projectId}",
secret="${secret}",
)
resp = client.sessions.revoke(
session_token="${sessionToken}",
)
print(resp)
```
We recommend showing users a list of all their active sessions so they can revoke any unrecognized session by IP address or user agent.
Use [custom claims](/docs/consumer-auth/manage-sessions/custom-claims#add-custom-claims-to-a-session) to attach values to the [User Session object](/docs/api-reference/consumer/api/sessions/session-object#schema-custom-claims) via the `session_custom_claims` parameter.
# Start of a session
Source: https://stytch.com/docs/consumer-auth/manage-sessions/lifecycle/start-session
A session starts after successful authentication.
## User session
A user session begins after any authentication endpoint (e.g. [authenticate magic link](/docs/api-reference/consumer/api/magic-links/authenticate)) is called with a specified `session_duration_minutes`.
If you do not pass in a `session_duration_minutes` parameter, a session will not be created.
These endpoints mint a [User Session](/docs/api-reference/consumer/api/sessions/session-object) and return a `session_token` and `session_jwt` in the response, which can be forwarded to the client and stored:
```json Response 200 expandable theme={null}
{
"method_id": "email-test-81bf03a8-86e1-4d95-bd44-bb3495224953",
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"reset_sessions": false,
"session": "{...}",
"session_token": "mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q",
"session_jwt": "eyJ...",
"status_code": 200,
"user": {...},
"user_id": "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6"
}
```
```json Object 200 expandable theme={null}
{
"session": {
"attributes": {
"ip_address": "",
"user_agent": ""
},
"authentication_factors": [
{
"created_at": "2025-01-09T07:41:52Z",
"delivery_method": "email",
"email_factor": {
"email_address": "sandbox@stytch.com",
"email_id": "email-test-81bf03a8-86e1-4d95-bd44-bb3495224953"
},
"last_authenticated_at": "2025-01-09T07:41:52Z",
"updated_at": "2025-01-09T07:41:52Z",
"type": "magic_link"
},
{
"created_at": "2025-01-09T08:41:52Z",
"delivery_method": "knowledge",
"last_authenticated_at": "2025-01-09T08:41:52Z",
"type": "password",
"updated_at": "2025-01-09T08:41:52Z"
}
],
"custom_claims": {
"claim1": "value1",
"claim2": "value2"
},
"expires_at": "2025-04-22T08:00:00Z",
"last_accessed_at": "2025-04-22T07:41:52Z",
"started_at": "2025-04-22T07:00:00Z",
"session_id": "session-test-fe6c042b-6286-479f-8a4f-b046a6c46509",
"user_id": "user-test-16d9ba61-97a1-4ba4-9720-b03761dc50c6"
}
}
```
Before performing any action that requires authorization, [authenticate the user session](/docs/consumer-auth/manage-sessions/validate-session) to ensure that the session is valid.
# API reference
Source: https://stytch.com/docs/fraud-risk/api-reference-link
# Reference guides
Source: https://stytch.com/docs/fraud-risk/development/overview
Developer reference guides for using Device Fingerprinting.
# Intelligent Rate Limiting
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/decisioning/intelligent-rate-limiting
Better rate limiting powered by device intelligence.
Intelligent Rate Limiting is included by default in Device Fingerprinting decisioning, and no additional implementation is required.
## Overview
Intelligent Rate Limiting (IntRL) automatically limits traffic based on device risk. To protect your application, IntRL analyzes device and traffic sub-signals and adds friction to high velocity traffic.
***
## How it works
High-velocity traffic from a single origin is often an indicator of fraudulent activity. Traditional rate limiting methods rely on simple origin identifiers like IP address. These are easily circumvented by sophisticated actors and can result in unintended rate limiting of good users sharing common IPs in settings like workplaces or university campuses.
Intelligent Rate Limiting combines signals to create a stable and highly unique targeting method for high velocity attacks. It automatically adds increasing levels of friction to those attacks through our `verdict` response in Fingerprint Lookup calls.
For suspicious fingerprints that are not associated with any known hardware and software configuration, Intelligent Rate Limiting also sets lower rate limits, which caps the impact of novel attacks without penalizing real users with unusual devices.
IntRL introduces three new `verdict.reasons[]` values that map to detected anomalies:
| **Reason** | **Action** | **Duration** | **Description** |
| - | - | - | - |
| `SMART_RATE_LIMIT_WARNING` | `CHALLENGE` | 60 minutes | Higher than normal velocity from a single origin; a `CHALLENGE` verdict will be returned for the next 60 minutes for the offending signal set. |
| `SMART_RATE_LIMIT_EXCEEDED` | `BLOCK` | 60 minutes | High velocity or abnormal traffic patterns; a `BLOCK` verdict will be returned for the next 60 minutes for the offending signal set. |
| `SMART_RATE_LIMIT_BANNED` | `BLOCK` | 24 hours | Extremely high velocity; a `BLOCK` verdict will be returned for 24 hours for the offending signal set. |
Once the relevant time period has passed, the offending origin (as determined by Device Fingerprinting signals) will be eligible to move back to a less restrictive status.
### Sample response
```json theme={null}
{
"created_at": "2024-08-24T07:00:00Z",
"expires_at": "2024-08-24T07:05:00Z",
"fingerprints": { ... },
"status_code": 200,
"telemetry_id": "026ac93b-8cdf-4fcb-bfa6-36a31cfecac1",
"verdict": {
"action": "BLOCK",
"detected_device_type": "INTEL_WINDOWS_CHROME",
"is_authentic_device": true,
"reasons": ["SMART_RATE_LIMIT_EXCEEDED"]
}
}
```
***
## Usage & considerations
There are some potential considerations depending on your integration with Device Fingerprinting:
### Different weights for different reasons
If you use Device Fingerprinting as an input to your own risk engine, you may want to explicitly weight the Intelligent Rate Limiting verdict reasons as higher risk.
### Verdict reasons are dynamic
These `verdict.reasons` are dynamic, stateful, and change over time. This means that:
* We don't recommend that you automatically ban via permanent [Rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) for these `verdict.reasons`, but they do indicate that you should investigate more deeply and potentially take action.
* If your risk engine correlates Device Fingerprinting results to IP addresses within your risk framework, we recommend that you *do not* permanently ban IP addresses based on IntRL `verdict.reasons`. These verdicts may change over time as traffic patterns change.
# Override verdict reasons
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/decisioning/override-verdict-reasons
Customizing the action associated with a verdict reason.
## Overview
For every fingerprint lookup, Device Fingerprinting recommends a specific [verdict action](/docs/fraud-risk/device-fingerprinting/verdict/overview). You can override the default action to customize the recommendations based on your application and traffic.
* This is most useful for customizing [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth) decisioning.
* If you are directly integrating with the Device Fingerprinting API, you can also use verdict reason overrides to change decisioning logic without needing to write and deploy new backend code.
### Example
The `VIRTUAL_MACHINE` warning flag results in a `CHALLENGE` by default,
but you might expect many legitimate users to be using an enterprise browser (that runs in a virtual machine). You can override verdict reasons so that `VIRTUAL_MACHINE` maps to an `ALLOW` verdict instead.
See [Reference guides](/docs/fraud-risk/development/warning-flags) for a full list of all verdict reasons or use the [Get Verdict Reasons endpoint](/docs/api-reference/fraud/api/get-verdict-reasons).
***
## Prerequisites
* An existing Stytch project in the [Dashboard](https://stytch.com/dashboard).
* The `project_id` and `secret` for your project's Test environment. These can be found under your project's *Project ID & API keys* section in the [Dashboard](https://www.stytch.com/dashboard).
* Device Fingerprinting [enabled for your project](https://stytch.com/dashboard/dfp-rules).
* If you don't have Device Fingerprinting enabled, you can [request access here](https://offers.stytch.com/dfp-30-day-trial?utm_source=stytch_docs\&utm_medium=direct\&utm_content=dfp_30_day_trial).
## Steps
You will need the verdict reason and action to associate with it.
* See [Reference guides](/docs/fraud-risk/development/overview) for warning flags (verdict reasons).
* Or use the [Get Verdict Reasons endpoint](/docs/api-reference/fraud/api/get-verdict-reasons).
For the chosen verdict reason, you can decide the new action: `ALLOW`, `BLOCK`, or `CHALLENGE`.
Call the [Set Verdict Reason Override](/docs/api-reference/fraud/api/set-verdict-reason-override) endpoint:
```bash theme={null}
curl --request POST \
--url https://telemetry.stytch.com/v1/verdict_reasons/override \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
--data '{
"verdict_reason": "VIRTUAL_MACHINE",
"override_action": "ALLOW",
"description": "Allow instead of challenge VMs because we expect lots of legitimate VM users"
}'
```
This example sets `VIRTUAL_MACHINE` to `ALLOW` instead of the default reason.
You can use the [Get Verdict Reasons endpoint](/docs/api-reference/fraud/api/get-verdict-reasons) to confirm your change:
```bash theme={null}
curl --request POST \
--url https://telemetry.stytch.com/v1/verdict_reasons/list \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
-d '{}'
```
The resulting list will contain an entry showing the override for `VIRTUAL_MACHINE`:
```json theme={null}
{
"verdict_reason_overrides": [
# ...
{
"verdict_reason": "VIRTUAL_MACHINE",
"default_action": "CHALLENGE",
"override_action": "ALLOW",
"override_created_at": "2025-01-02T03:04:05Z",
"description": "Allow instead of challenge VMs because we expect lots of legitimate VM users"
}
]
}
```
The response also includes `verdict.verdict_reason_overrides` that contains information about overrides applied to a specific lookup.
To delete the override, call the [Set Verdict Reason Override endpoint](/docs/api-reference/fraud/api/set-verdict-reason-override) with an action of `NONE`:
```bash theme={null}
curl --request POST \
--url https://telemetry.stytch.com/v1/verdict_reasons/override \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
--data '{
"verdict_reason": "VIRTUAL_MACHINE",
"override_action": "NONE"
}'
```
# Make decisions
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/decisioning/overview
Make decisions with using verdicts, rules, Intelligent Rate Limiting, and custom decisioning logic.
## Overview
Decisioning is a key phase of prevention in our [fraud prevention framework](/docs/fraud-risk/device-fingerprinting/overview#stytch-fraud-prevention-framework).
Given a particular set of signals, do we think this is a good user or a bad actor? Is it benign activity, or fraudulent or abusive behavior?
Device Fingerprinting's [verdicts](/docs/fraud-risk/device-fingerprinting/verdict/overview) provide a useful starting point for decisioning. You can use the recommended verdict action directly. Or, you can extend the verdict with your own custom decisioning logic.
Device Fingerprinting supports decisioning with the following features:
1. [Verdicts](#verdicts)
2. [Set rules](#set-rules)
3. [Override verdict reasons](#override-verdict-reasons)
4. [Intelligent Rate Limiting](#intelligent-rate-limiting)
5. [Custom decisioning logic](#custom-decisioning-logic)
***
## Verdicts
Every response from the Lookup API contains a verdict, consisting of:
* A recommended action.
* Reasons or warning flags that explain the recommended action.
* Additional metadata, like the detected device type.
Device Fingerprinting verdicts are designed to be highly precise and explainable, with the goal of ensuring real human traffic for your application. There are no opaque floating-point "risk scores" where you must guess an appropriate threshold.
You can use the recommended verdict action directly, or use it as an input into your own decisioning logic.
Learn more about verdicts, actions, and reasons.
***
## Set rules
As you learn from your fraud prevention analysis and feedback loop, you may find that you want to explicitly increase or reduce friction for specific fingerprints.
Setting rules enables you to customize verdicts. For example:
* You can set a rule to always `ALLOW` a known-good user,
* Or manually `BLOCK` a fingerprint that is associated with a repeat abuser's sockpuppet accounts.
Create rules to customize your decisioning.
***
## Override verdict reasons
You can also customize verdicts in a broader way by adding a verdict reason override, which changes the default action associated with a verdict reason (warning flag).
For example:
* You can configure `VIRTUAL_MACHINE` to `ALLOW` if you expect many legitimate virtual machine users,
* Or you can configure `KNOWN_DATACENTER_IP` to `CHALLENGE` if you prefer to add more friction for datacenter traffic.
Customize the action associated with a verdict reason.
***
## Intelligent Rate Limiting
Intelligent Rate Limiting is a feature of Device Fingerprinting that uses fingerprints for highly-precise rate-limiting. When Stytch detects high-velocity traffic from a single origin and set of fingerprints, Intelligent Rate Limiting automatically updates the verdict with escalating friction from `CHALLENGE` to `BLOCK`.
Unlike traditional IP-based rate limiting, the fingerprint-based approach ensures that you don't accidentally block good users who happen to share the same enterprise or university network as an attacker.
For suspicious fingerprints that are not associated with any known hardware and software configuration, Intelligent Rate Limiting also sets lower rate limits, which caps the impact of novel attacks without penalizing real users with unusual devices.
Learn more about better rate limiting powered by device intelligence.
***
## Custom decisioning logic
While Device Fingerprinting provides recommended actions through the verdict, you may be able to make better decisions after factoring in your own proprietary data, application workflows, and business needs.
Examples of how current Stytch customers have added custom decisioning logic:
* One of your enterprise customers always uses an enterprise VPN that triggers a `CHALLENGE` due to `POSSIBLE_TLS_MITM`. You write custom code that `ALLOW`s all users from that enterprise (based on their organization and IP address), as long as `POSSIBLE_TLS_MITM` is the only warning flag present.
* Your business is highly-sensitive to false positives, so you never want to `BLOCK` a user automatically. Instead, you write custom code that permits all `BLOCK` verdicts to proceed, but logs each verdict to an internal auditing system for manual review.
* Your fraud team already has a sophisticated ML-based "risk engine" that integrates data from across the whole system. You can treat Device Fingerprinting verdicts like any other data, and feed them to your ML models. In this scenario, you will continue using the output of the risk engine for decisioning.
The standard Device Fingerprinting Lookup API does not expose many of the raw signals collected, which may be useful in this scenario.
If you are interested in additional raw signal data, contact [support@stytch.com](mailto:support@stytch.com) or your account team.
***
## Next steps
Learn more enforcing verdicts with Device Fingerprinting.
# Set decisioning rules
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/decisioning/set-rules
Create rules to customize your decisioning for Device Fingerprinting.
## Overview
In some cases you may want to increase or reduce friction for previously fingerprinted instances. For example, manually `ALLOW` a user due to a false positive, or `BLOCK` an authentic user who violated the terms of service.
You can create a rule that changes the verdict action based on fingerprint attributes.
In this guide you'll learn how to set up rules. By the end, you'll have:
* Called the [Set Rule endpoint](/docs/api-reference/fraud/api/set-rule) to apply an authorization action on a specific fingerprint.
* Removed the rule.
***
## Important considerations
Consider the following when setting a rule:
### Verify the request
When you receive a support request to be unblocked, you should consider the risk of social engineering. Bad actors can also file support requests to try to evade your defenses, so you shouldn't blindly set `ALLOW` rules based on support requests.
When setting an `ALLOW` rule for a customer who reaches out, you should confirm that the user is a good actor and should be allowed access to your application.
### Determine which identifier to use
You'll need a fingerprint, ID, or IPGEO attribute (IP address, ASN, or country code) you want to set a rule for. These identifiers are returned by the [Fingerprint Lookup endpoint](/docs/api-reference/fraud/api/fingerprint-lookup) for each `telemetry_id`.
The ideal rule is precise and targets the specific users you are looking for. Consider the granularity and other characteristics to avoid setting a rule that unintentionally affects good users.
| **Identifier** | **Characteristics** | **Use Cases** | **Precedence** |
| - | - | - | - |
| **Visitor ID** | Entirely unique, stable for the lifetime of the cookie.
Ineffective for setting BLOCK verdicts as bad actors can churn Visitor ID by clearing cookies or local storage. | Permanent ALLOW rules to unblock a false positive, since it is entirely unique and will not impact other users | 1 |
| **Browser ID** | Entirely unique, fairly stable but can change during normal usage including clearing cookies.
Ineffective for setting BLOCK verdicts as bad actors can churn Browser ID. | Permanent ALLOW rules to unblock a false positive, since it is entirely unique and will not impact other users. | 2 |
| **Visitor Fingerprint** | 99.9% unique and rarely changes. | Typically best choice for permanent or temporary BLOCKs.
Can also be used for temporary or permanent ALLOW rules, if you are prioritizing stability over 100% uniqueness | 3 |
| **Browser Fingerprint** | Not very unique, often shared by users with the same browser version and device type.
Fairly stable, but will change across browsers and may change on major version updates. | Good candidate for a temporary block if a malicious actor is churning other more unique identifiers.
Not recommended for longer expiry windows (>= 24 hours), as you risk impacting other users on the same browser version and device type. | 4 |
| **Hardware Fingerprint** | Not very unique, shared by all users on the same specific machine make/model with the same device configurations.
Fairly stable, but will change based on device configurations. | Good candidate for a temporary BLOCK if a malicious actor is churning other more unique identifiers.
Not recommended for longer expiry windows (>= 24 hours), as you risk impacting other users on the same device model.
Less effective for mobile devices, which generally have more homogenous hardware and therefore a high overlap between devices. | 5 |
| **Network Fingerprint** | Not very unique, based on the TLS fingerprint. | For use with blocking bad actors: if the network fingerprint in question has a low overlap with other users, can be good for issuing more permanent BLOCK verdicts. | 6 |
| **CIDR Block** | Not very unique, shared by all users with IP addresses within the CIDR block. | Permanent ALLOW rules for a known IP address, or temporary BLOCKs if a specific IP address is attacking your application. | 7 |
| **ASN** | Not unique, shared by all users with IP addresses within the ASN. | Temporary BLOCKs if a specific ASN is attacking your application. | 8 |
| **Country Code** | Not unique, shared by all users within the given country. | Permanent BLOCK rules for countries that you wish to ban from your application. You may not set an ALLOW rule for a country code. | 9 |
If multiple rules apply, the highest precedence wins. For example, one request matches a Visitor ID Rule to `ALLOW` and a Country Code Rule to `BLOCK`, the more-specific Visitor ID Rule wins and the verdict is `ALLOW`.
#### CIDR rule conflicts
Since CIDR block rules may conflict with each other, we have two additional rules for precedence:
* The smallest block size takes precedence. For example, if an `ip_address` overlaps with a `cidr_block` rule of `ALLOW` for a block with a prefix of `/32` and a `cidr_block` rule of `BLOCK` with a prefix of `/24`, the rule match verdict will be `ALLOW`.
* Among equivalent size blocks, `BLOCK` takes precedence over `CHALLENGE`, which takes precedence over `ALLOW`. For example, if an `ip_address` overlaps with two `cidr_block` rules with blocks of the same size that return `CHALLENGE` and `ALLOW`, the rule match verdict will be `CHALLENGE`.
### Validate against historical traffic
Before setting a rule on an identifier, compare the identifier against your historical traffic if available.
You can look up the various fingerprint types to identify the uniqueness in the [Dashboard](https://stytch.com/dashboard/activity?tab=dfp-logs).
* When you search an identifier in the Device Fingerprinting Dashboard,
it will show how frequently other fingerprint types have shared that identifier. You should make sure the percentage of ALLOWs isn't too high.
* When you create a new rule in the Dashboard,
you can test the impact to check the number of changes is expected.
* In addition to the Stytch Dashboard, you should cross-reference the identifier with your own internal monitoring
to verify that the rule won't affect other valid users. Ensure the rule targets your expected scope of users.
***
## Prerequisites
* An existing Stytch project in the [Dashboard](https://stytch.com/dashboard).
* The `project_id` and `secret` for your project's Test environment. These can be found under your project's *Project ID & API keys* section in the [Dashboard](https://www.stytch.com/dashboard).
* Device Fingerprinting [enabled for your project](https://stytch.com/dashboard/dfp-rules).
* If you don't have Device Fingerprinting enabled, you can [request access here](https://offers.stytch.com/dfp-30-day-trial?utm_source=stytch_docs\&utm_medium=direct\&utm_content=dfp_30_day_trial).
## Steps
1. Navigate to [Device Fingerprinting Rules](https://stytch.com/dashboard/dfp-rules) in the Dashboard.
2. Click **New rule**.
3. Enter the identifier, like a `visitor_id` or `browser_id`, or an IPGEO attribute (IP address, ASN, or country code).
4. Set action to `ALLOW`, `BLOCK`, or `CHALLENGE`.
5. (Optional) Set an expiration time and a description for the rule.
6. Click **Test rule impact** and confirm the expected impact.
7. Click **Save**.
The example above creates a rule that ensures this Visitor ID always receives a `BLOCK` [verdict](/docs/fraud-risk/device-fingerprinting/verdict/block). Any future calls to the Fingerprint Lookup endpoint involving this fingerprint will automatically return a `BLOCK` verdict with a reason of `RULE_MATCH`.
You also have the option of providing an `expires_in_minutes` parameter if you need a rule to expire after a specified lifetime.
Since `expires_in_minutes` was omitted in our example request, the rule will be kept permanently.
1. Click the **Delete rule** button to delete the rule.
Call the [Set Rule endpoint](/docs/api-reference/fraud/api/set-rule) with the following body parameters in the request:
* The identifier, for example a `visitor_id` or `browser_id`.
* An action set to `ALLOW`, `BLOCK`, or `CHALLENGE`.
* An optional `expires_in_minutes` set to a specified `int`.
```bash theme={null}
curl --request POST \
--url https://telemetry.stytch.com/v1/rules/set \
-u 'project-test-...' \
--data '{
"visitor_id": "visitor-6139cbcc-4dda-4b1f-b1c0-13c08ec64d72",
"action": "BLOCK"
}'
```
The example request above creates a Rule that ensures this Visitor ID always receives a `BLOCK` [verdict](/docs/fraud-risk/device-fingerprinting/verdict/block). Any future calls to the Fingerprint Lookup endpoint involving this fingerprint will automatically return a `BLOCK` verdict with a reason of `RULE_MATCH`.
You also have the option of providing an `expires_in_minutes` parameter if you need a rule to expire after a specified lifetime. Since `expires_in_minutes` was omitted in our example request, the rule will be kept permanently.
To remove a rule you've set, call the same [Set Rule endpoint](/docs/api-reference/fraud/api/set-rule) with the following body parameters in the request:
* The same `visitor_id` or `browser_id` from **step 2**.
* An `action` set to `NONE`.
```bash theme={null}
curl --request POST \
--url https://telemetry.stytch.com/v1/rules/set \
-u '${projectId}:${secret}' \
--data '{
"visitor_id": "visitor-6139cbcc-4dda-4b1f-b1c0-13c08ec64d72",
"action": "NONE"
}'
```
***
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Enforce decisions
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/enforcement/overview
How to enforce decisions once you've made them.
## Overview
Enforcement is a key phase of prevention in our [fraud prevention framework](/docs/fraud-risk/device-fingerprinting/overview#stytch-fraud-prevention-framework).
Once we have made a decision about what to do, we need to actually enforce that decision: reduce friction for a good user, or add friction for a bad actor.
Your enforcement options may vary depending on if you're using Protected Auth or the Device Fingerprinting API directly:
1. [Protected Auth](#protected-auth)
2. [Custom enforcement logic](#custom-enforcement-logic)
3. [Integrate with WAFs and other tooling](#integrate-with-security-tooling)
***
## Protected Auth
Protected Auth requires the Stytch [frontend](/docs/api-reference/b2b/frontend-sdks/react/overview) or [mobile](/docs/api-reference/b2b/mobile-sdks/react-native/overview) SDKs.
If you are using Stytch for authentication, you can toggle on Protected Auth in the [Dashboard](https://stytch.com/dashboard/sdk-configuration) to immediately protect your application from attacks like credential stuffing and automated account creation.
Protected Auth uses Device Fingerprinting to protect sensitive events like login and password reset. When enabled in *Enforcement mode*, the Protected Auth SDK automatically generates Telemetry IDs and includes them in requests to the Stytch API. The Stytch API will reject any requests that receive a `BLOCK` verdict.
Learn more about Stytch's ready-made solution.
Guide for enabling and using Protected Auth.
***
## Custom enforcement logic
If you are using the standalone Device Fingerprinting API, you can write backend code to implement your desired custom enforcement logic.
The ideal enforcement method will vary depending on your use case, as there are trade-offs between immediate prevention, meaningful error messages, and increasing the difficulty for the attacker to understand your defenses.
Four common ways to do custom enforcement:
1. [Block the request](#block-the-request)
2. [Step-up authentication](#step-up-authentication)
3. [Shadow-ban the action or user](#shadow-ban-the-action-or-user)
4. [Allow the action and log for later use](#allow-the-action-and-log-for-later-use)
For other enforcement logic options, reach out to [support@stytch.com](mailto:support@stytch.com) or your account team.
We are happy to learn about your scenario and business to help you implement meaningful and precise enforcement.
### Block the request
Your application can block the specific request that received a `BLOCK` verdict, returning an error. This is the most common enforcement approach.
The error response may be opaque in order to avoid giving information to the attacker. For example, if an attempt to login received a `BLOCK` verdict, the application can display a standard "Username or password is incorrect" message, rather than telling the user they are blocked by Device Fingerprinting.
### Step-up authentication
Your application can prompt the user for multi-factor authentication (MFA) or some other check like CAPTCHA when a request receives a `BLOCK` or `CHALLENGE` verdict.
For example, in our [example app](https://github.com/stytchauth/stytch-b2b-dfp-mfa-example), a `CHALLENGE` verdict requires the user to complete an MFA challenge even if they had previously logged in with the same device and marked it as "trusted".
Another common pattern is to block logins and [send an "unusual activity" notification](/docs/fraud-risk/get-started/use-case/new-device-notifications). The user will not be able to log in until they have confirmed access to their email,
which means attackers will not be able to directly gain access by phishing or credential-stuffing your application.
### Shadow-ban the action or user
Shadow-banning is the practice of blocking an action while giving the appearance that the action succeeded. This is a form of opaque error that leads the attacker to waste their time and effort trying to continue.
For example, in our [example app](https://github.com/stytchauth/stytch-b2b-dfp-mfa-example), a `BLOCK` verdict results in the application displaying an "Email magic link sent!" page
even though no email was actually sent. An attacker might wait for an email, thinking that they have successfully breached the account.
In raffles, giveaways, and other similar contests, shadow-banning can be used to quietly prevent an attacker from winning real prizes. Because the attacker believes they entered successfully, they will not escalate their attack; but the shadow-ban prevents them from ever winning.
### Allow the action and log for later use
In longer-term fraud prevention strategies, your application may allow the action and log the fingerprints and verdict for later use.
For example, if you have your own "risk engine" that accumulates evidence of suspicious activity, `BLOCK` verdicts should increment the risk score. Once the risk score reaches your preferred threshold, you can take action.
Or in a consumer application, you may want to observe until you can ban a whole set of fraudulent accounts simultaneously. This is common in gaming and social applications, where attackers may create cheating rings or sockpuppet accounts that support each other. Eliminating the entire set of accounts at once can be more effective than picking off individual accounts one-at-a-time, which gives the attacker instant feedback to improve their deception.
***
## Integrate with security tooling
Device Fingerprinting is a powerful tool for stopping bad actors, and it is complementary to other tools, including WAFs.
### Web application firewalls (WAFs)
You may see high-velocity attacks that overwhelm your servers. Even though Device Fingerprinting detects bot activity and returns `BLOCK` verdicts, it still takes compute resources to lookup the fingerprints and block the bots.
In this situation, blocking traffic at your WAF, firewall, or other network layer can protect your compute resources so that you can serve real users. Device Fingerprinting logs should help you identify IP addresses associated with the attack.
### Other security tooling
Other security tooling may be helpful in your overall fraud prevention strategy. For example, you can combine the device-level data of Device Fingerprinting with content-level data from a spam detection tool, or push your Device Fingerprinting logs to your security information and event management (SIEM) tool to correlate with other events.
For guidance on integrating with other tools, reach out to [support@stytch.com](mailto:support@stytch.com) or your account team.
***
## Next steps
Demo on how to use Device Fingerprinting to power adaptive MFA.
# Add external metadata
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/external-metadata
Provide context by attaching additional metadata to your fingerprints and verdicts.
## Overview
Device Fingerprinting is more useful with the full context of your application. To provide context, you can attach additional metadata to your fingerprints and verdicts.
Without external metadata, it can be difficult to interpret your data. For example, you might see a large volume of lookups for the same fingerprints. Is it because one user is very active and taking many actions on their own account? Or is it because an attacker is creating many different accounts using the same device?
Using the Stytch Dashboard and external metadata, you can understand user behavior and fraud patterns with full context.
You should consider your company's [privacy and compliance policy](/docs/fraud-risk/device-fingerprinting/privacy-and-compliance#external-metadata) when using external metadata.
Device Fingerprinting stores the `external_metadata` and does not use it for decisioning or enforcement.
***
## External metadata fields
The Fingerprint Lookup API has an optional parameter for `external_metadata`:
```json theme={null}
"external_metadata": {
"external_id": "user-123",
"organization_id": "organization-123",
"user_action": "LOGIN"
}
```
Each field is optional and you can provide any string data in these fields:
Represents a specific user identifier, like an email address or user ID.
Represents a group identifier, like an organization or other group.
Represents the action that was fingerprinted.
### How Protected Auth uses external metadata
Stytch authentication methods are protected using Device Fingerprinting. Each protected method adds external metadata to provide context:
Stores different identifiers depending on the action: email address, biometric registration ID, User ID or Member ID..
Stores the member's organization when using multi-tenant authentication.
Stores the associate API action (SDK method), like `OTPsSMSSend` or `SDKB2BDiscoveryPasswordReset`.
***
## In the Stytch Dashboard
After you start sending external metadata in your Fingerprint Lookup API requests, you will be able to view and query them in the [Dashboard](https://stytch.com/dashboard/activity?tab=dfp-logs).
Here are some examples of questions you can ask:
* In a recent spike of signups (`user_action`), what were the fingerprints? Is there a common pattern in their email addresses or domains (`external_id`)?
* A specific user recently reported unauthorized activity on their account. Are there different fingerprints associated with their `external_id` that might indicate an account takeover?
* Recently you've seen more account takeover attempts against one particular business customers (`organization_id`).
Is there a pattern in their fingerprints?
The Dashboard is a convenient way to perform ad-hoc queries against your recent Device Fingerprinting data. You can also store the Fingerprint Lookup responses yourself and correlate it with your other logs.
# Fingerprints & IDs
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/fingerprints
Stytch Device Fingerprinting provides multiple device identifiers for different use cases.
## Overview
Device Fingerprinting returns two kinds of identifiers based on different hardware, browser, and network characteristics:
* IDs
* Fingerprints
***
## IDs
IDs are based on a cookie, and they are guaranteed to be unique. IDs are good for identifying good users returning to your site. However, they are easily changed by bad actors who clear their cookies or use private browsing (incognito mode).
***
## Fingerprints
Fingerprints are deterministically-generated hashes of various low-level signals. These do not depend on stateful storage or IP address, so they remain stable even in incognito mode or when a VPN is used.
Fingerprints are usually stable for months or longer, including through routine browser updates. Rarely, a browser update changes low-level signals permanently, which results in new fingerprints.
There are four different levels of fingerprints:
* Visitor (most specific)
* Browser
* Hardware
* Network (least specific)
***
## Table of different identifiers
This table outlines the distinct qualities of each identifier. For more details on different fingerprint characteristics and usage with our `/rules` endpoint, see [Setting Rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules).
| **Identifier** | **Definition** | **Use Cases** | **Uniqueness** |
| - | - | - | - |
| **Visitor ID** | A stateful cookie stored on the user's device. | Having a unique identifier for a user. | Guaranteed unique |
| **Browser ID** | Combination of Visitor ID and Hardware Fingerprint to create a clear identifier of a browser. | Detecting stolen sessions; the browser ID should only be associated with a single hardware fingerprint. | Guaranteed unique |
| **Visitor Fingerprint** | A highly unique set of diverse signals. | Banning specific kinds of traffic in a cookie-less way. | 99.9% unique |
| **Browser Fingerprint** | A set of signals that identifies a browser on a specific kind of device. | Banning an unusual browser configuration only used for abuse. | Low |
| **Hardware Fingerprint** | A set of signals that identifies an operating system and architecture. | Detecting mismatches between hardware fingerprint and capabilities. | Low |
| **Network Fingerprint** | Also known as TLS fingerprint: a set of signals associated with a particular network stack. | Blocking programmatic access like Golang, Curl, or Node. | Low |
## Using Fingerprints & IDs
You can [set rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) based on specific fingerprints to customize the behavior for specific devices. Fingerprints can also be tied to other user data to associate known hardware sets, browsers, and other attributes with your user base.
When determining which fingerprint to use for a given use case, consider a fingerprint's uniqueness and stability.
### Examples
Add an invisible bot protection for a smooth user experience.
Stop fake account creation abuse.
Use IP-based geographic filters for access control.
Add a remembered device flow.
# Stytch Device Fingerprinting
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/overview
Prevent fraud with device intelligence. Block bots and bad actors and improve UX for your real users.
## Overview
Device Fingerprinting (DFP) collects and interprets various attributes of a user's device to help you prevent fraud, reduce risk, and improve UX for your real users. Here are some examples of device attributes:
* Browser type
* Screen size
* Operating system
* Time zone
* IP address and related geographical data (city, region or state, country)
Stytch Device Fingerprinting collects these raw attributes and enhances them with proprietary tamper detection, warning flags, and recommended actions to take.
If you're using Stytch for authentication, you can enable [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth) to automatically enforce the resulting actions by allowing real users, blocking bots, and presenting challenges to suspicious activity.
See the README on how to get started.
***
## Stytch fraud prevention framework
At a high level, Stytch thinks about fraud prevention in four main areas:
1. **Signal gathering:** Capture information about user activity.
2. **Decisioning:** Given that information, decide what to do.
3. **Enforcement:** Given the decision, add or reduce friction in the user's journey.
4. **Analysis and feedback loop:** Observe, iterate, and improve detection and controls based on real-world outcomes.
Ultimately, every fraud prevention team needs to collect the right signals to make the right decisions, enforce those decisions,
and improve as bad actors try to evade their defenses. Device Fingerprinting provides a powerful tool for your team to reliably stop bad actors.
***
## Features & benefits
For a given device, Device Fingerprinting delivers stable [identifiers (fingerprints)](/docs/fraud-risk/device-fingerprinting/fingerprints), a mix of industry-standard and proprietary signals, and derived insights about how you should respond.
Anyone can write Javascript code that collects raw browser signals like user agent string, but that code is easily reverse-engineered; attackers can spoof the signals or alter the payload to actively mislead you.
That's why Stytch doesn't just give you raw signals like user agent and instead provides:
### Verdicts
Clear action recommendations (`ALLOW`, `BLOCK`, `CHALLENGE`) rather than opaque floating-point risk scores along with:
* Warning flags about automated or deceptive behavior, like headless browser automation or user agent spoofing.
* High velocity flags with Intelligent Rate Limiting.
### Deterministically-generated fingerprints
Aggregations of device signals that remain stable across incognito browsing, webviews, VPNs, changes to user agent or IP addresses, and more.
### Customizable rules
Tailored decisioning rules for your own application needs.
### Tamper-resistant design
Including encryption, obfuscation, and proprietary tamper detection.
### Flexible integration
You can use Stytch Device Fingerprinting as a standalone fraud and risk solution, or integrate with your existing signal gathering, decisioning engine, and enforcement logic.
***
## Integrating Device Fingerprinting
If you're using Stytch for authentication already, you can turn on [Protected Auth](/docs/fraud-risk/get-started/using-protected-auth) to start protecting your sign-ups and logins immediately.
Otherwise, you can integrate with the [Device Fingerprinting API](/docs/fraud-risk/get-started/using-dfp-api) directly.
### How it works
At its core, Stytch Device Fingerprinting requires two integration points: a client-side Javascript library that gathers signals, and a backend API that interprets them.
The Javascript library calls a WebAssembly binary that gathers and sends signals to the Stytch backend for processing. The Stytch backend will return a Telemetry ID.
When you want to make a decision about that user, call the Lookup API with the Telemetry ID.
The API will return Stytch's view of the user, including signals, fingerprints, warning flags, and verdict.
Now, you can decide how to respond to the user's request and enforce your decision.
***
## Next steps
Enable Protected Auth with no code changes.
Integrate with the Device Fingerprinting API.
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Privacy & compliance
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/privacy-and-compliance
Considerations for privacy and compliance of your application when using Device Fingerprinting.
Stytch Device Fingerprinting collects telemetry data and stores a visitor ID to local storage.
This page outlines some considerations for privacy and compliance of your application.
Consult your legal and compliance team to determine the exact impact on your business.
The information on this page does not, and is not intended to, constitute legal advice;
instead, all information is for general informational purposes only.
## General considerations
In general, users should be notified of:
* The types of data being collected.
* How the data is used.
* What information is stored on the device.
The exact requirements for these notifications vary based on the jurisdiction of the user and your service.
In various regulations, data collection and processing for fraud prevention and security use cases is explicitly allowed as "legitimate interest" or "necessary processing".
***
## Data collected
Stytch Device Fingerprinting collects various forms of data about the user's device.
The only personally-identifiable data (PII) is the IPGEO information, described below.
### IPGEO information
By default, Stytch returns IP address, Autonomous System Number (ASN), and IP-based geolocation information in the response of the [Fingerprint Lookup API](/docs/api-reference/fraud/api/fingerprint-lookup).
IPGEO information is opt-out and you may contact Stytch to remove this information from the payload.
Consult your compliance team to understand if you need to update your privacy policy to collect this data.
***
## Data stored on the user's device
Device Fingerprinting stores the [visitor ID](/docs/fraud-risk/device-fingerprinting/fingerprints) to the local storage of the user's browser.
***
## External metadata
The Fingerprint Lookup API provides an `external_metadata` parameter that can help you correlate fingerprints based on your own records of that user.
If you provide external metadata, specifically `external_id`, consider the privacy implications.
For example, if you set `external_id` to be a user's email address or other PII,
then that information is shared with Stytch and may require updates to your privacy policy or other disclosures.
# Protected Auth
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/protected-auth
Stytch's combined solution for authentication and fraud prevention.
## Overview
Protected Auth is a ready-made solution in Stytch's frontend & mobile SDKs that seamlessly integrates with [Device Fingerprinting](/docs/fraud-risk/device-fingerprinting/overview) to detect and stop fraud, such as credential stuffing attacks.
When Protected Auth is enabled, the SDK automatically generates a Telemetry ID before an API call and includes the Telemetry ID in the Stytch API request. The API will verify the fingerprint verdict before authenticating the request and handles the backend integration.
### Scenario: Credential stuffing
Protected Auth will stop the attack by detecting signs of browser automation or scripting. The attacker only sees failed authentication attempts with no idea if the request failed due to a failed fingerprint request or an incorrect password.
***
## Configuring Protected Auth
Protected Auth requires no code changes and is enabled from the [Dashboard](https://stytch.com/dashboard/sdk-configuration).
* Follow our [Get started](/docs/fraud-risk/get-started/using-protected-auth) guide to enabling Protected Auth.
### Connection type modes
There are two modes for Protected Auth:
* **Observation mode:** Generates and submits fingerprints to the API, but the API will **not** enforce any action.
* **Enforcement mode:** The API will react to each fingerprint lookup triggered by the SDK based on the verdict.
### Handling challenge verdicts
`BLOCK` verdicts are a clear sign that traffic should be blocked. But in cases where it is unclear if the request is malicious, `CHALLENGE` verdicts are returned.
You can configure how Protected Auth handles challenge verdicts:
* See [Challenge verdict](/docs/fraud-risk/device-fingerprinting/verdict/challenge) to learn more about your options: Allow, Block, or Trigger CAPTCHA.
By default, the API will continue `CHALLENGE` verdicts as if they received an `ALLOW` verdict.
### CAPTCHA behavior
If you've configured a [CAPTCHA key](/docs/api-reference/consumer/frontend-sdks/react/resources/captcha#configuration) in the Dashboard, the behavior will depend on how Protected Auth is configured:
* If Protected Auth is in **Observation mode** or disabled, a CAPTCHA check will be run on *every* frontend SDK request
* If Protected Auth is in **Enforcement mode** *and* configured to trigger CAPTCHA on `CHALLENGE`s, a CAPTCHA check will be only be run upon a `CHALLENGE` verdict
***
## Protected methods
These methods will collect fingerprints and, when in *Enforcement mode*, prevent actions if a fingerprint receives a `BLOCK` verdict:
### Consumer
* `stytch.biometrics.authenticate`
* `stytch.cryptoWallets.authenticate`
* `stytch.impersonation.authenticate`
* `stytch.magicLinks.email.loginOrCreate`
* `stytch.magicLinks.email.send`
* `stytch.otps.authenticate`
* `stytch.otps.email.loginOrCreate`
* `stytch.otps.sms.loginOrCreate`
* `stytch.otps.sms.send`
* `stytch.otps.whatsapp.loginOrCreate`
* `stytch.otps.whatsapp.send`
* `stytch.passwords.authenticate`
* `stytch.passwords.create`
* `stytch.passwords.resetByEmail`
* `stytch.passwords.resetByEmailStart`
* `stytch.passwords.resetByExistingPassword`
* `stytch.passwords.resetBySession`
* `stytch.totps.authenticate`
* `stytch.totps.recover`
* `stytch.webauthn.authenticate`
### Multi-tenant / B2B
* `stytch.impersonation.authenticate`
* `stytch.magicLinks.authenticate`
* `stytch.magicLinks.discovery.authenticate`
* `stytch.magicLinks.email.discovery.send`
* `stytch.oauth.authenticate`
* `stytch.oauth.discovery.authenticate`
* `stytch.otps.email.authenticate`
* `stytch.otps.email.discovery.authenticate`
* `stytch.otps.email.discovery.send`
* `stytch.otps.email.loginOrSignup`
* `stytch.otps.sms.authenticate`
* `stytch.otps.sms.send`
* `stytch.passwords.authenticate`
* `stytch.passwords.discovery.authenticate`
* `stytch.passwords.discovery.resetByEmail`
* `stytch.passwords.discovery.resetByEmailStart`
* `stytch.passwords.resetByEmail`
* `stytch.passwords.resetByEmailStart`
* `stytch.passwords.resetByExistingPassword`
* `stytch.passwords.resetBySession`
* `stytch.recoveryCodes.recover`
* `stytch.recoveryCodes.rotate`
* `stytch.sso.authenticate`
* `stytch.totp.authenticate`
* `stytch.totp.create`
***
## Next steps
Follow our guide to setting up and using Protected Auth.
# Allow verdict
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/verdict/allow
Handling `ALLOW` verdicts for low-risk devices.
## Overview
When Stytch returns an `ALLOW` verdict, it is because this request does not raise warning flags or it matches known-good device configurations. You can allow the privileged action.
***
## Allow verdicts in the API
When you receive an `ALLOW` verdict in the [Fingerprint Lookup](/docs/api-reference/fraud/api/fingerprint-lookup) response, you should allow the request associated with that Telemetry ID to proceed.
If you maintain your own risk score based on cumulative Device Fingerprinting verdicts, you should assign a low level of risk to an `ALLOW` verdict.
## Allow verdicts in Protected Auth
When [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth) is enabled, the Stytch SDK will allow requests to proceed when an `ALLOW` verdict is returned.
***
## Blocking a device or device group receiving an allow verdict
Fraud is an adversarial environment, so some bad actors may still receive `ALLOW` verdicts. This may be due to manual abuse (where a human is clicking on their own browser) or novel evasion techniques.
To block a device or group of devices that's receiving an `ALLOW` verdict, [set a rule](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) to `BLOCK` that traffic.
# Block verdict
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/verdict/block
Handling `BLOCK` verdicts for high-risk devices.
## Overview
When Stytch returns an `BLOCK` verdict, it is because this device has automation or spoofing that is undesirable. You should block the privileged action.
***
## Block verdicts in the API
When you receive a `BLOCK` verdict in the [Fingerprint Lookup](/docs/api-reference/fraud/api/fingerprint-lookup) response, we recommend blocking the request associated with that Telemetry ID from proceeding.
Stytch returns a `verdict.reasons` array in the Fingerprint Lookup response that will indicate the reason for the `BLOCK` verdict. For example, the following response indicates that the request received a `BLOCK` verdict because it uses browser automation tooling and a deceptive user agent:
```json theme={null}
"verdict": {
"action": "BLOCK",
"detected_device_type": "UNKNOWN",
"is_authentic_device": false,
"reasons": ["USER_AGENT_DECEPTION", "HEADLESS_BROWSER_AUTOMATION"]
}
```
If you maintain your own risk score based on cumulative Device Fingerprinting verdicts, we recommend assigning a high level of risk to a `BLOCK` verdict.
You may also choose to assign different risk scores to different verdict reasons.
See [Reference guides](/docs/fraud-risk/development/overview) for a full list of possible verdict reasons.
## Block verdicts in Protected Auth
When [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth) is enabled in *Enforcement mode* and a `BLOCK` verdict is returned, the Stytch SDK will always prevent the user's request from proceeding.
***
## Unblocking a device or device group
If you'd like to unblock a device or group of devices that's receiving an `BLOCK` verdict, you can [set a rule](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) to `ALLOW` that traffic.
# Challenge verdict
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/verdict/challenge
Handling `CHALLENGE` verdicts for medium-risk devices.
## Overview
When Stytch returns a `CHALLENGE` verdict, that indicates unusual configuration that is often associated with attacks, but also has legitimate use cases. For example:
For example, if the request has characteristics of running in a virtual machine (VM), we may return a `CHALLENGE` verdict. Sometimes, VMs are used in automated attacks, but they are also used for legitimate applications like enterprise browsers. As a result, how you want to handle the request depends on your risk profile and the reason for the `CHALLENGE`.
Common strategies for handling `CHALLENGE` verdicts are:
* Requiring some additional user input (like requiring MFA or surfacing a CAPTCHA) before allowing the request to proceed.
* For sensitive applications, blocking all requests that receive `CHALLENGE` verdicts.
* Or, allowing all requests that receive `CHALLENGE` verdicts to proceed.
***
## Challenge verdicts in the API
When you receive a `CHALLENGE` verdict in the [Fingerprint Lookup](/docs/api-reference/fraud/api/fingerprint-lookup) response, we generally recommend introducing some additional verification (such as a CAPTCHA, MFA, or other step that requires user input) to ensure that the user is legitimate before allowing them to proceed.
We return a `verdict.reasons` array in the Fingerprint Lookup response that will indicate the reason for the `CHALLENGE` verdict. For example, the following response indicates that the request received a `CHALLENGE` verdict because it may have resulted from automatic browser traffic:
```json theme={null}
"verdict": {
"action": "CHALLENGE",
"detected_device_type": "UNKNOWN",
"is_authentic_device": false,
"reasons": ["POSSIBLE_BROWSER_AUTOMATION"]
}
```
If you maintain your own risk score based on cumulative Device Fingerprinting verdicts, you should assign a medium level of risk to a `CHALLENGE` verdict. You may also choose to assign different risk scores to different verdict reasons.
See [Reference guides](/docs/fraud-risk/development/overview) for a full list of verdict reasons.
## Challenge verdicts in Protected Auth
When [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth) is enabled in *Enforcement mode*, the Stytch SDK will allow requests that hit `CHALLENGE` verdicts to proceed by default.
For increased strictness, you can configure the behavior under [Frontend SDK settings](https://stytch.com/dashboard/sdk-configuration) to either block requests that result in a `CHALLENGE` verdictor to trigger a CAPTCHA:
### Run reCAPTCHA on a CHALLENGE verdict
Stytch offers a JavaScript SDK integration with Google's Invisible reCAPTCHA that runs before each SDK method call. Invisible reCAPTCHA is a type of CAPTCHA that evaluates traffic without requiring any interaction from the user.
If [reCAPTCHA](https://stytch.com/dashboard/captcha/recaptcha) is configured and a user calls a Stytch SDK method, the SDK retrieves a CAPTCHA token that is sent along with the SDK request. The Stytch backend calls the Google API on your behalf and returns an error if the reCAPTCHA doesn't meet the threshold that you set.
When *Challenge handling* is set to **Trigger CAPTCHA** and a `CHALLENGE` verdict is returned, the SDK will trigger the CAPTCHA flow in order to add another layer of protection from malicious requests.
***
## Allow or block a device or device group
If you'd like to allow or block a device or group of devices that's receiving an `CHALLENGE` verdict, you can [set a rule](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules).
## Override verdict reasons
You can [override verdict reasons](/docs/fraud-risk/device-fingerprinting/decisioning/override-verdict-reasons) if you do expect traffic with the warning flags that default to `CHALLENGE`, but would like to allow or block them.
# Not found errors
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/verdict/not-found
Handling 404 errors for Fingerprint Lookup.
## 404 telemetry\_id\_not\_found errors
If the `telemetry_id` is missing from the lookup request, or we cannot find it in our records, Stytch will return a 404 `telemetry_id_not_found` [error](/docs/api-reference/fraud/api/errors/404#telemetry_id_not_found).
This can happen for a few different reasons:
* An attacker can modify telemetry IDs to try to bypass your defenses.
* An attacker might omit telemetry IDs because they don't have a JavaScript environment.
* Your frontend implementation might have a bug involving `GetTelemetryID()`.
You should "fail closed" on 404 errors and treat them as if they were `BLOCK`.
See [Reference guides](/docs/fraud-risk/development/overview) for guidance on testing your integration.
## Not found results in the API
When you receive a 404 in the [Fingerprint Lookup](/docs/api-reference/fraud/api/fingerprint-lookup) response, we recommend blocking the request from proceeding.
A missing or invalid telemetry ID could be a sign of an attacker trying to bypass Device Fingerprinting by generating fake telemetry IDs or not passing them into requests.
## Not found results in Protected Auth
When [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth) is enabled in *Enforcement mode* and the `telemetry_id` is missing from the request or not found by Lookup, the Stytch SDK will always prevent the user's request from proceeding.
# Verdict actions & warning flags
Source: https://stytch.com/docs/fraud-risk/device-fingerprinting/verdict/overview
Device Fingerprinting provides verdicts with a recommended action and warning flags.
## Overview
The Stytch verdict provides actionable context with each fingerprint: action, warning flags (verdict reasons), and other metadata.
***
## Action
The verdict `action` is Stytch's recommendation for what action to take.
If you are protecting a privileged action, you can use the recommended action directly or combine it into your own [decisioning](/docs/fraud-risk/device-fingerprinting/decisioning/overview).
* `ALLOW` indicates this device does not raise warning flags or it matches known-good device configurations.
You can allow the privileged action.
* `BLOCK` indicates this device has automation or spoofing that is undesirable.
You should block the privileged action.
* `CHALLENGE` indicates unusual configuration that is often associated with attacks, but also has legitimate use cases.
You can add friction like 2FA or other verification before allowing the privileged action to proceed.
***
## Warning flags (verdict reasons)
The verdict `reasons`, also known as warning flags, provide context about why a particular action was recommended. Examples:
* A `BLOCK` verdict action with `[HEADLESS_BROWSER_AUTOMATION]` reasons indicates that Stytch detected signs of headless browser automation like Puppeteer.
* A `CHALLENGE` verdict action with `[VIRTUAL_MACHINE, SMART_RATE_LIMIT_WARNING]` reasons indicates that Stytch detected the request came from a virtual machine and that the request also triggered [Intelligent Rate Limiting](/docs/fraud-risk/device-fingerprinting/decisioning/intelligent-rate-limiting). While suspicious, it may still have legitimate uses.
If multiple reasons are present, the action will always be the strictest: `BLOCK` > `CHALLENGE` > `ALLOW`.
For a full list of possible reasons, see [Reference guides](/docs/fraud-risk/development/overview).
***
## Other metadata
Stytch also provides other metadata about the request in the verdict:
Stytch's proprietary assessment of device authenticity. It will be false if browser deception is detected.
Stytch's classification of the device operating system and architecture.
Provides context about [Rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) or [Verdict reason overrides](/docs/fraud-risk/device-fingerprinting/decisioning/override-verdict-reasons) that affected the verdict action.
## Examples
This verdict indicates an authentic Apple device:
```json theme={null}
{
// ...other fields omitted
"verdict": {
"action": "ALLOW",
"detected_device_type": "APPLE_CHROME",
"is_authentic_device": true,
"reasons": []
}
}
```
This verdict indicates a request that was blocked based on an [IP geolocation-based rule](/docs/fraud-risk/get-started/use-case/block-countries):
```json theme={null}
{
// ...other fields omitted
"verdict": {
"action": "BLOCK",
"detected_device_type": "APPLE_CHROME",
"is_authentic_device": true,
"reasons": ["RULE_MATCH"],
"rule_match_type": "COUNTRY_CODE",
"rule_match_identifier": "SY",
}
}
```
# Email Risk is coming soon
Source: https://stytch.com/docs/fraud-risk/email-risk/coming-soon
Detect suspicious email addresses for trust & safety and fraud prevention use cases.
## Contact us for early access
Email Risk provides high-confidence email and domain signals and recommended actions to take.
Email addresses reveal information that is useful for fraud detection. For example, fraudsters often use disposable email addresses or “tumbling” to sign up for multiple accounts and exploit trials or giveaways.
With Stytch Email Risk, you get high-confidence email and domain signals with recommended actions, so that you can block bad signups at the front door and prevent free trial abuse, promotional abuse, and other new account fraud.
Contact us at
[fraud-team@stytch.com](mailto:fraud-team@stytch.com)
for early access.
# Go-live checklist
Source: https://stytch.com/docs/fraud-risk/get-started/go-live-checklist
This guide provides a checklist of recommended steps to take before enforcing decisions on your production traffic.
## Overview
There are three main steps:
1. Document your use case and implementation plan
2. Ensure you've completed important integration steps
3. Run in production in observation mode
You don't need to do these in this exact order, but we recommend that you complete these three steps before enforcing decisions on your production traffic.
This checklist is also available as a fillable spreadsheet. To request a copy, email [support@stytch.com](mailto:support@stytch.com) or contact your account team.
***
## Document your use case and implementation plan
You'll want to define the problem, plan key decisions about your integration, and define outcomes. Documenting these will help you understand business goals and related design decisions, enabling you to revise them over time if needed.
### Define the problem
* What is the fraud or abuse problem we are trying to solve?
* How does the attacker benefit?
### Key integration decisions
* Are you using Protected Auth or the Device Fingerprinting API standalone?
* If using Protected Auth:
* How will you [handle challenges](/docs/fraud-risk/device-fingerprinting/verdict/challenge#challenge-verdicts-in-protected-auth)?
* If using the API:
* What actions will you fingerprint?
* What are the high-level [decisioning](/docs/fraud-risk/device-fingerprinting/decisioning/overview) and [enforcement](/docs/fraud-risk/device-fingerprinting/enforcement/overview) processes?
* Will you [set rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) or [override verdict reasons](/docs/fraud-risk/device-fingerprinting/decisioning/override-verdict-reasons) to customize your decisioning?
### Outcomes
* What are key metrics and indicators of success?
* How will you handle users who report issues
* How will you ensure a user-reported issue is not a social engineering attack?
***
## Ensure you've completed important integration steps
You'll want to complete the following steps before running in production:
1. Set up [custom domains](/docs/fraud-risk/development/custom-domains) to reduce ad-blocker interference.
2. If using the API:
1. Consider adding [external metadata](/docs/fraud-risk/device-fingerprinting/external-metadata) for additional context.
2. [Test your integration](/docs/fraud-risk/development/overview), including properly handling non-200 responses.
3. If you're using Protected Auth, these are automatically handled for you.
3. Review [privacy & compliance](/docs/fraud-risk/device-fingerprinting/privacy-and-compliance) considerations.
***
## Run Observation mode in production
When you're ready, you can collect fingerprints and verdicts in production without taking action. This is valuable because you can observe traffic and predicted actions to ensure they look correct, without disrupting your live users. You can view Fingerprint Lookup results from the last 30 days in the [Dashboard](https://stytch.com/dashboard/activity?tab=dfp-logs).
**For Protected Auth:**
* Set your *Connection type* to **Observation mode** to collect data without blocking any requests.
**For the API:**
* Call the Lookup API without taking any enforcement action on the result.
* You can also log results to your preferred observability or analytics platform.
***
## Next steps
Once you've completed the three steps above and confirmed the observation results look reasonable, you can start enforcing your decisions.
**For Protected Auth:**
* Switch to **Enforcement mode**.
**For the API:**
* Change your code (or feature flag, etc.) to enforce decisions based on your plan.
Now, you're protecting your application and users with Device Fingerprinting!
***
# Block traffic by country
Source: https://stytch.com/docs/fraud-risk/get-started/use-case/block-countries
Using IP-based geographic filters for access control
In this guide we'll walk through how to use Stytch Device Fingerprinting to block all traffic from certain countries.
You may want to block entire countries for regulatory reasons. For example, in the United States, regulatory sanctions ban business with countries like Cuba, Iran, North Korea, and Syria. You also may want to block countries where you have no business presence or see overwhelmingly abusive behavior.
You will:
* Decide on the list of countries you would like to block
* Run a shell script to create one [Rule](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) for each country to block
## Decide on the list of countries to block
First, decide which countries you want to block. You will need the ISO-3166-1 alpha-2 country codes, like `US` for United States, to set a country-based Device Fingerprinting Rule.
## Run a shell script to create one Rule for each country
The following shell function enables you to block a single country using the [Set Rule](/docs/api-reference/fraud/api/set-rule) API.
You can also create Rules with the [Stytch Dashboard](https://stytch.com/dashboard/dfp-rules). Or, use the Stytch SDK to call the [Set Rule](/docs/api-reference/fraud/api/set-rule) API.
```shell theme={null}
# Usage: stytch_block_country US "Block US traffic since we do not serve US customers"
# Requires the following environment variables:
# - STYTCH_PROJECT_ID
# - STYTCH_SECRET
stytch_block_country() {
local cc="$1"
local desc="$2"
if [[ -z "$STYTCH_PROJECT_ID" || -z "$STYTCH_SECRET" ]]; then
echo "Error: please export STYTCH_PROJECT_ID and STYTCH_SECRET" >&2
return 1
fi
if [[ -z "$cc" || ! "$cc" =~ ^[A-Za-z]{2}$ ]]; then
echo "Usage: stytch_block_country " >&2
return 1
fi
if [[ -z "$desc" ]]; then
echo "Error: description required" >&2
return 1
fi
echo "Blocking country: $cc — $desc"
curl -sS -X POST "https://telemetry.stytch.com/v1/rules/set" \
-u "${STYTCH_PROJECT_ID}:${STYTCH_SECRET}" \
-H "Content-Type: application/json" \
-d "$(printf '{"action":"BLOCK","country_code":"%s","description":"%s"}' "$cc" "$desc")"
}
```
Then, you can call this function for each country you would like to block:
```shell theme={null}
# Example list of US sanctioned countries: Cuba, Iran, North Korea, and Syria
SANCTIONED_COUNTRIES=(CU IR KP SY)
# Loop through and block each one
for cc in "${SANCTIONED_COUNTRIES[@]}"; do
stytch_block_country "$cc" "Block sanctioned country"
done
```
After creating the Rules, you can [view them in the Dashboard](https://stytch.com/dashboard/dfp-rules).
Now, any traffic originating from these countries (based on IP address) will receive a BLOCK verdict.
If you are using [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth) with Stytch authentication, Stytch will automatically reject attempts to sign up or login from these countries. If you are using Device Fingerprinting standalone, you should block the action in your backend code when you see a BLOCK verdict.
## What's next?
If you are using Stytch for authentication and use SMS or WhatsApp one-time passcodes (OTPs), you can set [country code allowlists](/docs/api-reference/pwa/api/v3/country-code-allowlist/set-country-code-allowlist-sms) to prevent toll fraud to phone numbers in certain countries.
In Device Fingerprinting, you can also set Rules based on other characteristics, such as `visitor_id` or `network_fingerprint`. See [Set decisioning rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) for more information.
# Credential stuffing attack
Source: https://stytch.com/docs/fraud-risk/get-started/use-case/credential-stuffing-attack
Protect your application and its users from credential stuffing attacks
## Overview
In this guide, we’ll walk through how to use Stytch’s Device Fingerprinting (DFP) to identify and stop a credential stuffing attack.
Credential stuffing is a cyberattack where attackers take usernames and passwords leaked from other platforms and use them on your application to perform account takeovers. This attack works because some users reuse the same credentials across multiple services. While the success rate for any individual account is very low, credential stuffing can result in many compromised accounts because of the sheer volume of automated attempts.
Stytch Device Fingerprinting is designed to block these attacks and provides telemetry needed to distinguish legitimate users from bad actors.
This guide uses the terminology of [the Stytch fraud prevention framework](/docs/fraud-risk/device-fingerprinting/overview). Follow the link to learn more about the framework.
## Signal gathering
You should collect and look up a fingerprint at the Login or Password Authentication step.
See Use the [Device Fingerprinting API](/docs/fraud-risk/get-started/using-dfp-api) to add DFP to your login flow.
If you’re using one of Stytch’s frontend or mobile SDKs, you can automatically collect and submit fingerprints by turning on [Protected Auth](/docs/fraud-risk/get-started/using-protected-auth). It wraps your login calls and handles the telemetry gathering for you.
## Warnings and Indicators
Monitor your DFP fingerprint submits through the Stytch Dashboard and your own internal logging or through Stytch’s [Event Log Streaming](/docs/resources/workspace-management/event-log-streaming). If you are experiencing a credential stuffing attack, you may notice one or more of the following factors:
* **Sudden increase in password authentication volume and velocity**
While a large spike in traffic may be malicious, you should also consider legitimate reasons for increased volume, such as a recent product launch or a marketing event.
* **Sudden increase in errors**
An elevation in 404 `email_not_found` and/or 401 `unauthorized_credentials` errors indicates attempts against emails that do not exist in Stytch. This is a strong indicator of automated credential stuffing. Generally, a high diversity of attempted emails distinguishes an attack from simple user error.
* **Unusual shift in DFP verdicts**
DFP detects automation tools, including browser automation and programmatic access. By default, any request which is detected as an automation receives a BLOCK verdict. See [Reference guides](/docs/fraud-risk/development/warning-flags) for a full list of all verdict reasons or use the [Get Verdict Reasons endpoint](/docs/api-reference/fraud/api/get-verdict-reasons).
## Decisioning
* **Identify confirmed attackers**
Once you’ve identified the bad actor by their IP address or a fingerprint, you can create a DFP Rule in your Stytch Dashboard to stop the attack. Before finalizing the rule, verify that it will not inadvertently block legitimate users. You can reference the uniqueness of each identifier in this [guide](/docs/fraud-risk/device-fingerprinting/fingerprints#table-of-different-identifiers).
* **Identify suspected compromised accounts**
If a login is successful, but originates from a Fingerprint or IP that was previously associated with a high volume of 404 `email_not_found` or 401 `unauthorized_credentials` errors, that account may be compromised. We recommend flagging these accounts for immediate password resets and session invalidation.
## How Stytch helps mitigate credential stuffing across our platform
Stytch’s DFP is designed to automate your defense against credential stuffing. Rather than requiring manual intervention, Stytch uses a proprietary fingerprinting method to protect your users from malicious actors.
* **[DFP Verdict Actions](/docs/fraud-risk/device-fingerprinting/verdict/overview)**
By default, DFP identifies bot signatures, headless browsers, and programmatic access. Most credential stuffing attempts will receive an immediate BLOCK verdict, allowing you to stop the attack at the API level.
* **[Intelligent Rate Limiting](/docs/fraud-risk/device-fingerprinting/decisioning/intelligent-rate-limiting)**
Stytch automatically limits traffic based on device risk. If an IP or Fingerprint begins "spraying" credentials, our system blocks that specific actor.
* **[Breach Detection](/docs/multi-tenant-auth/authentication/passwords/strength-policy#breach-detection)**
If you are using Stytch’s password product for authentication, there is built-in breach detection. Stytch integrates with [HaveIBeenPwned](https://haveibeenpwned.com/) to detect known passwords that have been leaked in data breaches. By default, Stytch verifies the user’s password on both password creation and subsequent authentications.
We also recommend implementing the following proactive security controls:
* **[Enable MFA](/docs/multi-tenant-auth/manage-sessions/intermediate-sessions/mfa)**: This is the single strongest control against credential stuffing.
* **[Add new device notifications](/docs/fraud-risk/get-started/use-case/new-device-notifications)**: Use Stytch’s user device history to send notifications about new devices and help identify potentially unauthorized access to user accounts.
* **[Configure User locks](/docs/resources/policies/platform/user-locks)**: The lock duration and number of allowed failed attempts are configurable at the project level via the Dashboard in the Password and User Policies page.
* **Implement Passwordless Authentication**: Moving to Email Magic Links or Passkeys removes the "password" variable entirely, eliminating the attack vector.
## What's next?
Learn how to [get started with Device Fingerprinting in just a few minutes](/docs/fraud-risk/get-started/using-dfp-api).
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Invisible CAPTCHA
Source: https://stytch.com/docs/fraud-risk/get-started/use-case/invisible-captcha
A recipe to add bot protection with a seamless user experience for your application
In this guide we'll walk through how to use Stytch Device Fingerprinting to implement "invisible captcha" bot protection for your application.
In a traditional captcha, every user needs to complete an interactive task, which adds friction while still being vulnerable to bot attacks. Invisible captcha solutions only prompt suspicious-looking users to complete a task, providing a seamless experience for most real users.
With Stytch Device Fingerprinting, you can easily implement invisible captcha protection. We use this ourselves in our auth products [to protect email magic links from being consumed by corporate email scanners](/docs/multi-tenant-auth/authentication/magic-links/overview#security), and you can use it in any flow where you don't want automated traffic.
## Goals
The goal of invisible captcha is to protect your application from automated abuse while maintaining a smooth user experience for legitimate users. This means our approach is to:
1. Identify and block automated requests transparently
2. Allow legitimate users to proceed without interruption
3. Prompt for additional verification only when necessary
Stytch Device Fingerprinting is hardened against reverse engineering and spoofing, so it provides more reliable bot detection than other invisible captcha providers.
## High level flow
To implement invisible captcha with Stytch Device Fingerprinting, you will first get the fingerprint and verdict:
1. The frontend client will call `GetTelemetryID()` and send the result to the backend for processing.
2. The backend server will call the Fingerprint Lookup API to fetch the results from Stytch.
3. In the Lookup response, Stytch will recommend a verdict action of `ALLOW`, `BLOCK`, or `CHALLENGE`.
Then, you will act on the recommended verdict action:
4. If the verdict is `ALLOW`, you can let the user proceed.
5. If the verdict is `BLOCK` or `CHALLENGE`, you can serve an interactive challenge or reject the client entirely.
## Getting a fingerprint and verdict
To get a fingerprint and Stytch's recommended verdict, you'll call `GetTelemetryID()` on the frontend and [Fingerprint Lookup](/docs/api-reference/fraud/api/fingerprint-lookup) on the backend.
For a step-by-step tutorial, see [Use the Device Fingerprinting API](/docs/fraud-risk/get-started/using-dfp-api).
Once you look up the fingerprint, you will be able to take action.
## Act on the recommended verdict action
### ALLOW response
The Fingerprint Lookup response may return an ALLOW verdict, like this:
```js theme={null}
{
"verdict": {
"action": "ALLOW",
"detected_device_type": "...",
"is_authentic_device": true,
"reasons": [...]
}
// other fields omitted
}
```
The `verdict.action` of ALLOW indicates that this device looks like a normal user on a normal device. You can immediately allow the user to carry out the protected action without any additional friction. In pseudocode:
```js theme={null}
lookupResponse = client.fraud.fingerprint.lookup({telemetry_id})
if (lookupResponse.verdict.action == "ALLOW") {
return doProtectedAction()
}
```
### BLOCK response
The Fingerprint Lookup response may return an BLOCK verdict, like this:
```js theme={null}
{
"verdict": {
"action": "BLOCK",
"detected_device_type": "UNKNOWN",
"is_authentic_device": false,
"reasons": ["HEADLESS_BROWSER_AUTOMATION"]
}
// other fields omitted
}
```
The `verdict.action` of BLOCK indicates that this device looks automated or malicious. Usually this is due to known automation warning flags detected by Stytch, or due to banned device signatures from known past abuse across Stytch's network.
You should prevent the user from completing the action.
```js theme={null}
lookupResponse = client.fraud.fingerprint.lookup({telemetry_id})
if (lookupResponse.verdict.action == "BLOCK") {
return genericErrorResponse()
}
```
We recommend returning opaque error messages to avoid giving information to the attacker. For example, if the protected action is a login, you can return a standard "Username or password is incorrect" message instead of telling the user they are blocked by Device Fingerprinting. See [Enforcement with Device Fingerprinting](/docs/fraud-risk/device-fingerprinting/enforcement/overview) for more guidance.
### CHALLENGE response
The Fingerprint Lookup response may return an CHALLENGE verdict, like this:
```js theme={null}
{
"verdict": {
"action": "CHALLENGE",
"detected_device_type": "UNKNOWN",
"is_authentic_device": false,
"reasons": ["VIRTUAL_MACHINE"]
}
// other fields omitted
}
```
The `verdict.action` of CHALLENGE indicates that this request is unusual in a way that may or may not be malicious. For example, virtual machines are often used in automated attacks, but they can also be used in legitimate enterprise browser environments.
Depending on how sensitive your action is, you have several options:
* Treat CHALLENGE like ALLOW, which is more permissive
* Treat CHALLENGE like BLOCK, which is more strict
* On CHALLENGE, prompt the user for additional verification. For example:
* Complete a traditional captcha-style interactive task
* Answer a simple question about your service
* Step-up with [multi-factor authentication (MFA)](/docs/consumer-auth/authentication/mfa/overview)
```js theme={null}
lookupResponse = client.fraud.fingerprint.lookup({telemetry_id})
if (lookupResponse.verdict.action == "CHALLENGE") {
return simpleChallenge("What is the name of this site?")
}
```
The amount of friction to add for a CHALLENGE depends on your application and how sensitive the action is.
## Modifying decisioning logic
Fraud and abuse prevention is an adversarial problem, so you may find instances where the recommended action is not correct. In traditional captcha systems, you might have trouble tuning a risk threshold with an acceptable false positive and false negative rate. Since Stytch provides rich contextual data, you can modify the decisioning logic precisely based on many dimensions.
* You can [set Rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) to allow or block specific fingerprints or IP geolocation features.
* You can [override verdict reasons](/docs/fraud-risk/device-fingerprinting/decisioning/override-verdict-reasons) if the default action is not ideal for your typical users.
* You can write custom code using any of the attributes of the Fingerprint Lookup response or your own application context
## What's next?
Learn how to [get started with Device Fingerprinting in just a few minutes](/docs/fraud-risk/get-started/using-dfp-api). Then, check out the [go-live checklist](/docs/fraud-risk/get-started/go-live-checklist) for production readiness.
Access to Device Fingerprinting is gated to help protect our users from reverse engineering. If you are interested in invisible captcha use cases, please contact us to request access.
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# New device notifications
Source: https://stytch.com/docs/fraud-risk/get-started/use-case/new-device-notifications
Alerting users about logins from new devices
New device notifications are a key security measure to help identify potentially unauthorized access to user accounts. When there is a login from a new device, IP address, or location, they should receive a notification. If the login is unauthorized, the user can take immediate action like revoking sessions, resetting their password, or contacting your support team.
You can easily send notifications for new devices, IP address, and login locations with Stytch Device Fingerprinting. At a high level, you will include a Telemetry ID (for device information) in your authentication requests. Stytch will return new device flags in the authentication response and as a webhook event.
In this guide, you will learn how to use Stytch's user device history to send notifications about new devices. You will:
1. Collect device fingerprints from the frontend and submit them as part of the authentication request
2. Use the Stytch response or webhook to detect new devices
3. Implement your own method to notify your user about unusual logins
This guide uses an integrated approach where you use Stytch authentication and Device Fingerprinting together.
This guide uses Consumer authentication. If you are using B2B authentication, the methods are similar.
## Prerequisites
1. A [Stytch project](https://stytch.com/dashboard) for Consumer Authentication.
* 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 Project, and then select B2B Authentication or Consumer Authentication.
2. Access to Device Fingerprinting. If you don't have access, you can [request a trial](https://offers.stytch.com/dfp-30-day-trial?utm%5Fsource=stytch%5Fdocs\&utm%5Fmedium=direct\&utm%5Fcontent=dfp%5F30%5Fday%5Ftrial).
3. An application that has already implemented an authentication method with Stytch.
## Collecting device fingerprints and submitting them
You will use Stytch Device Fingerprinting to collect device information. The implementation differs depending on whether you use a frontend SDK or a backend SDK.
If you're using one of Stytch's [frontend](/docs/api-reference/b2b/frontend-sdks) or [mobile SDKs](/docs/api-reference/b2b/mobile-sdks), you can automatically collect and submit fingerprints by turning on [Protected Auth](/docs/fraud-risk/device-fingerprinting/protected-auth).
On the dashboard [Frontend SDK page](https://stytch.com/dashboard/sdk-configuration), under optional configuration, enable Protected Auth.
In **Observation** mode, this will collect and record device information. In **Enforcement** mode, Protected Auth will also block authentication attempts that receive a BLOCK verdict.
For sending new device notifications, you can use either mode.
These code snippets are equivalent to steps 1 and 2 of the [Getting Started API guide](/docs/fraud-risk/get-started/using-dfp-api).
If you're using one of Stytch's backend SDKs or integrating directly with the Stytch API, you will need to add the Device Fingerprinting script to your application frontend, call `GetTelemetryID()`, and pass the resulting Telemetry ID to the backend SDK call.
In your application frontend:
```html theme={null}
```
Then in your backend, include the telemetry ID in your authenticate request to Stytch. To see which endpoints accept a telemetry ID, check the [API documentation](/docs/api-reference/consumer/api/overview) .
```js theme={null}
(req, res) => {
const telemetryId = req.headers['x-telemetry-id'];
const params = {
token: "SeiGwdj...mXD4=",
telemetry_id: telemetryId, // include the telemetry_id
};
const response = await client.magicLinks.authenticate(params);
console.log(response);
};
```
Now, Stytch will store the device information for the user. The Stytch API response will include information about whether the device is new or has been previously seen.
## Detecting new devices through webhook or response
You can detect new devices in two ways:
* Using a [webhook](/docs/resources/workspace-management/webhooks) sent by Stytch
* Or, in the response body for a backend authenticate request
### Webhook
You can use webhooks to receive data about new devices.
If you're using one of Stytch's frontend or mobile SDKs, this is the only way to send new device notifications. If you're using a backend integration, you can use either webhooks or the authenticate response.
With webhooks, you can de-couple the core application logic and any notifications you send.
In the [Webhooks](https://stytch.com/dashboard/webhooks) page of the Dashboard, you will configure a new webhook URL. Your application should listen on this URL for new device events.
The webhook event name differs depending on which kind of Stytch auth you are using.
In Consumer auth, you can subscribe to the `direct.user_device.new_device_attribute` event to receive notifications about new devices. The resulting events will look like this:
```js theme={null}
USER_DEVICE = {
"action": "NEW_DEVICE_ATTRIBUTE",
"event_id": "webhook-test-...",
"id": "user-test-...",
"object_type": "user_device",
"project_id": "project-test-...",
"source": "DIRECT",
"timestamp": "2025-01-02-...",
"vertical": "CONSUMER",
"user_device": {
"visitor_id": "visitor-...",
"visitor_id_details": {
"is_new": true,
"first_seen_at": "2025-01-02-....",
"last_seen_at": null
},
"ip_address": "1.2.3.4",
"ip_address_details": {
"is_new": false,
"first_seen_at": "2024-12-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_city": "San Francisco",
"ip_geo_country": "US",
"ip_geo_country_details": {
"is_new": false,
"first_seen_at": "2024-11-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_region": "California"
}
}
```
In Multi-tenant auth, you can subscribe to the `direct.member_device.new_device_attribute` event to receive notifications about new devices. The resulting events will look like this:
```js theme={null}
MEMBER_DEVICE = {
"action": "NEW_DEVICE_ATTRIBUTE",
"event_id": "webhook-test-...",
"id": "member-test-...",
"object_type": "member_device",
"project_id": "project-test-...",
"source": "DIRECT",
"timestamp": "2025-01-02-...",
"vertical": "B2B",
"member_device": {
"visitor_id": "visitor-...",
"visitor_id_details": {
"is_new": true,
"first_seen_at": "2025-01-02-....",
"last_seen_at": null
},
"ip_address": "1.2.3.4",
"ip_address_details": {
"is_new": false,
"first_seen_at": "2024-12-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_city": "San Francisco",
"ip_geo_country": "US",
"ip_geo_country_details": {
"is_new": false,
"first_seen_at": "2024-11-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_region": "California"
}
}
```
In both cases, the field `.is_new` will be true if this is the first time any of the following attributes have been seen for this user:
* Device (based on Visitor ID)
* IP address
* Country (based on IP geolocation)
You can also use the `first_seen_at` and `last_seen_at` fields for additional context.
If a user logs in with a combination of device, IP address, and country that are all already-known, no webhook event will be sent.
You can write custom code that listens for the `new_device_attribute`. When a new device is detected, notify the user through email, push notification, or in-app inbox.
### Authenticate response (backend only)
The new device data in the response body should only be used in *backend* integrations.
On the frontend, an attacker could modify the response body. The response body should not be used for security-sensitive purposes in a frontend integration.
If you are using a backend integration, you can use the response body to detect new devices and send notifications.
Every endpoint supported by user device history will return a `user_device` field. For example, if you are using Email Magic Links, the response will look something like this:
```js theme={null}
{
"user_device": {
"visitor_id": "visitor-....",
"visitor_id_details": {
"is_new": true,
"first_seen_at": "2025-01-02-....",
"last_seen_at": null
},
"ip_address": "1.2.3.4",
"ip_address_details": {
"is_new": false,
"first_seen_at": "2024-12-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_city": "San Francisco",
"ip_geo_country": "US",
"ip_geo_country_details": {
"is_new": false,
"first_seen_at": "2024-11-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_region": "California"
},
"session": {
"authentication_factors": [
{
"delivery_method": "email",
...
}
],
"custom_claims": {},
...
}
"user_id": "user-live-..",
...
},
```
Every endpoint supported by member device history will return a `member_device` field. For example, if you are using Email Magic Links, the response will look something like this:
```js theme={null}
{
"member_device": {
"visitor_id": "visitor-....",
"visitor_id_details": {
"is_new": true,
"first_seen_at": "2025-01-02-....",
"last_seen_at": null
},
"ip_address": "1.2.3.4",
"ip_address_details": {
"is_new": false,
"first_seen_at": "2024-12-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_city": "San Francisco",
"ip_geo_country": "US",
"ip_geo_country_details": {
"is_new": false,
"first_seen_at": "2024-11-02-....",
"last_seen_at": "2025-01-02-...."
},
"ip_geo_region": "California"
},
"member_session": {
"authentication_factors": [
{
"delivery_method": "email",
...
}
],
"custom_claims": {},
...
}
"member_id": "member-live-..",
...
},
```
You can then write your custom code that handles new devices, like sending the user (or member) a notification through email, push notification, or in-app inbox.
## Best practices for notifications
Once you've detected a login from a new device, you can notify them.
Here are a few things to keep in mind when sending new device notifications:
1. "Not me" action
2. Balancing signal and noise
3. Latency considerations
### "Not me" action
Your new device notification should include a "Not me" action to take if the login was not actually coming from the real user. When the user selects "Not me", you can respond immediately to the suspicious login:
* Help them contact your support team with relevant information
* Automatically [revoke](/docs/api-reference/b2b/api/sessions/revoke-session) all [sessions](/docs/api-reference/b2b/api/sessions/get-session) associated with that user
* For password-based logins, you can reset the password
In the longer term, you can help improve their account security:
* Increase your password requirements, [including turning on breach detection](/docs/multi-tenant-auth/authentication/passwords/strength-policy)
* Switch away from password-based logins to use more secure options like passwordless ([magic links](/docs/multi-tenant-auth/authentication/magic-links/overview)) or [OAuth](/docs/multi-tenant-auth/authentication/oauth/overview)
* [Add MFA](/docs/multi-tenant-auth/authentication/mfa/overview) through SMS, TOTP, or Passkeys
### Balancing signal and noise
A new device is not necessarily an indicator of a bad actor attempting to log in. The three new device attributes will naturally change over time for real users:
* The `visitor_id` may change due to users switching browsers or clearing their cache.
* IP addresses change naturally. Users may switch networks during normal usage, especially on mobile devices or when using VPNs. Even on the same network, Internet Service Providers (ISPs) may change the IP address of a client.
* The `ip_geo_country` is based on IP address and may change more frequently in geographical areas where multiple countries are close to each other.
You can observe your own traffic to see which factors are most useful for your users, and tailor your notifications accordingly. For example, you may find that within your user base, IP addresses change often, so you can send a notification only if the visitor ID or country change as well. You can also contact Stytch support for help designing your notifications.
### Latency considerations
If you are using the authenticate response in your backend integration, consider the latency impact.
For example, when a user logs in from a new device, you may want to send a notification via email. Sending an email can take multiple seconds, so it would be a poor experience if the user needed to wait for the email to be sent before they completed their login.
Instead of synchronously sending notifications in your application code, you can send notifications async or add them to a task queue, while immediately returning the logged-in session to the user.
## What's next?
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Prevent free trial abuse
Source: https://stytch.com/docs/fraud-risk/get-started/use-case/prevent-free-trial-abuse
Protect your resources for real users by stopping fake accounts and related abuse
In this guide we'll walk through how to use Stytch Device Fingerprinting to prevent free trial abuse.
Free trials are vulnerable to account creation abuse, in which a single actor signs up for multiple accounts in order to claim more free trial credits. Rewards programs ("promo abuse") and giveaways (fraudulent entries) are also at risk for this kind of fraud.
Stytch Device Fingerprinting provides powerful tools to prevent free trial abuse.
## Goals and overview
The business purpose of a free trial is to enable real new users to try your product. This means our goal is to reduce "wasted" free trial resources that are given to abusers, without impacting real users.
Using Stytch Device Fingerprinting, you will be able to make better decisions by:
1. Identifying automated requests, including scripts, bots, and browser automation
2. Identifying confirmed repeat users using the Visitor ID
3. Identifying suspected repeat users based on fingerprint and traffic patterns
Then using these identifications, you can add enforcement actions to stop users from signing up, or add additional friction to protect your free trial resources.
This guide uses the terminology of [the Stytch fraud prevention framework](/docs/fraud-risk/device-fingerprinting/overview). Follow the link to learn more about the framework.
## Signal gathering
You should collect and look up a fingerprint at account creation time.
See [Use the Device Fingerprinting API](/docs/fraud-risk/get-started/using-dfp-api) to add Device Fingerprinting to your application and start collecting data.
You should also send [external metadata](/docs/fraud-risk/device-fingerprinting/external-metadata) related to the account creation, which will help you correlate the fingerprints with additional application context.
## Decisioning
Using the fingerprint data and the Stytch verdict, you will be able to classify different kinds of new accounts.
### 1. Identify automated and malicious requests
Device Fingerprinting detects automation tools, including browser automation and programmatic access. By default, any request which is detected as automation receives a verdict with recommended action `BLOCK`. Other warning signs like the use of Tor or high-velocity traffic will also receive a `BLOCK` verdict.
You should block any request that receives a `BLOCK` verdict from Stytch.
### 2. Identify confirmed repeat users
In Device Fingerprinting, the Visitor ID is guaranteed to be unique for each user. If you see multiple attempts to create an account that share the same Visitor ID, these accounts are being created from the same device.
You should block any request that shares a Visitor ID with earlier requests.
### 3. Identify suspected repeat users
In Device Fingerprinting, the Visitor Fingerprint is a collection of various device characteristics. The Visitor Fingerprint does not change when using incognito, clearing cookies, or using a VPN.
However, the Visitor Fingerprint is not guaranteed to be unique. It will be identical between two devices with the same hardware and software configuration. High traffic that shares the same Visitor Fingerprint may be manual abuse: a bad actor repeatedly creating accounts by clicking around on your website.
You can still use the Visitor Fingerprint to identify traffic with suspicious characteristics. As a general rule, if 1% of your traffic shares the same Visitor Fingerprint, this is suspicious and worth further investigation.
By combining Visitor Fingerprint with other attributes like IP address, you can also get more granular views. These can also be rate-limited or restricted based on your expected traffic.
Stytch support can help you determine reasonable rate limits for your traffic level and use case.
## Enforcement
Now that you have identified different categories of users, you can take action.
As described in [Enforcement with Device Fingerprinting](/docs/fraud-risk/device-fingerprinting/enforcement/overview), you have multiple options.
For each of the three categories above, you could:
* Immediately block sign-ups and do not create an account.
* Or, restrict the accounts until they pass stricter uniqueness checks, like SMS 2FA phone number or credit card number.
* Or, [shadow-ban](https://en.wikipedia.org/wiki/Shadow_banning) the account and provide a limited or worse experience.
* Or, allow the creation of accounts, but log the information and use it to periodically perform "ban waves".
Each of these strategies has different trade-offs depending on how expensive your resource is, how quickly it can be exploited, and how quickly you give feedback to an attacker.
As a starting point, we recommend:
* For sign-ups with a `BLOCK` verdict, immediately block the sign-up with a generic error message.
* For confirmed repeat users, allow account creation but provide a shadow-banning experience.
* For suspected repeat users, log the information and review regularly. Once you have established baseline activity metrics, you can block more proactively using appropriate rate limits.
## Analysis and feedback loop
You can monitor your results through the [Device Fingerprinting Dashboard](https://stytch.com/dashboard/activity?tab=dfp-logs), as well as your own logging and analytics systems.
Using these logs and metrics, you should:
* Track the costs associated with free trial abuse
* Exclude fake users from growth metrics and marketing data
* Observe traffic patterns over time
You may also receive reports of false positives from users asking to be unblocked. These reports may be social engineering attacks from bad actors who are trying to evade your defenses. If you do confirm the identity of the reporter, you can [Set Rules](/docs/fraud-risk/device-fingerprinting/decisioning/set-rules) to allowlist them.
## What's next?
Learn how to [get started with Device Fingerprinting in just a few minutes](/docs/fraud-risk/get-started/using-dfp-api).
If you are interested in stopping free trial abuse, please reach out to Stytch.
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Remembered device
Source: https://stytch.com/docs/fraud-risk/get-started/use-case/remembered-device
A recipe to implement a remembered device flow with Stytch Device Fingerprinting
You can see a hosted demo of this flow at our demo application [here](https://www.stytchdemo.com/recipes/remembered-device-integrated). On the [stytchdemo.com](https://www.stytchdemo.com) homepage, it is the "Remembered Device" recipe card. The relevant code is in GitHub [here](https://github.com/stytchauth/stytch-nextjs-integration).
In this guide we'll walk through how to use Stytch's Device Fingerprinting (DFP) product to implement a remembered device flow. Remembered device flows are a form of adaptive MFA, or triggering MFA only on unrecognized logins.
For this guide, we'll use the [Visitor ID](/docs/fraud-risk/device-fingerprinting/fingerprints) as the known device identifier. Visitor ID is a cookie-based identifier, and is a good choice for allowlists because good actors don't often intentionally clear their cookies, though it can happen naturally - for example, if they use a different browser. Other good examples for trigger points are login attempts from a new IP address or a new country.
We'll be using a Stytch Consumer project in a NextJS application for the purposes of this guide, but the same basic steps apply to any auth implementation. You will:
Generate device fingerprints and signals from the frontend and submit them as part of the authentication request.
Evaluate whether this is a recognized device using either the Stytch Device Fingerprinting [Lookup response](/docs/api-reference/fraud/api/fingerprint-lookup) or the `user_device` authentication response field, depending on which approach you choose in step 2, and Stytch [User metadata](/docs/api-reference/consumer/api/resources/metadata).
Conditionally show super-secret data within the application if either:
* The login request is from a known device, or
* The request is from a new device and the user completes Step-up MFA.
Store this context in [Session custom claims](/docs/consumer-auth/manage-sessions/custom-claims) via a claim called `authorized_for_secret_data` for future reference throughout your application, and update the Known Devices list.
## Before you start
In order to complete this guide, you'll need:
1. A Stytch project (either Consumer or B2B).
* 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 Project, and then select B2B Authentication or Consumer Authentication.
2. Access to Device Fingerprinting. If you don't have access, you can [request a trial](https://offers.stytch.com/dfp-30-day-trial?utm%5Fsource=stytch%5Fdocs\&utm%5Fmedium=direct\&utm%5Fcontent=dfp%5F30%5Fday%5Ftrial).
3. A basic authentication flow on which to add adaptive MFA.
* If you don't have one set up already, you can use one of our example apps. The snippets in this guide will follow [a recipe from our Next.js demo application](https://www.stytchdemo.com/recipes/remembered-device-integrated) (code [here](https://github.com/stytchauth/stytch-nextjs-integration) - look for "remembered device" files).
* This guide uses Stytch for authentication, but any backend authentication implementation can work.
* If you are using Stytch for authentication, this guide assumes you're using a Consumer project. The same steps would apply to B2B projects as well if you don't want to use Stytch's [built-in B2B MFA offerings](/docs/multi-tenant-auth/authentication/mfa/overview).
## Step-by-step walkthrough
The first step of this guide is step 1 and 2 of the [Getting Started guide](/docs/fraud-risk/get-started/using-dfp-api#step-1%3A-add-the-device-fingerprinting-script-to-your-app).
Add the Stytch telemetry script to your login page:
```js theme={null}
```
Add a method or event handler to call the async function `GetTelemetryID()`, which gathers signals from the device and resolves to a `telemetry_id`.
```js theme={null}
const authenticateWithTelemetry = async () => {
// Get telemetry_id from the telemetry script
let telemetryId: string | undefined;
try {
const publicToken = process.env.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN;
telemetryId = await (window as any).GetTelemetryID({ publicToken });
} catch (telemetryError) {
console.warn('Could not get telemetry ID:', telemetryError);
}
```
When you call your backend authenticate endpoint, send the `telemetry_id` along as well.
In the snippet below from the example app flow, we're using Email Magic Links as our authentication method, so the `telemetry_id` is generated and sent along in the request to authenticate the Magic Link token.
```js theme={null}
// Call our authenticate API
const response = await fetch('/api/authenticate_eml_remembered_device', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(telemetryId && { 'X-Telemetry-ID': telemetryId }),
},
body: JSON.stringify({ token }),
});
```
The Stytch integrated approach assumes you are using Stytch for authentication and Device Fingerprinting and relies on specific Stytch features. If you are not using Stytch for auth, switch to the Standalone DFP tab.
Your backend endpoint will:
1. Authenticate the Email Magic Link `token`.
2. Get the Visitor ID from the `user_device` response.
3. Compare this against the known devices list to determine whether this is a known device or not.
4. Store the remembered device context in `session.custom_claims` for reference downstream.
```js theme={null}
// First, authenticate the magic link token and include the telemetry_id
let authenticateResponse = await stytchClient.magicLinks.authenticate({
token: token,
session_duration_minutes: 10080,
telemetry_id: telemetryId,
});
```
You can grab the `visitor_id` from the `user_device` response field. Determine whether MFA is required based on whether the request is from a known device:
* If known:
* Set `authorized_for_secret_data: true` in the Session claims
* Return super secret data already, or optionally return it elsewhere in your application, gated on `session.custom_claims.authorized_for_secret_data: true`
* If not known:
* Store the pending Visitor ID in session custom claims for reference later on (to add to the known devices list in the case of a successful MFA flow)
* Set `authorized_for_secret_data: false` in the Session claims
* Let the frontend know that MFA is required
Note that the `user_device` in the response could be null if the fingerprint lookup failed for any reason, such as an invalid `telemetry_id`. If the `user_device` is null, you should fail closed and always require MFA for security reasons.
```js theme={null}
// Function to determine if a device is known
function isKnownDevice(visitorID: string, knownDevices: string[]) {
return knownDevices.includes(visitorID);
}
// Get the list of already known devices and the visitor ID of this request
const knownDevices = authenticateResponse.user.trusted_metadata?.known_devices || [];
// Fail closed: if the `visitor_id` is not present in the response, require MFA
const visitorID = authenticateResponse.user_device?.visitor_id ?? '';
// Check if the Visitor ID is in the known devices list
const knownDevice = isKnownDevice(visitorID, knownDevices) && visitorID !== '';
// Set the session cookie in the response
cookies.set('api_sms_remembered_device_session', authenticateResponse.session_token, {
httpOnly: true,
maxAge: 1000 * 60 * 30,
});
// Update the session
if (knownDevice) {
// Update session with custom claims to mark this session as authorized
await stytchClient.sessions.authenticate({
session_token: authenticateResponse.session_token,
session_custom_claims: {
authorized_for_secret_data: true,
authorized_device: visitorID,
},
});
} else {
// Store pending visitorID in session custom claims for later retrieval during OTP auth
await stytchClient.sessions.authenticate({
session_token: authenticateResponse.session_token,
session_custom_claims: {
authorized_for_secret_data: false,
pending_device: visitorID,
},
});
}
```
Your backend endpoint will:
1. Authenticate the Email Magic Link `token`.
2. Make the DFP `/lookup` call to determine the Visitor ID.
3. Compare this against the known devices list to determine whether this is a known device or not.
4. Store the remembered device context in `session.custom_claims` for reference downstream.
```js theme={null}
// First, authenticate the magic link token
const authenticateResponse = await stytchClient.magicLinks.authenticate({
token: token,
session_duration_minutes: 10080,
});
// Get the list of already known devices:
const knownDevices = authenticateResponse.user.trusted_metadata?.known_devices || [];
```
Perform the `/lookup` call to check the Visitor ID for this login request:
```js theme={null}
// Fail closed: require MFA if no telemetry ID provided
if (!telemetryId) {
console.log('No telemetry ID provided, requiring MFA (fail closed)');
requiresMfa = true;
} else {
try {
// Lookup the telemetry ID response to get the Visitor ID
const fingerprintResponse = await stytchClient.fraud.fingerprint.lookup({
telemetry_id: telemetryId,
});
// Logic for decisioning is in the following snippets
} catch (err) {
console.error('Error checking remembered device:', telemetryError);
// Fail closed: if telemetry lookup fails, require MFA for security
requiresMfa = true;
}
}
```
In the block where you successfully get a lookup response, determine whether MFA is required based on whether the request is from a known device:
* If the device is **known**:
* Set `authorized_for_secret_data: true` in the Session claims
* Return super secret data in the response
* Or, optionally return super secret data elsewhere in your application, gated on `session.custom_claims.authorized_for_secret_data: true`
* Otherwise, the device is **not known**:
* Store the pending Visitor ID in session custom claims for reference later on (to add to the known devices list in the case of a successful MFA flow)
* Set `authorized_for_secret_data: false` in the Session claims
* Let the frontend know that MFA is required
```js theme={null}
// Function to determine if a device is known
function isKnownDevice(visitorID: string, knownDevices: string[]) {
return knownDevices.includes(visitorID);
}
// Default to closed
let requiresMfa = true;
... // Inside the try/catch from above:
// Check if the Visitor ID is in the known devices list
if (isKnownDevice(visitorID, knownDevices) && visitorID !== '') {
console.log('Visitor ID is known, no MFA required');
requiresMfa = false;
// Update session with custom claims to mark this session as authorized
await stytchClient.sessions.authenticate({
session_token: authenticateResponse.session_token,
session_custom_claims: {
authorized_for_secret_data: true,
authorized_device: visitorID,
},
});
} else {
console.log('Visitor ID is not known, MFA required');
requiresMfa = true;
// Store pending visitorID in session custom claims for later retrieval during OTP auth
await stytchClient.sessions.authenticate({
session_token: authenticateResponse.session_token,
session_custom_claims: {
authorized_for_secret_data: false,
pending_device: visitorID,
},
});
}
```
Finally, return a response to your frontend with the Session. In this example, we go ahead and include the super secret data in the response if MFA isn't required.
```js theme={null}
return res.status(200).json({
session_token: authenticateResponse.session_token,
visitorID: visitorID,
user_id: authenticateResponse.user_id,
super_secret_data: knownDevice ? SUPER_SECRET_DATA.REMEMBERED_DEVICE : undefined,
});
```
**Trusted Device** → Grant Access
**Untrusted Device** → Prompt MFA
Now your application enters the Conditional Access and MFA Enforcement step: Trusted devices receive access to the protected data; unrecognized devices must complete MFA before access is granted.
Below are two different approaches - the first uses a dedicated backend endpoint, the other uses React's `getServerSideProps`:
In our [integrated example application recipe](https://github.com/stytchauth/stytch-nextjs-integration/blob/main/pages/recipes/api-sms-remembered-device-integrated/profile.tsx), we implement that logic in [a backend endpoint](https://github.com/stytchauth/stytch-nextjs-integration/blob/main/pages/api/known_devices_integrated.ts) called `/api/known_devices_integrated`.
```js theme={null}
export async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
// Lookup the session to get the user id
const cookies = new Cookies(req, res);
const storedSession = cookies.get('api_sms_remembered_device_session');
if (!storedSession) {
return res.status(400).json({ errorString: 'No session provided' });
}
const stytchClient = loadStytch();
const { session, user } = await stytchClient.sessions.authenticate({
session_token: storedSession,
});
const hasRegisteredPhone = user.phone_numbers.length > 0;
const phoneNumber = user.phone_numbers[0]?.phone_number ?? '';
// Server-side authorization check based on session authentication factors and custom claims
const hasEmailFactor = session.authentication_factors.find((i: any) => i.delivery_method === 'email');
const hasSmsFactor = session.authentication_factors.find((i: any) => i.delivery_method === 'sms');
const visitorID = session.custom_claims?.authorized_device || session.custom_claims?.pending_device || 'no visitor ID';
let superSecretData = null;
let requiresMfa = true; // Default to requiring MFA unless session proves otherwise
const isRememberedDevice = session.custom_claims?.authorized_for_secret_data;
if (hasEmailFactor && hasSmsFactor) {
// User has completed full MFA - authorized for super secret data
superSecretData = SUPER_SECRET_DATA.FULL_MFA;
requiresMfa = false;
} else if (isRememberedDevice) {
// User is in a remembered device location (authorized during EML auth via session claims)
superSecretData = SUPER_SECRET_DATA.REMEMBERED_DEVICE;
requiresMfa = false;
} else {
// User needs MFA - either no email factor or not in trusted location
requiresMfa = true;
}
// Get the known devices for the user
const deviceList = user.trusted_metadata?.known_devices || [];
// Return success
return res.status(200).json({
deviceList,
user,
session,
hasRegisteredPhone,
phoneNumber,
superSecretData,
isRememberedDevice,
requiresMfa,
visitorID,
});
} else {
return res.status(405).end();
}
}
```
In our [standalone example application recipe](https://github.com/stytchauth/stytch-nextjs-integration/blob/main/pages/recipes/api-sms-remembered-device/profile.tsx), we implement that logic in `getServerSideProps`. Notably, all of the variables used in the logic here cannot be changed or accessed by a user:
```js theme={null}
export const getServerSideProps: GetServerSideProps = async (context) => {
const cookies = new Cookies(context.req, context.res);
const storedSession = cookies.get('api_sms_remembered_device_session');
...
// Get the session data
const { session } = await stytchClient.sessions.authenticate({ session_token: storedSession });
let superSecretData = null;
let isRememberedDevice = false;
let requiresMfa = true; // Default to requiring MFA unless session proves otherwise
let visitorID = '';
// Server-side authorization check based on session authentication factors
const hasEmailFactor = session.authentication_factors.find((i: any) => i.delivery_method === 'email');
const hasSmsFactor = session.authentication_factors.find((i: any) => i.delivery_method === 'sms');
if (hasEmailFactor && hasSmsFactor) {
// User has completed full MFA - authorized for super secret data
superSecretData = SUPER_SECRET_DATA.FULL_MFA;
requiresMfa = false;
} else if (hasEmailFactor && session.custom_claims?.authorized_for_secret_data) {
// User is in a remembered device location (authorized during EML auth via session claims)
superSecretData = SUPER_SECRET_DATA.REMEMBERED_DEVICE;
isRememberedDevice = true;
requiresMfa = false;
visitorID = session.custom_claims.authorized_device as string || '';
} else {
// User needs MFA - either no email factor or not in trusted location
requiresMfa = true;
visitorID = session.custom_claims?.pending_device as string || '';
}
return {
props: {
user: JSON.parse(JSON.stringify(user)),
session: JSON.parse(JSON.stringify(session)),
...
superSecretData,
isRememberedDevice,
requiresMfa,
visitorID,
},
}
```
Use this known device data to determine frontend behavior; for example, the React component in our example app looks like:
```js theme={null}
// Fetch the known devices data from the endpoint above:
useEffect(() => {
const fetchKnownDevices = async () => {
const resp = await fetch('/api/known_devices_integrated', { method: 'POST' });
const data = await resp.json();
if (data.errorString) {
setError(data.errorString);
} else {
setDeviceResponse(data as SuccessData);
}
};
fetchKnownDevices();
}, []);
...
Super secret area
{deviceResponse.superSecretData ? (
{deviceResponse.superSecretData}
{deviceResponse.isRememberedDevice && (
🎉 Device remembered!
)}
) : (
<>
{deviceResponse.requiresMfa
? `Additional authentication required. This appears to be a new device (${deviceResponse.visitorID || 'unknown device'}). Please complete SMS verification to continue.`
: 'Super secret profile information is secured by two factor authentication. To unlock this area complete the SMS OTP flow.'
}
```
If a user logs in from a new Visitor ID and successfully completes MFA, add that new visitor ID to the known devices list so they can skip MFA in subsequent logins.
This should be done on your backend and require a Session with both factors (primary and MFA):
```js theme={null}
export async function handler(req: NextApiRequest, res: NextApiResponse) {
const stytchClient = loadStytch();
// Get session from cookie
const cookies = new Cookies(req, res);
const storedSession = cookies.get('api_sms_remembered_device_session');
// Authenticate the session to get user ID and verify factors
const { session } = await stytchClient.sessions.authenticate({ session_token: storedSession });
// Verify that the session has both EML and SMS factors (proving MFA completion)
const hasEmailFactor = session.authentication_factors.some(f => f.delivery_method === 'email');
const hasSmsFactor = session.authentication_factors.some(f => f.delivery_method === 'sms');
if (!hasEmailFactor || !hasSmsFactor) {
return res.status(403).json({ errorString: 'MFA not completed. Both email and SMS factors required.' });
}
// Get the pending device from session custom claims that was stored during EML authentication
const pendingDevice = session.custom_claims?.pending_device;
// Get user to access existing trusted metadata
const user = await stytchClient.users.get({ user_id: session.user_id });
// Get existing known devices or initialize empty array
const existingKnownDevices = user.trusted_metadata?.known_devices || [];
// Add new device if it's not already in the list
if (!existingKnownDevices.includes(pendingDevice)) {
const updatedKnownDevices = [...existingKnownDevices, pendingDevice];
// Update the user's trusted metadata and clean up pending device
await stytchClient.users.update({
user_id: session.user_id,
trusted_metadata: {
...user.trusted_metadata,
known_devices: updatedKnownDevices,
pending_device: undefined, // Remove the pending device
},
});
...
}
```
## What's next?
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Get started with the API
Source: https://stytch.com/docs/fraud-risk/get-started/using-dfp-api
Start collecting and analyzing fingerprints using the Device Fingerprinting API.
## Overview
This guide shows you how to set up Device Fingerprinting using the API. By the end, you'll have:
* Added Device Fingerprinting to your application.
* Wired up a JS event to call `GetTelemetryID()`.
* Called the `fingerprint.lookup()` [endpoint](/docs/api-reference/fraud/api/fingerprint-lookup).
* Collected fingerprint data to store.
* Analyzed Stytch's verdict and recommended action.
***
## Prerequisites
* An existing Stytch B2B or Consumer project in the [Dashboard](https://www.stytch.com/dashboard).
* The `project_id`, `public_token`, and `secret` for your project's Test environment. These can be found under your project's *Project ID & API keys* section in the [Dashboard](https://www.stytch.com/dashboard).
* Device Fingerprinting [enabled for your project](https://stytch.com/dashboard/dfp-rules).
* If you don't have Device Fingerprinting enabled, you can [request access here](https://offers.stytch.com/dfp-30-day-trial?utm_source=stytch_docs\&utm_medium=direct\&utm_content=dfp_30_day_trial).
Estimated time required: **1 hour**
## Get started
1. To start, add the following script tag into the `HEAD` or `BODY` of your app's HTML file:
```html theme={null}
```
2. Once the HTML page is live in your app, every client that loads the page will fetch a copy of the Device Fingerprinting bundle.
1. Call `GetTelemetryID()` with your project's `public_token`when the client triggers a particular event or accesses a protected resource.
2. The function will return a promise that resolves to a Telemetry ID once the fingerprinting step is complete.
3. Once you've extracted the Telemetry ID, send it to your backend or middleware as an X-header or URL paramater.
For this guide, we'll wire up an `onClick()` event handler to a login endpoint:
```JavaScript theme={null}
```
1. Extract the `telemetry_id` from your backend, middleware, or wherever you sent the request from Step 2.
2. Call the `fingerprint.lookup()` [endpoint](/docs/api-reference/fraud/api/fingerprint-lookup) and pass in the `telemetry_id` to pull the data and analyze the fingerprints:
```bash theme={null}
curl --request POST \
--url https://telemetry.stytch.com/v1/fingerprint/lookup \
-u "${PROJECT_ID}:${SECRET}" \
-H 'Content-Type: application/json' \
-d '{
"telemetry_id": "${'${TELEMETRY_ID}'}"
}'
```
This API call requires a basic auth header of `PROJECT_ID` and `SECRET`, which can be found in the [Dashboard](https://stytch.com/dashboard).
3. After a successful lookup call, the response should look like:
```json Response 200 theme={null}
{
"created_at": "2023-01-01T00:00:00Z",
"expires_at": "2033-01-01T00:00:00Z",
"fingerprints": {
"browser_fingerprint": "browser-fingerprint-0b535ab5-ecff-4bc9-b845-48bf90098945",
"browser_id": "browser-id-99cffb93-6378-48a5-aa90-d680232a7979",
"hardware_fingerprint": "hardware-fingerprint-4af7a05d-cf77-4ff7-834f-0622452bb092",
"network_fingerprint": "network-fingerprint-b5060259-40e6-3f29-8215-45ae2da3caa1",
"visitor_fingerprint": "visitor-fingerprint-6ecf5792-1157-41ad-9ad6-052d31160cee",
"visitor_id": "visitor-6139cbcc-4dda-4b1f-b1c0-13c08ec64d72"
},
"status_code": 200,
"telemetry_id": "026ac93b-8cdf-4fcb-bfa6-36a31cfecac1",
"verdict": {
"action": "ALLOW",
"detected_device_type": "...",
"is_authentic_device": true,
"reasons": [...]
}
}
```
4. You can store and log this response to collect your fingerprint data.
In the response, there is a `verdict` object with a recommended `action` for you to take against the client. There are three possible recommended actions:
* `ALLOW`: A known valid device grouping or device profile that is part of the default set of known allowed devices by Stytch. This grouping is made up of mostly verified device profiles that match the fingerprint characteristics of known or authentic origins.
* `BLOCK`: A known bad or malicious device profile that is undesirable and should be blocked from completing the privileged action in question.
* `CHALLENGE`: An unknown or potentially malicious device that should be put through increased friction such as multi-factor authentication or other forms of extended user verification before allowing the privileged action to proceed.
See [Verdicts](/docs/fraud-risk/device-fingerprinting/verdict/overview) to learn more.
Our recommended actions are based on proprietary fingerprinting and device classification processes.
You can see the results of your lookups in the [Dashboard](https://stytch.com/dashboard/activity?tab=dfp-logs):
This provides you with a high-level view of the various fingerprints, verdicts, and verdict reasons your application is encountering.
You can also view the details of individual fingerprints to understand the various devices that are querying your service.
## Next steps
Create rules to customize your decisioning for specific fingerprints.
***
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Get started with Protected Auth
Source: https://stytch.com/docs/fraud-risk/get-started/using-protected-auth
Use Protected Auth to defend your signup and login flows.
## Overview
If you are using Stytch for [Consumer](/docs/consumer-auth/overview) or [Multi-tenant authentication](https://staging.stytch.com/docs/multi-tenant-auth/overview), you can use Protected Auth to ensure that only legitimate users can access your auth flows.
Protected Auth is a frontend SDK feature that uses [Device Fingerprinting](/docs/fraud-risk/device-fingerprinting/overview) to block bots and other suspicious activity at signup and login time.
To use Protected Auth, you must use Stytch for authentication. If you use another authentication provider, you will integrate Device Fingerprinting [using the API](/docs/fraud-risk/get-started/using-dfp-api).
This guide shows you how to set up Protected Auth. By the end, you'll have:
* Installed the frontend SDK.
* Turned on **Observation mode**.
* Turn on **Enforcement mode**.
***
## Prerequisites
* [JavaScript](/docs/api-reference/b2b/frontend-sdks/vanilla-js/overview) frontend SDK or mobile SDK ([React Native](/docs/api-reference/b2b/mobile-sdks/react-native/overview), [iOS](/docs/api-reference/b2b/mobile-sdks/ios-sdk), or [Android](/docs/api-reference/b2b/mobile-sdks/android-sdk)) installed. Minimum versions required:
* Vanilla JS SDK: `v2.2.0`
* React Native SDK: `v0.12.0`
* iOS SDK: `v0.18.0`
* Android SDK: `v0.14.0`
* An existing Stytch B2B or Consumer project in the [Dashboard](https://www.stytch.com/dashboard).
* The `project_id` and `secret` for your project's Test environment. These can be found under your project's *Project ID & API keys* section in the [Dashboard](https://www.stytch.com/dashboard).
* Device Fingerprinting [enabled for your project](https://stytch.com/dashboard/dfp-rules).
* If you don't have Device Fingerprinting enabled, you can [request access here](https://offers.stytch.com/dfp-30-day-trial?utm_source=stytch_docs\&utm_medium=direct\&utm_content=dfp_30_day_trial).
## Get started
Follow the [Quickstart](/docs/get-started/quickstart) to get started with the SDK.
Enable **Protected Auth** under [Frontend SDK settings](https://stytch.com/dashboard/sdk-configuration) in the Dashboard:
By default, the *Connection Type* is set to **Observation mode** when enabling Protected Auth. This mode:
* Automatically generates and submits a fingerprint to the Stytch API.
* The API will **not** enforce any action.
In Observation mode, you can test Protected Auth on real traffic without risk as it will not block any requests and allow you to analyze the results safely.
Results of automatic fingerprint lookups show up as [Device Fingerprinting Logs](https://stytch.com/dashboard/activity?tab=dfp-logs) in the Dashboard.
This will give you a high-level view of the various fingerprints, verdicts, and verdict reasons your application is experiencing. You can view details of individual fingerprints to understand the various devices that are querying your service.
Once you are comfortable with the verdicts for your traffic, you can switch the *Connect Type* to **Enforcement mode**. This enables the Stytch API to react to each lookup triggered by the SDK:
* For `ALLOW` verdicts, continue requests in the normal authentication flow.
* For `BLOCK` verdicts, reject requests and return a `401`.
* For `CHALLENGE` verdicts, the default behavior will continue requests as if they received an `ALLOW` verdicts.
You can configure how Protected Auth [handles challenge verdicts](/docs/fraud-risk/device-fingerprinting/protected-auth#handling-challenge-verdicts) by adjusting the *Challenge handling* setting:
## What's next
Learn more about options for handling challenge verdicts.
***
Find out why Stytch's device intelligence is trusted by Calendly, Replit, and many more.
# Fraud & Risk
Source: https://stytch.com/docs/fraud-risk/overview
Use Stytch Device Fingerprinting to protect your application and its users.
## Device Fingerprinting
Start using the Device Fingerprinting API under an hour.
See an example of how to use Device Fingerprinting to power MFA.
Device Fingerprinting API reference.
### Block advanced programmatic attacks
Check for bots before any sensitive operation and prevent costly attacks against current users. Defend your application against:
* Credential stuffing and brute-force attacks
* Phishing and account takeover (ATO) attacks
* Toll fraud
* Account creation abuse
Protect your resources for real users by stopping fake accounts.
Implement invisible CAPTCHA bot protection for your application.
### Use device information as an additional user identifier
Add or remove friction based on a user's device with highly-unique and stable device identifiers:
* Detect unrecognized devices or trusted devices
* Ban all of a user's account
* Prevent seat sharing or enforce paywalls
Reduce login friction when users return on a known device.
Use Device Fingerprinting to notify users when a new device has logged into their account.
Use IP-based geographic filters for access control.
### Protected Auth
Protected Auth is a ready-made solution in Stytch’s frontend & mobile SDKs that seamlessly integrates with Device Fingerprinting to detect and stop fraud, such as credential stuffing attacks.
Add Protected Auth with no code changes.
Read more about Protected Auth and its configurations.
***
## Learn about the platform
Learn about Stytch's fraud prevention framework.
Learn about the different identifiers provided by Device Fingerprinting.
Attaching additional metadata to fingerprints and verdicts.
Out-of-box decision verdicts for you to take action on.
Extend Device Fingerprinting's decisioning with your own logic.
Enforce your decisioning in your application.
***
## More resources
An integration checklist to help go live smoothly.
Additional guides for developers, such as custom domain configuration and testing your integration.
# Connected Apps Authorization Code Flow
Source: https://stytch.com/docs/connected-apps/authorization-code-flow
Learn more about the authorization code flow for a Connected App.
[OIDC](https://openid.net/developers/how-connect-works/) is an industry standard for allowing applications to ask an Authorization Server—your Stytch-powered application—to verify the identity of a user. By logging in a user in this way we can consolidate information about a user's authentication and authorization in Stytch and manage user data in one centralized location across multiple apps.
To ground our discussion about Connected Apps we will discuss the OIDC Authorization Code Flow (in the Connected App configuration, this is a "confidential app", described below). At the end of the article we will cover modification of this flow for "public apps" - Authorization Code Flow with PKCE.
### Authorization Code Flow
The general flow for the Authorization Code Flow for a Connected App proceeds as follows:
1. A user using the Connected App wishes to authenticate this app with your Stytch-powered Authorization Server in order to access user information (what we're implementing here). They click a button or follow a link to an Authorization URL (configured below) owned by your Stytch app.
2. The Stytch API is called. [**When using the Stytch frontend SDK**](/docs/connected-apps/build-login-flow/login-flow), to begin the login flow, the Connected App will redirect the user's browser to an Authorization URL in your Stytch-powered application. This page will host a mounted Stytch component that will catch the URL parameters provided to the Authorization URL page and proceed with the login flow. After the initial step succeeds, Stytch will respond with a redirect to send the user's browser to the Connected App's `redirect_uri`with a `code` for the Connected App to complete the flow on its backend.
The redirect to this page should include these URL parameters as defined in the OIDC specification:
| Name | Meaning |
| :- | :- |
| `response_type` | The response type of the OIDC request. For Authorization Code Flow this is always `code` |
| `scope` | The scope of access of the user information. Typically one or more of `openid`, `profile`, `email`, `phone` |
| `client_id` | The Stytch client app id, which you will receive when configuring a Client App (below) |
| `redirect_uri` | The URI owned by the Connected App to call back when Stytch verifies access is granted. After the initial steps of the login process, Stytch redirects to this URI. You will configure this in Stytch when configuring a Connected App (below) |
| `state` (optional) | An opaque value (not used by the flow) which will be passed back and forth throughout the flow. This can be used in some enhanced security scenarios or to store information about user state that should be preserved throughout the flow. |
| `code_challenge` | Required for public clients, [see here](/docs/connected-apps/build-custom-flow/public-apps) |
| `code_challenge_method` | Required for public clients, [see here](/docs/connected-apps/build-custom-flow/public-apps) |
3. The user may be prompted to verify that the Connected App has permission to access their data (depending on whether the Connected App is "First Party" or "Third Party). For more information, see our documentation about [Client Types](/docs/connected-apps/oauth-learn-more/client-types).
4. Upon success, the Stytch API returns both a `redirect_uri`, owned by the Connected App, and a `code` parameter. [**When using the frontend SDK**](/docs/connected-apps/build-login-flow/login-flow), Stytch redirects the browser to the configured `redirect_uri` owned by the Connected App with a code parameter.
5. The Connected App uses this code parameter, along with its `client_id` and `client_secret`, to request an `access_token` (and optionally an `id_token` or `refresh_token`) from Stytch.
Once these steps are complete, the Connected App is logged in and can use the `access_token` to access user data.
## What's next
* Learn more about [Client Types](/docs/connected-apps/oauth-learn-more/client-types)
* Learn more about [OAuth Scopes](/docs/connected-apps/oauth-learn-more/oauth-scopes)
* Configure a Connected App with the [SDK](/docs/connected-apps/build-login-flow/login-flow) or [API](/docs/connected-apps/build-custom-flow/getting-started-api).
* Follow [this guide ](/docs/connected-apps/build-custom-flow/public-apps)if you are configuring a public app
# via the Stytch API
Source: https://stytch.com/docs/connected-apps/build-custom-flow/getting-started-api
Examples and considerations when doing a server-side integration with Stytch.
In this guide, you'll learn how to configure your suite of OIDC applications to log in and perform actions on behalf of your users with your Stytch-powered app using our API. If you prefer to use pre-built UI components, see how to build wtih our frontend SDK [here](/docs/connected-apps/build-login-flow/login-flow).
This enables users of your Stytch app to use their in-house tools, external integrations, desktop apps, AI agents, any OIDC-compatible app to use your Stytch application as an Authorization Server, reducing user information duplication and simplifying the process of integrating Connected Apps with your app.
***
To begin the login flow, the Connected App will make a request to an Authorization URL in your Stytch-powered application. This page will need to implement a mechanism which assists carrying out the flow -- it can be something as simple as a backend endpoint which handles the calls, or as complex as a dedicated frontend component. The entry point should parse out some URL parameters to supply to the Stytch OAuth Authorization flow, which you can see [here](/docs/connected-apps/authorization-code-flow).
This page will need to initiate a call to start OAuth authorization, which will return information about which scopes the user should be prompted to consent to. The result of this call should be used to prompt a user for consent. Once the user confirms, the authorization flow can be finalized by calling the submit OAuth authorize endpoint.
Upon a successful submission Stytch will respond with a `redirect_uri` and a `code` for the Connected App to complete the flow on its backend.
For this flow to succeed, the user must be identified, provided via either the `member_id` + `organization_id`, `session_jwt`, or `session_id`. Prior to executing the authorization call, you'll want to check for a valid session and redirect to the login page with a `return_to` parameter if the user is not authenticated.
## **Prerequisites**
In order to complete this guide, you'll need:
* A Stytch project. If you don't have one already, or would like to create a new one, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select B2B Authentication or Consumer Authentication.
* An application using either the Stytch frontend SDKs or the Stytch backend APIs. If you are using the Stytch SDKs, ensure you are using an up-to-date version, e.g. v13.19.0+ for the Python SDK and v12.31.0+ for the Node SDK.
* A Relying Party app (the Connected App) that wishes to receive user information from your application.
## **Integration steps**
Initiate a call to [start OAuth authorization](/docs/api-reference/b2b/api/connected-apps/consent-management/start-oauth-authorization).
```python B2B Python expandable theme={null}
import os
from flask import Flask, request, make_response
from stytch import B2BClient
app = Flask(__name__)
client = B2BClient(
project_id=os.getenv("STYTCH_PROJECT_ID"),
secret=os.getenv("STYTCH_SECRET"),
)
def perform_authorize(
consent_granted,
client_id,
redirect_uri,
response_type,
scopes,
state,
code_challenge,
code_challenge_method,
session_jwt,
):
return client.idp.oauth.authorize(
consent_granted=consent_granted,
client_id=client_id,
redirect_uri=redirect_uri,
response_type=response_type,
scopes=scopes,
state=state,
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
session_jwt=session_jwt,
)
@app.get("/oauth/authorize")
def oauth_authorize():
response_type = request.args.get("response_type", "code")
scope_str = request.args.get("scope", "")
client_id = request.args.get("client_id")
redirect_uri = request.args.get("redirect_uri")
state = request.args.get("state")
code_challenge = request.args.get("code_challenge")
code_challenge_method = request.args.get("code_challenge_method")
scopes = [s for s in scope_str.split(" ") if s]
session_jwt = request.cookies.get("stytch_session_jwt")
base_params = {
"client_id": client_id,
"redirect_uri": redirect_uri,
"response_type": response_type,
"scopes": scopes,
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": code_challenge_method,
"session_jwt": session_jwt,
}
start_resp = client.idp.oauth.authorize_start(**base_params)
if start_resp.consent_required:
# See Step 2: Render consent screen
return make_response("Consent required", 200)
auth_resp = perform_authorize(consent_granted=True, **base_params)
resp = make_response("", 307)
resp.headers["Location"] = auth_resp.redirect_uri
return resp
if __name__ == "__main__":
app.run(port=3000)
```
```javascript B2B Node expandable theme={null}
const express = require('express');
const cookieParser = require('cookie-parser');
const stytch = require('stytch');
const app = express();
app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));
const client = new stytch.B2BClient({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
async function performAuthorize(params) {
return client.idp.oauth.authorize(params);
}
app.get('/oauth/authorize', async (req, res) => {
try {
const {
response_type = 'code',
scope = '',
client_id,
redirect_uri,
state,
code_challenge,
code_challenge_method,
} = req.query;
const scopes = String(scope).split(' ').filter(Boolean);
const session_jwt = req.cookies?.stytch_session_jwt || undefined;
const baseParams = {
client_id,
redirect_uri,
response_type,
scopes,
state,
code_challenge,
code_challenge_method,
session_jwt,
};
const startResp = await client.idp.oauth.authorizeStart(baseParams);
if (startResp.consent_required) {
// See Step 2: Render consent screen
return res.status(200).send('Consent required');
}
const authResp = await performAuthorize({
consent_granted: true,
...baseParams,
});
res.status(307).set('Location', authResp.redirect_uri).send();
} catch (err) {
console.error(err);
res.status(400).send('Authorization failed');
}
});
app.listen(3000, () => console.log('Server listening on :3000'));
```
```python Consumer Python expandable theme={null}
import os
from urllib.parse import urlencode
from flask import Flask, request, make_response
from stytch import Client
app = Flask(__name__)
client = Client(
project_id=os.getenv("STYTCH_PROJECT_ID"),
secret=os.getenv("STYTCH_SECRET"),
)
def perform_authorize(
consent_granted,
client_id,
redirect_uri,
response_type,
scopes,
state,
code_challenge,
code_challenge_method,
session_jwt,
):
return client.idp.oauth.authorize(
consent_granted=consent_granted,
client_id=client_id,
redirect_uri=redirect_uri,
response_type=response_type,
scopes=scopes,
state=state,
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
session_jwt=session_jwt,
)
@app.get("/oauth/authorize")
def oauth_authorize():
response_type = request.args.get("response_type", "code")
scope_str = request.args.get("scope", "")
client_id = request.args.get("client_id")
redirect_uri = request.args.get("redirect_uri")
state = request.args.get("state")
code_challenge = request.args.get("code_challenge")
code_challenge_method = request.args.get("code_challenge_method")
scopes = [s for s in scope_str.split(" ") if s]
session_jwt = request.cookies.get("stytch_session_jwt")
base_params = {
"client_id": client_id,
"redirect_uri": redirect_uri,
"response_type": response_type,
"scopes": scopes,
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": code_challenge_method,
"session_jwt": session_jwt,
}
start_resp = client.idp.oauth.authorize_start(**base_params)
if start_resp.consent_required:
# See Step 2: Render consent screen
return make_response("Consent required", 200)
auth_resp = perform_authorize(consent_granted=True, **base_params)
resp = make_response("", 307)
resp.headers["Location"] = auth_resp.redirect_uri
return resp
if __name__ == "__main__":
app.run(port=3000)
```
```javascript Consumer Node expandable theme={null}
const express = require('express');
const cookieParser = require('cookie-parser');
const stytch = require('stytch');
const app = express();
app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));
const client = new stytch.Client({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
});
async function performAuthorize(params) {
return client.idp.oauth.authorize(params);
}
app.get('/oauth/authorize', async (req, res) => {
try {
const {
response_type = 'code',
scope = '',
client_id,
redirect_uri,
state,
code_challenge,
code_challenge_method,
} = req.query;
const scopes = String(scope).split(' ').filter(Boolean);
const session_jwt = req.cookies?.stytch_session_jwt || undefined;
const baseParams = {
client_id,
redirect_uri,
response_type,
scopes,
state,
code_challenge,
code_challenge_method,
session_jwt,
};
const startResp = await client.idp.oauth.authorizeStart(baseParams);
if (startResp.consent_required) {
// See Step 2: Render consent screen
return res.status(200).send('Consent required');
}
const authResp = await performAuthorize({
consent_granted: true,
...baseParams,
});
res.status(307).set('Location', authResp.redirect_uri).send();
} catch (err) {
console.error(err);
res.status(400).send('Authorization failed');
}
});
app.listen(3000, () => console.log('Server listening on :3000'));
```
If consent is required, we need to render a page informing what scopes are requested. Otherwise, skip ahead to Step 3.
```python B2B Python expandable theme={null}
def render_consent_html(scopes, hidden):
return f"""
`;
}
```
Once the [authorization request is submitted](/docs/api-reference/b2b/api/connected-apps/consent-management/submit-oauth-authorization), authorization is complete.
```python B2B Python expandable theme={null}
from flask import request, make_response
@app.route("/oauth/authorize/submit", methods=["POST", "GET"])
def oauth_authorize_submit():
response_type = request.values.get("response_type", "code")
scope_str = request.values.get("scope", "")
client_id = request.values.get("client_id")
redirect_uri = request.values.get("redirect_uri")
state = request.values.get("state")
code_challenge = request.values.get("code_challenge")
code_challenge_method = request.values.get("code_challenge_method")
consent_granted = (request.values.get("consent_granted", "false").lower() == "true")
scopes = [s for s in scope_str.split(" ") if s]
session_jwt = request.cookies.get("stytch_session_jwt")
auth_resp = perform_authorize(
consent_granted=consent_granted,
client_id=client_id,
redirect_uri=redirect_uri,
response_type=response_type,
scopes=scopes,
state=state,
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
session_jwt=session_jwt,
)
resp = make_response("", 307)
resp.headers["Location"] = auth_resp.redirect_uri
return resp
```
```javascript B2B Node expandable theme={null}
app.post('/oauth/authorize/submit', async (req, res) => {
try {
const {
response_type = 'code',
scope = '',
client_id,
redirect_uri,
state,
code_challenge,
code_challenge_method,
consent_granted,
} = req.body;
const scopes = String(scope).split(' ').filter(Boolean);
const session_jwt = req.cookies?.stytch_session_jwt || undefined;
const authResp = await performAuthorize({
consent_granted: String(consent_granted).toLowerCase() === 'true',
client_id,
redirect_uri,
response_type,
scopes,
state,
code_challenge,
code_challenge_method,
session_jwt,
});
res.status(307).set('Location', authResp.redirect_uri).send();
} catch (err) {
console.error(err);
res.status(400).send('Authorization failed');
}
});
```
```python Consumer Python expandable theme={null}
from flask import request, make_response
@app.route("/oauth/authorize/submit", methods=["POST", "GET"])
def oauth_authorize_submit():
response_type = request.values.get("response_type", "code")
scope_str = request.values.get("scope", "")
client_id = request.values.get("client_id")
redirect_uri = request.values.get("redirect_uri")
state = request.values.get("state")
code_challenge = request.values.get("code_challenge")
code_challenge_method = request.values.get("code_challenge_method")
consent_granted = (request.values.get("consent_granted", "false").lower() == "true")
scopes = [s for s in scope_str.split(" ") if s]
session_jwt = request.cookies.get("stytch_session_jwt")
auth_resp = perform_authorize(
consent_granted=consent_granted,
client_id=client_id,
redirect_uri=redirect_uri,
response_type=response_type,
scopes=scopes,
state=state,
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
session_jwt=session_jwt,
)
resp = make_response("", 307)
resp.headers["Location"] = auth_resp.redirect_uri
return resp
```
```javascript Consumer Node expandable theme={null}
app.post('/oauth/authorize/submit', async (req, res) => {
try {
const {
response_type = 'code',
scope = '',
client_id,
redirect_uri,
state,
code_challenge,
code_challenge_method,
consent_granted,
} = req.body;
const scopes = String(scope).split(' ').filter(Boolean);
const session_jwt = req.cookies?.stytch_session_jwt || undefined;
const authResp = await performAuthorize({
consent_granted: String(consent_granted).toLowerCase() === 'true',
client_id,
redirect_uri,
response_type,
scopes,
state,
code_challenge,
code_challenge_method,
session_jwt,
});
res.status(307).set('Location', authResp.redirect_uri).send();
} catch (err) {
console.error(err);
res.status(400).send('Authorization failed');
}
});
```
Enter the URL where the component is mounted into the Authorization URL field in the Connected Apps section of the [Stytch Dashboard](https://stytch.com/dashboard/connected-apps).
After Stytch handles verification of the user and the application's ability to access the user's information, Stytch will issue a redirect. To set this up, each Connected App is also configured in the [Connected Apps section](https://stytch.com/dashboard/connected-apps) of the Dashboard.
This redirect is intended to go to a callback URL which should be supplied by the Connected App. The redirect will pass an authorization `code` - a short-lived credential that the Connected App will use to complete the authorization flow.
Create a new Connected App and specify what type of user consent and OIDC flow the Connected App should expect. Stytch supports connecting a few types of client apps which vary based on how much user consent is appropriate and specifics of the OIDC flow.
Continue to configure the Connected App. Make sure to take a note of the “Client ID” and the “Client secret” (for confidential apps); these will be needed by the Connecting App to complete the Authorization Code Flow.
The Connected App will also supply a Redirect URL for receiving the login information from Stytch after the initial login is complete. This should be entered into the “Redirect URLs” field.
At this point, off-the-shelf software should be prepared to authorize as a Connected App with Stytch. If implementing your own Connected App, see [this guide](/docs/connected-apps/build-custom-flow/implementing-your-own) to learn more.
# Implementing your own Connected App
Source: https://stytch.com/docs/connected-apps/build-custom-flow/implementing-your-own
Examples and considerations when implementing your own Connected App.
Generally off-the-shelf software will only need to be configured with the location of the Authorization URL, a Client ID, and a Client Secret for proper setup. Afterwards, the software should be prepared to authorize with Stytch.
If, however, you are implementing your own Connected App, or just curious, it's worth understanding the second part of the Authorization Code Flow—what happens upon the Connected App receiving the redirect.
### Server Side Flow
The server side flow is very similar to Stytch's M2M Authentication (for [B2B](/docs/multi-tenant-auth/authentication/m2m/authenticate-client), for [Consumer](/docs/consumer-auth/authentication/m2m/authenticate-client)) in that we will be exchanging the Connected App's own credentials for an access token. The difference in this case is that we will also exchange the `code` returned from integrating with the [SDK](/docs/connected-apps/build-login-flow/login-flow) or [API](/docs/connected-apps/build-custom-flow/getting-started-api). This `code` represents the ability of the client app to act *on behalf of the user.* Conceptually the code can be thought of as representing permission from the user to enable the app to see their data and act on their behalf.
To perform the token exchange, just as in M2M Authentication, we will make a `POST` request to your project's Token Endpoint. The endpoint is located at `https://${projectDomain}/v1/oauth2/token` (or similar, depending on your Stytch test or production environment or CNAME settings). See [this guide](/docs/connected-apps/resources/custom-domains) to learn more about setting up a custom domain.
The request should contain these parameters:
| Name | Meaning |
| :- | :- |
| `grant_type` | The type of auth grant we're seeking. For Authorization Code Flow this is always `authorization_code` |
| `code` | The `code` received in a parameter of the request sent to the Redirect URL. |
| `client_id` | The Stytch client app id, which you will receive when configuring a Client App (below) |
| `client_secret` | The Stytch Connected App's Client Secret - required for confidential clients |
| `redirect_uri` | While there is no redirect issued from this request `redirect_uri` must be present and identical to the value used in the first part of the flow. |
| `code_verifier` | Required for public clients, see [this guide](/docs/connected-apps/build-custom-flow/public-apps) |
For example, here is what such a request might look like using `curl`:
```bash theme={null}
curl --request POST \
--url https://${projectDomain}/v1/oauth2/token \
-d grant_type="authorization_code" \
-d client_id="$CONNECTED_APP_CLIENT_ID" \
-d client_secret="$CONNECTED_APP_CLIENT_SECRET" \
-d code="$CODE_FROM_REDIRECT_PARAMETERS" \
-d redirect_uri="$REDIRECT_URL"
```
The response from this request will be a JSON payload with the access token for authorizing requests from the Client App.
# Public apps: Authorization Code Flow with PKCE
Source: https://stytch.com/docs/connected-apps/build-custom-flow/public-apps
Examples and considerations when implementing a public app.
A potential vulnerability exists in the OIDC flow due to the nature of the flow requiring two asynchronous requests to complete. A malicious party could intercept the `code` returned in the first step of authorization and attempt to use it to authorize their own app before the legitimate app exchanges it for an `access_token`. In the case of the Authorization Code Flow this is prevented by the fact that the pre-shared Client Secret is not known to the malicious actor so they don't have enough information to do the complete code exchange.
Due to the nature of in-browser single-page apps with no backend, or mobile apps where secrets can be extracted from the app if distributed within the app, there is sometimes no location to safely store the `client_secret`. We call these "Public" apps. For these apps, we implement a variant of the Authorization Code Flow using PKCE (Proof Key for Code Exchange). This flow uses an extension to the Authorization Code Flow to ensure both requests used during the Authorization Code Flow come from the same Connected App.
When beginning the authorization request, the Connected App will construct a `code_verifier` by generating a cryptographically random value. The Connected App will use the `code_verifier` to generate a `code_challenge`. The `code_challenge` is the SHA-256 hash of the `code_verifier`. On the initial request to Stytch—the request made to the page hosting the `` component—the additional URL parameters of `code_challenge` and `code_challenge_method` should be sent:
| Name | Meaning |
| :- | :- |
| `code_challenge` | The SHA-256 hash of the value of the `code_verifier` |
| `code_challenge_method` | The method of hashing the `code_verifier`. For Stytch this should always be `S256` |
Upon receipt of this request, Stytch will save the value of `code_challenge` for later. After the user has granted permission for access, Stytch will return a `code` from this step just as in the regular Authorization Code Flow.
During the code exchange part of the process, the Public app will exchange the `code` for an `access_token` as the server would do in the Authorization Code Flow. However, the Public app has no access to a `client_secret` so cannot send that parameter. The Public app *instead* sends the original `code_verifier` with this request:
| Name | Meaning |
| :- | :- |
| `code_verifier` | The original value used to generate the `code_challenge` |
Stytch will verify that the previously saved `code_challenge` was generated from this `code_verifier` before generating and returning credentials for the app. In this way Stytch can be sure that the app that requested the `code` in the first request is the same app that is requesting the `access_token` in the second request.
# Build an authorization server using Stytch Connected Apps
Source: https://stytch.com/docs/connected-apps/build-login-flow/login-flow
Enable your application to become an OAuth and OIDC Authorization server by leveraging the Stytch frontend SDK.
In this guide, you'll learn how to configure your suite of OIDC applications to log in and perform actions on behalf of your users with your Stytch-powered app using our frontend SDKs. If you prefer to use your own UI components, see how to build a custom flow [here](/docs/connected-apps/build-custom-flow/getting-started-api).
This enables users of your Stytch app to use compatible applications—in-house tools, external integrations, desktop apps, AI agents, or any OIDC-compatible app—to use your Stytch application as an Authorization Server.
## **Prerequisites**
In order to complete this guide, you'll need:
* A Stytch project. If you don't have one already, or would like to create a new one, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select B2B Authentication or Consumer Authentication.
* A React or Next.js app authorized to use Stytch [frontend SDKs](https://stytch.com/dashboard/sdk-configuration).
* A Relying Party app (the Connected App) that wishes to receive user information from your application.
## Integration steps
To use the **Stytch frontend SDK**, we have created a component for use in your app:
* `` for Consumer applications
* `` for B2B applications
When mounted on the page located at the Authorization URL, this component will catch the parameters passed in the URL and initiate the flow with Stytch. After the initial step succeeds, Stytch will respond with a redirect to send the user's browser to the `redirect_uri` with a code for the Connected App to complete the flow on its backend.
```javascript B2B theme={null}
const { member } = useStytchMember();
if (!member) {
const loginURL = `/login?returnTo=${window.location.toString()}`
return
}
return
```
```javascript Consumer theme={null}
const { user } = useStytchUser();
if (!user) {
const loginURL = `/login?returnTo=${window.location.toString()}`
return
}
return
```
For this flow to succeed, the user must already be logged in to your Stytch-powered app and have an active session, so you'll want to check for a valid session prior to rendering the component and if the user is not logged in, redirect to the login page with a `return_to` parameter. Learn more about the Authorization Code Flow [here](/docs/connected-apps/authorization-code-flow).
Enter the URL where the component is mounted into the Authorization URL field in the Connected Apps section of the [Stytch Dashboard](https://stytch.com/dashboard/connected-apps).
After Stytch handles verification of the user and the application's ability to access the user's information, Stytch will issue a redirect. To set this up, each Connected App is also configured in the [Connected Apps section](https://stytch.com/dashboard/connected-apps) of the Dashboard.
This redirect is intended to go to a callback URL which should be supplied by the Connected App. The redirect will pass an authorization `code` - a short-lived credential that the Connected App will use to complete the authorization flow.
Create a new Connected App and specify what type of user consent and OIDC flow the Connected App should expect. Stytch supports connecting a few types of client apps which vary based on how much user consent is appropriate and specifics of the OIDC flow.
Continue to configure the Connected App. Make sure to take a note of the “Client ID” and the “Client secret” (for confidential apps); these will be needed by the Connecting App to complete the Authorization Code Flow.
The Connected App will also supply a Redirect URL for receiving the login information from Stytch after the initial login is complete. This should be entered into the “Redirect URLs” field.
At this point, off-the-shelf software should be prepared to authorize as a Connected App with Stytch. If implementing your own Connected App, see [this guide](/docs/connected-apps/build-custom-flow/implementing-your-own) to learn more.
## **What's next**
Read more about our other offerings:
* [Implementing your own Connected App](/docs/connected-apps/build-custom-flow/implementing-your-own)
* [Public Apps](/docs/connected-apps/build-custom-flow/public-apps): Authorization Code Flow with PKCE
* Log users in with other commonly used IdPs such as Google and Microsoft ActiveDirectory with OAuth [multi-tenant](/docs/multi-tenant-auth/authentication/oauth/overview) and [consumer](/docs/consumer-auth/authentication/oauth/overview) applications
* Learn more about Machine-to-Machine authentication with M2M for [multi-tenant](/docs/multi-tenant-auth/authentication/m2m/authenticate-client) and [consumer](/docs/consumer-auth/authentication/m2m/authenticate-client) applications
* Configure a [custom domain](/docs/connected-apps/resources/custom-domains) for your Connected Apps
* Add custom claims to your Connected Apps access tokens using a Custom Claim Template for [multi-tenant](/docs/multi-tenant-auth/manage-sessions/custom-claims) and [consumer](/docs/consumer-auth/manage-sessions/custom-claims) applications
# Integrate with CLI agents
Source: https://stytch.com/docs/connected-apps/guides/cli-agents
Create a CLI app with Stytch Authentication.
In this guide, we'll walk through the creation of a simple Command Line Interface application which utilizes Stytch Connected Apps for authentication via an existing web application.
This guide will reference two example apps:
* [stytch-react-example](https://github.com/stytchauth/stytch-react-example) - used as the base web app that will authorize the CLI app.
* [stytch-connected-apps-cli-example](https://github.com/stytchauth/stytch-connected-apps-cli-example) - a basic CLI app made with Cobra.
Feel free to pull down both of these example repositories to follow along and run through this guide!
Users will initiate authorization from within the CLI, redirect to the web app, authorize the CLI app from within the web app, and then be redirected back to the CLI where a Connected Apps `access_token` will be minted.
## Pre-requisites
In order to complete this guide, you'll need:
* A Stytch project. If you don't have one already, or would like to create a new one, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click Create Project, and then select Consumer Authentication.
* A web app that uses Stytch for authentication (this guide will use [stytch-react-example](https://github.com/stytchauth/stytch-react-example)).
* A basic CLI app (this guide will use [stytch-connected-apps-cli-example](https://github.com/stytchauth/stytch-connected-apps-cli-example)).
## Create a CLI app
Navigate to the Connected Apps section of your Stytch Dashboard, and create a new Connected App.
Configure the app as First Party Public.
Add `http://127.0.0.1/callback` as a login redirect URL in the Connected App configuration - this is the URL our CLI app will use to receive the authorization `code`.
Omitting a port in the redirect URL permits any port to be used, allowing the CLI application to use dynamic port allocation. We want to dynamically allocate a free port, as opposed to picking a static one, as we don't know what conflicting processes may be running on our end users device.
Set the authorization URL in Connected Apps page to `http://localhost:3000/oauth/authorize` - this is the URL that our web app will expose to mount the ` `component for authorization.
Add a route in your web app that mounts the `` component.
In [stytch-react-example](https://github.com/stytchauth/stytch-react-example), we do this [here](https://github.com/stytchauth/stytch-react-example/blob/main/src/components/Authorize.tsx):
```javascript theme={null}
import { IdentityProvider, useStytchUser } from '@stytch/react';
import { useEffect } from 'react';
const Authorize = () => {
const { user } = useStytchUser();
useEffect(() => {
if (!user) {
window.location.href = '/';
}
}, [user]);
return ;
};
export default Authorize;
```
To initiate the authorization flow, configure your CLI app to navigate to your web app's page that mounts ``.
The list of URL parameters required for this request can be found [here](/docs/connected-apps/build-login-flow/login-flow).
* If you'd like to receive a `refresh_token`, allowing your CLI app to refresh the `access_token` without the user re-authorizing, make sure your request includes `scope=offline_access`.
* Because this is a public Connected App, we need to use PKCE rather than a `client_secret`, as there is nowhere to securely store a `client_secret`. The PKCE `code_challenge` and `code_challenge_method` will be sent along as part of the initiating request.
In [stytch-connected-apps-cli-example](https://github.com/stytchauth/stytch-connected-apps-cli-example), we do this in `cmd/auth.go`.
Relevant snippets below:
```go theme={null}
// Get a free port for the callback server
port := utils.GetOpenPort()
redirectURI := fmt.Sprintf("http://127.0.0.1:%d/callback", port)
// Generate PKCE values
codeVerifier, err := generateCodeVerifier()
if err != nil {
fmt.Printf("Error generating code verifier: %v\n", err)
return
}
codeChallenge := generateCodeChallenge(codeVerifier)
// Construct the auth URL with PKCE parameters
params := url.Values{}
params.Add("client_id", clientID)
params.Add("redirect_uri", redirectURI)
params.Add("response_type", "code")
params.Add("code_challenge", codeChallenge)
params.Add("code_challenge_method", "S256")
params.Add("scope", "offline_access")
authURL := fmt.Sprintf("%s?%s", authorizeURL, params.Encode())
fmt.Println("Opening browser for authentication...")
// Open the browser with the auth URL
err = openBrowser(authURL)
if err != nil {
fmt.Println("Please open the following URL in your browser:", authURL)
}
```
Next, set up a callback handler in your CLI app that authenticates the authorization `code` received back from `` via the [Get Access Token endpoint](/docs/api-reference/consumer/api/m2m/token/get-access-token).
Note that we need to include `code_verifier` in this call since this is a Public app.
In [stytch-connected-apps-cli-example](https://github.com/stytchauth/stytch-connected-apps-cli-example), we do this in `cmd/auth.go`:
```go theme={null}
// Channel to receive the auth code
codeChan := make(chan string)
errorChan := make(chan error)
// Set up the callback handler
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
if code == "" {
errorChan <- fmt.Errorf("no code received in callback")
return
}
// Send success response to browser
w.Write([]byte("Authentication successful! You can close this window."))
codeChan <- code
})
// Start the server in a goroutine
go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
errorChan <- err
}
}()
// Wait for either the code or an error
var code string
select {
case code = <-codeChan:
fmt.Println("Received authorization code")
case err := <-errorChan:
fmt.Printf("Error: %v\n", err)
return
case <-time.After(5 * time.Minute):
fmt.Println("Timeout waiting for authentication")
return
}
// Exchange the code for a token
token, err := exchangeCodeForToken(code, codeVerifier)
// Save the tokens to keyring
utils.SaveToken(token.AccessToken, utils.AccessToken)
utils.SaveToken(token.RefreshToken, utils.RefreshToken)
// Shutdown the server
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.Shutdown(ctx)
```
`exchangeCodeForToken()` in the above snippet is implemented [here](https://github.com/stytchauth/stytch-connected-apps-cli-example/blob/main/cmd/auth.go#L146), and calls the [Get Access Token endpoint](/docs/api-reference/consumer/api/m2m/token/get-access-token) with:
1. The `code` retrieved by the channel set up,
2. The `code_verifier` generated at the start of the authorization flow
3. The Connected App's `client_id`
4. `grant_type`: “authorization\_code” because this is an initial authorization flow and not a refresh flow.
The access and refresh tokens returned should be stored securely, where other CLI applications or tools cannot access them. The example above uses a helper called `utils.saveToken`, which utilizes go keyring.
```go theme={null}
// SaveToken persists the token securely
func SaveToken(tok string, typ TokenType) error {
return keyring.Set(service, user+string(typ), tok)
}
```
To test the flow with the example apps above:
1. Set up and run stytch-react-example locally, following the instructions in the README.
2. Authenticate into the example app, so that you have an active Session.
3. Run [stytch-connected-apps-cli-example](https://github.com/stytchauth/stytch-connected-apps-cli-example), following the instructions in the README.
4. You should be automatically redirected to the authorization flow in your browser, redirect back to your CLI, and see a Connected Apps `access_token`!
Now that you're authenticated with a Connected Apps `access_token` in your CLI app, your backend can authenticate API requests by verifying this token.
For example, if your CLI app calls `GET https://yourapp.com/api/resources` to take some action in your app/platform, attaching the `access_token` as a Bearer token, your backend can perform authentication via something like the following:
```go theme={null}
func resourcesHandler(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
token := strings.TrimPrefix(auth, "Bearer ")
resp, err := stytchClient.IDP.IntrospectTokenLocal(r.Context(), &idp.IntrospectTokenLocalParams{
Token: token,
})
if err != nil || !resp.TokenActive {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Token is valid — respond with protected data
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "Authenticated!"}`))
}
```
# Cross App Access (XAA) Flow
Source: https://stytch.com/docs/connected-apps/guides/cross-app-access
Exchange a JWT from an external workforce identity provider for a Stytch Connected Apps access token without browser interaction.
The Cross App Access Flow is only applicable for multi-tenant auth uses of Connected Apps.
The Cross App Access (XAA) Flow allows a Connected App to exchange a JWT issued by an external workforce identity provider (IdP) for a Stytch access token. This enables service-to-service and agent-to-service flows where the user has already authenticated with their organization's IdP (e.g. Okta, Microsoft Entra, Google Workspace) and needs to access resources protected by Stytch Connected Apps — without a browser-based redirect.
This flow uses the `urn:ietf:params:oauth:grant-type:jwt-bearer` grant type and implements the [Identity Assertion Authorization Grant (ID-JAG)](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/) draft specification.
## When to use the XAA Flow
Use the Cross App Access Flow when:
* Your application needs to act on behalf of users who have already authenticated with a workforce IdP
* You want to avoid browser redirects (e.g. in CLI tools, background services, or AI agents)
* Your users sign in via OIDC SSO and you want Connected Apps to recognize them without a separate authorization code flow
## Prerequisites
* A B2B Stytch project with [Connected Apps](/docs/connected-apps/overview) enabled
* An [OIDC SSO connection](/docs/multi-tenant-auth/authentication/sso/overview) configured for the organization, pointing to the external IdP
* A **Confidential** Connected App client (first-party or third-party)
## How it works
The user authenticates with their organization's workforce identity provider (e.g. Okta, Microsoft Entra) via OpenID Connect. After authentication, the Connected App receives an ID token representing the user's session with the IdP.
The Connected App makes a [Token Exchange](https://datatracker.ietf.org/doc/html/rfc8693) request to the workforce IdP's token endpoint to obtain an ID-JAG token:
```bash theme={null}
curl -X POST https://your-idp.example.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "requested_token_type=urn:ietf:params:oauth:token-type:id-jag" \
-d "subject_token=" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \
-d "audience=" \
-d "scope=openid email profile"
```
The IdP validates the subject token, evaluates any administrator-defined policies, and issues a signed ID-JAG token with `typ: "oauth-id-jag+jwt"` in the header.
The Connected App sends the ID-JAG token to Stytch's [token endpoint](/docs/api-reference/b2b/api/connected-apps/exchange-id-jag) using the XAA Flow's grant type:
```bash theme={null}
curl -X POST https://.customers.stytch.dev/v1/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=" \
-d "client_id=" \
-d "client_secret=" \
-d "scope=openid email profile"
```
Stytch validates the ID-JAG token by:
1. Matching the token's `iss` claim to a configured OIDC connection
2. Verifying the token signature against the OIDC connection's JWKS endpoint
3. Validating that `aud` contains the Stytch project's issuer URL
4. Confirming the `client_id` claim matches the requesting Connected App
5. Resolving the token's `sub` claim to a member (see below)
If all checks pass, Stytch issues a Connected Apps access token scoped to the resolved member.
## Member resolution
Stytch resolves the ID-JAG token's `sub` claim to a member using two mechanisms, checked in this order:
1. **OIDC registration match**: Stytch looks for an active OIDC member registration on the same connection where the `provider_subject` matches the token's `sub`. This is automatically created when a member signs in via OIDC SSO — no additional configuration is needed.
2. **External ID match**: If no OIDC registration is found, Stytch checks for a member in the organization whose `external_id` matches the token's `sub`.
If your members sign in via OIDC SSO before using the XAA Flow, they will be resolved automatically through their OIDC registration. No manual `external_id` mapping is required.
## ID-JAG token format
The ID-JAG token must be a signed JWT with the following structure:
**Header:**
```json theme={null}
{
"alg": "RS256",
"typ": "oauth-id-jag+jwt",
"kid": ""
}
```
**Claims:**
| Claim | Description |
| - | - |
| `iss` | The IdP's issuer URL (must match the OIDC connection's issuer) |
| `sub` | The user's identifier at the IdP |
| `aud` | Must include your Stytch project's issuer URL |
| `client_id` | The Connected App's client ID |
| `scope` | Requested scopes (space-delimited) |
| `exp` | Expiration time |
| `iat` | Issued-at time |
| `jti` | Unique token identifier |
## Token exchange response
A successful exchange returns a standard OAuth 2.0 token response:
```json theme={null}
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "openid email profile"
}
```
# Integrate with AI agents
Source: https://stytch.com/docs/connected-apps/guides/integrate-with-ai-agents
Enable AI agents to securely connect to your application.
In this guide, you'll learn how to turn your application into an OAuth 2.0/OIDC provider to enable user-permissioned access to application via 3rd parties such as AI Agents. To illustrate how to do this, we'll create a custom GPT that connects to your application via OAuth. This works for any 3rd party that allows integrations via OAuth or OIDC.
## **Prerequisites**
In order to complete this guide, you'll need:
* A Stytch project. If you don't have one already, or would like to create a new one, in the [Dashboard](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select B2B Authentication or Consumer Authentication.
* A React, Next.js, or other JavaScript based app that uses the Stytch [B2B Frontend SDK](/docs/api-reference/b2b/api/sdks) or [Consumer Frontend SDK](/docs/api-reference/consumer/api/sdks). This will need to be available via a public URL to connect to ChatGPT. If you need something to get started, check out our [example apps](/docs/get-started/quickstart).
* A ChatGPT account with a Plus subscription, so that you can create a custom GPT.
## Integration steps
To turn your app into an identity provider, we have created a component for use in your app:
* `` when using Consumer applications
* `` when using B2B applications
When mounted on the page located at the Authorization URL, this component will catch the parameters passed in the URL and initiate the flow with Stytch. After the initial step succeeds, Stytch will respond with a redirect to send the user's browser to the `redirect_uri` with a code for the Connected App to complete the flow on its backend.
```javascript B2B theme={null}
const { member } = useStytchMember();
if (!member) {
const loginURL = `/login?returnTo=${window.location.toString()}`
return
}
return
```
```javascript Consumer theme={null}
const { user } = useStytchUser();
if (!user) {
const loginURL = `/login?returnTo=${window.location.toString()}`
return
}
return
```
For this flow to succeed, the user must already be logged in to your Stytch-powered app and have an active session, so you'll want to check for a valid session prior to rendering the component and if the user is not logged in, redirect to the login page with a `return_to` parameter. Learn more about the Authorization Code Flow [here](/docs/connected-apps/authorization-code-flow).
Enter the URL where the component is mounted into the Authorization URL field in the Connected Apps section of the Stytch Dashboard. For this example, we'll use `/idp`.
Your app is now an OAuth 2.0/OIDC compliant identity provider!
Next, you'll need to deploy your updates to a publicly available URL. After Stytch handles verification of the user and the application's ability to access the user's information, Stytch will issue a redirect. This redirect is intended to go to a URL which should be supplied by the Connected App and provides some short-lived credentials that allow the Connected App to complete the authorization flow.
To set this up, each Connected App is also configured in the Connected Apps section of the Dashboard.
Create a new Connected App and specify what type of user consent and OIDC flow the Connected App should expect. For this use case, we'll create a **Third Party Confidential** app, so choose that option under **Client type**.
Continue to configure the Connected App on the next screen. Make sure to take a note of the "**Client ID**" and the "**Client secret**"; these will be needed by the Connecting App, in this case ChatGPT, to complete the Authorization Code Flow.
Go to [ChatGPT custom GPTs](https://chatgpt.com/gpts) and click *create*. Scroll to the bottom and under Actions, select *create new Action*. Then, under authentication configure OAuth. You'll want to specify the following:
* Client ID: the Client ID you just created
* Client Secret: the Client Secret you just created
* Authorization URL: this the URL where the IdP component is mounted, for example `example.com/idp`
* Token URL: this is the URL you created in the previous step from the Stytch Dashboard
* Scope: for this use case, specify: `openid profile email`
Once you've configured and saved the OAuth credentials, you'll get a Callback URL. Go back to the Stytch Dashboard and enter it under "Redirect URLs" for the configured Connected App.
You're ready to go with your app connected to ChatGPT!
Finally, you'll want to add an OpenAPI spec to your custom GPT so that it knows what endpoints it can call using the Access Token from the OAuth connection. You can see [an example](https://github.com/stytchauth/stytch-connected-apps-b2b-demo/blob/main/apiSpec.yaml).
## **What's next**
Learn more:
* Explore [a B2B example app](https://github.com/stytchauth/stytch-connected-apps-b2b-demo) showing this use case.
* Checkout [a hosted version of this B2B example](https://www.stytchb2bdemo.com/) and the corresponding [custom GPT](https://chatgpt.com/g/g-67aead5398348191bfe4e5bd384f7d62-stytch-b2b-oauth-demo).
# Add Stytch Connected Apps to an existing authentication system
Source: https://stytch.com/docs/connected-apps/guides/integrate-with-existing-system
Make your application an OIDC/OAuth2.1 provider without migrating your entire authentication system.
You can use Connected Apps to make your application an OIDC/OAuth2.1 provider without migrating your entire authentication system to the Stytch platform. Any authentication system that issues a JWT `access_token` or `id_token` with an `email` claim can be used here with the help of [Trusted Auth Tokens](/docs/multi-tenant-auth/authentication/trusted-auth-tokens).
When used in this manner, Stytch will be responsible for all OAuth client management, user consent, and token issuance. Your existing authentication system will remain in control of the user's login experience and account information.
## Demo Apps
Follow along with our demo apps to learn how Stytch can be used to add MCP-compliant OAuth flows to an existing identity provider that doesn't support OAuth.
Check out our [Firebase Demo App](https://github.com/stytchauth/stytch-all-examples/tree/main/advanced_auth/connected_apps/b2b-standalone/firebase-express-access-management) for multi-tenant auth.
For consumer authentication, check out our [Supabase Demo App](https://github.com/stytchauth/stytch-all-examples/tree/main/advanced_auth/connected_apps/consumer-standalone/supabase-nextjs-user-management).
## **Integrating with an Existing Auth System**
Learn how to add Stytch Connected Apps to any Auth system, including Supabase, Firebase, AWS Cognito, or Auth0.
Create a [Trusted Auth Token Profile](/docs/multi-tenant-auth/authentication/trusted-auth-tokens) to configure Stytch to accept JWTs issued by your existing Identity Provider. The Trusted Auth Token Profile tells Stytch how to validate these JWTs, and how to map claims like the user's role information to constructs in the Stytch platform.
Keep track of the `profile_id` created from this step.
Install the Javascript SDK to access the Stytch UI component used to handle the OAuth consent flow. The OAuthAuthorize component shown below wraps either the [B2BIdentityProvider](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/identity-provider/configuration) React component for multi-tenant auth or the [IdentityProvider](/docs/api-reference/consumer/frontend-sdks/react/prebuilt-ui/identity-provider/configuration) React component for consumer auth. This component will take in a JWT issued by your existing authentication system matching the profile you created above.
The styling and copy of the IdentityProvider UI component can be customized to match your existing application. See available options in the [B2BIdentityProvider](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/identity-provider/configuration) or [IdentityProvider](/docs/api-reference/consumer/frontend-sdks/react/prebuilt-ui/identity-provider/configuration) API reference.
```javascript B2B theme={null}
import { useMemo } from 'react';
import { createStytchClient, StytchB2BProvider, B2BIdentityProvider } from '@stytch/react/b2b';
const stytch = createStytchClient('PUBLIC_TOKEN');
// The Trusted Auth Token Profile ID created in Step 1
const tokenProfileID = "trusted-token-profile-...";
export const OAuthAuthorizeComponent = ({ trustedAuthToken }) => {
const tokenParams = useMemo(() => ({ trustedAuthToken, tokenProfileID }),
[trustedAuthToken]);
return (
)
}
```
```javascript Consumer theme={null}
import { useMemo } from 'react';
import { createStytchClient } from '@stytch/vanilla-js';
import { StytchProvider, IdentityProvider } from '@stytch/react';
const stytch = createStytchClient('public-token-test-61a44e32-7950-4b43-80a3-e1e493dbf321');
// The Trusted Auth Token Profile ID created in Step 1
const tokenProfileID = "trusted-token-profile-...";
export const OAuthAuthorizeComponent = ({ trustedAuthToken }) => {
const tokenParams = useMemo(() => ({ trustedAuthToken, tokenProfileID }),
[trustedAuthToken]);
return (
)
}
```
The OAuth Authorization page takes in numerous arguments from OAuth clients in the form of query parameters. If the user is not logged in, your application should redirect them to your existing login implementation. After login, your application should redirect the user back to the *exact same URL* - preserving the OAuth query parameters for Stytch to handle.
Your existing auth and routing libraries likely have first-class functionality to do this already - consult their documentation for details.
```javascript theme={null}
// If the user is unauthenticated, store the full location to return to
// query parameters and all
export const redirectToLogin = (returnState) => {
localStorage.setItem('returnTo', returnState);
window.location.href = '/login'
}
// After the user has authenticated, redirect them back
// into the previous OAuth flow
const onLoginComplete = () => {
const returnTo = localStorage.getItem('returnTo')
if (returnTo) {
localStorage.deleteItem('returnTo');
window.location.href = returnTo;
} else {
window.location.href = '/home';
}
}
```
Define a new page in your application for handling OAuth Authorization. Most commonly, this page lives at `${yourDomain}/oauth/authorize`. OAuth clients will redirect the user to this page to kick off an OAuth login flow.
This page should retrieve the JWT from your existing authentication system and pass it in to the `OAuthAuthorizeComponent` we defined earlier.
```javascript Supabase theme={null}
import { useState, useEffect } from 'react';
import { createClient } from '@supabase/supabase-js';
import { OAuthAuthorizeComponent } from './components';
const supabase = createClient(
'https://.supabase.co',
'',
);
export default function OAuthAuthorizePage() {
const [session, setSession] = useState(null);
useEffect(() => {
supabase.auth.getSession().
then(({ data: { session } }) => {
if(!session) {
// Redirect to login page
return redirectToLogin(window.location.href)
}
setSession(session);
});
}, []);
return session
?
: null;
}
```
```javascript Firebase theme={null}
import { useState, useEffect } from 'react';
import { getAuth } from "firebase/auth";
import { OAuthAuthorizeComponent } from './components';
const auth = getAuth();
export default function OAuthAuthorizePage() {
const [token, setToken] = useState(null)
useEffect(() => {
if (!auth.currentUser) {
// Redirect to login page
return redirectToLogin(window.location.href)
}
auth.currentUser.getIdToken().
then((token) => setToken(session));
}, []);
return session
?
: null;
}
```
```javascript AWS Amplify (Cognito) theme={null}
import { useState, useEffect } from 'react';
import { fetchAuthSession } from 'aws-amplify/auth';
import { OAuthAuthorizeComponent } from './components';
export default function OAuthAuthorizePage() {
const [token, setToken] = useState(null);
useEffect(() => {
fetchAuthSession()
.then((session) => setToken(session.tokens.idToken))
// Redirect to your login page if the user is not logged in
.catch(err => redirectToLogin(window.location.href));
}, []);
return session
?
: null;
}
```
```javascript Auth0 theme={null}
import { useState, useEffect } from 'react';
import { Auth0Client } from '@auth0/auth0-spa-js';
import { OAuthAuthorizeComponent } from './components';
const auth0 = new Auth0Client({
domain: '{yourDomain}',
clientId: '{yourClientId}'
});
export default function OAuthAuthorizePage() {
const [token, setToken] = useState(null)
useEffect(() => {
auth0.getIdTokenClaims()
then((claims) => setToken(claims.__raw))
// Redirect to your login page if the user is not logged in
.catch(err => redirectToLogin(window.location.href))
}, []);
return session
?
: null;
}
```
In the Connected Apps section of the [Stytch Dashboard](https://stytch.com/dashboard/connected-apps), set the Authorization URL to the URL of the page where OAuthAuthorizeComponent is mounted. This will allow Stytch to broadcast the URL to clients using your project's [OpenID Connect Endpoint](/docs/api-reference/b2b/api/connected-apps/configuration/get-openid-configuration).
After completion of an OAuth flow, the OAuth client will be granted a Stytch-issued Access Token (for [B2B](/docs/api-reference/b2b/api/connected-apps/tokens/connected-app-access-token-object), for [Consumer](/docs/api-reference/consumer/api/connected-apps/tokens/connected-app-access-token-object)), and optionally an ID Token (for [B2B](/docs/api-reference/b2b/api/connected-apps/tokens/connected-app-id-token-object), for [Consumer](/docs/api-reference/consumer/api/connected-apps/tokens/connected-app-id-token-object)). The OAuth client can now use this access token to invoke API endpoints hosted by your application. Use the Stytch Backend SDKs - or any JWT-compatible library - to validate the access token within your existing codebase. Refer to the Authenticate Access Token API docs (for [B2B](/docs/api-reference/b2b/api/connected-apps/methods/authenticate-access-token-local), for [Consumer](/docs/api-reference/consumer/api/connected-apps/methods/authenticate-access-token-local)) for a complete reference.
```python B2B Python theme={null}
from stytch import B2BClient
client = B2BClient(
project_id="PROJECT_ID",
secret="SECRET",
custom_base_url="${projectDomain}",
)
resp = client.idp.introspect_access_token_local(
access_token="eyJ...",
)
```
```javascript B2B Node theme={null}
import * as stytch from 'stytch';
const client = new stytch.B2BClient({
project_id: 'PROJECT_ID',
secret: 'SECRET',
custom_base_url: '${projectDomain}',
});
client.idp.introspectTokenLocal({ token: 'eyJ...' })
.then((resp) => {
console.log(resp);
})
.catch((err) => {
console.log(err);
});
```
```python Consumer Python theme={null}
from stytch import Client
client = Client(
project_id="PROJECT_ID",
secret="SECRET",
custom_base_url="${projectDomain}",
)
resp = client.idp.introspect_access_token_local(
access_token="eyJ...",
)
```
```javascript Consumer Node theme={null}
import * as stytch from 'stytch';
const client = new stytch.Client({
project_id: 'PROJECT_ID',
secret: 'SECRET',
custom_base_url: '${projectDomain}',
});
client.idp.introspectTokenLocal({ token: 'eyJ...' })
.then((resp) => {
console.log(resp);
})
.catch((err) => {
console.log(err);
});
```
## What's next
Your application is now an OAuth 2.1 / OIDC Compliant identity provider!
* Read about OAuth for MCP for [B2B](/docs/connected-apps/guides/remote-mcp-servers) and for [Consumer](/docs/connected-apps/guides/mcp-servers-cloudflare)
* Learn more about [OAuth Scopes](/docs/connected-apps/oauth-learn-more/oauth-scopes) in the Stytch platform
# MCP authorization overview
Source: https://stytch.com/docs/connected-apps/guides/mcp-auth-overview
Using Stytch for Remote MCP Server authorization
## The Model Context Protocol
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is a specification for how AI agents can communicate with, and discover the capabilities of, servers which implement specific functionality. Whereas an LLM may not natively have the capability to perform certain actions, such as sending email, if it can communicate with an MCP server that advertises this capability it is empowered to delegate the implementation of email sending to that server. Naturally, this type of interaction can extend beyond email to whatever services implementers wish to create.
For the purposes of implementing authorization with Stytch, MCP Clients are a type of Connected App. MCP Clients request access to a member's account through the `authorization_code` grant.
In this guide, we'll walk through the creation of a Remote (MCP) server which manages authorization using Stytch. We'll discuss all the steps that go into a complete MCP Authorization flow. These steps are performed between a variety of actors - some are performed by the MCP Client, some by your MCP Server, and some by Stytch directly. At each step, we'll call out which actor is responsible for what. For steps that need to be implemented by you, we'll show code snippets in a variety of languages.
To illustrate, we'll follow along with the [Stytch MCP Server](https://mcp.stytch.dev/), which uses Stytch under the hood.
This guide is for implementing MCP Authorization with the Stytch product. For a more general purpose guide on the role of OAuth within the MCP ecosystem, check out our [Blog](https://stytch.com/blog/MCP-authentication-and-authorization-guide/).
## **Pre-requisites**
At a high level, your MCP Server is responsible for the following:
1. Validating Stytch-issued access tokens and returning a specific error format on failure
2. Serving a JSON document instructing MCP Clients to talk to Stytch
In addition, your application is responsible for hosting one of the following Stytch components to handle the OAuth Consent step:
* `` React Component for Consumer applications
* `` React Component for B2B applications
If you are adding MCP to an existing project, this should be added to your main frontend codebase. If you are building a standalone MCP Server, this can be added to the MCP Server directly.
This guide configures the Stytch SDK to use your Project's default domain. We strongly recommend [configuring a custom domain](/docs/connected-apps/resources/custom-domains#using-a-custom-auth-domain) to ensure maximum compatibility and security for your integrations.
## **The Complete MCP Authorization Flow**
This step is performed by your MCP Server.
When an MCP client first tries to connect to your MCP server, the client will send an [Initialization](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization) request.
Your MCP server should validate that the request contains a valid Stytch-issued access token JWT.
If the request has no access token, or an invalid or expired access token, your MCP server must respond with a `401 Unauthorized` status code, and a specially formatted [WWW-Authenticate](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/WWW-Authenticate) header. This header will direct the client to look up the Protected Resource Metadata (PRM) document as detailed in [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728#name-use-of-www-authenticate-for).
Let's try with the [Stytch MCP Server](https://mcp.stytch.dev/), which uses Stytch under the hood.
```bash theme={null}
# The MCP Client initializes a connection without any credentials
curl -D - "https://mcp.stytch.dev/mcp"
```
The response contains a `WWW-Authenticate` header with a `resource_metadata` attribute.
```bash theme={null}
HTTP/2 401
www-authenticate: Bearer error="Unauthorized", error_description="Unauthorized",
resource_metadata="https://mcp.stytch.dev/.well-known/oauth-protected-resource"
Unauthorized
```
Here are some examples of how to implement this check in a variety of languages and runtimes:
Node projects should use the [Stytch](https://www.npmjs.com/package/stytch) backend SDK to perform token validation using the [Introspect Access Token](/docs/api-reference/b2b/api/connected-apps/methods/introspect-token) API method.
```javascript B2B expandable theme={null}
import * as stytch from "stytch";
import express from "express";
const app = express();
const client = new stytch.B2BClient({
project_id: "PROJECT_ID",
secret: "SECRET",
custom_base_url: '${projectDomain}',
})
const authorizeTokenMiddleware = async (req, res, next) => {
const wwwAuthValue = `Bearer error="Unauthorized", ` +
`error_description="Unauthorized",` +
`resource_metadata="${req.get("host")}/.well-known/oauth-protected-resource"`;
const token = req.headers.authorization &&
req.headers.authorization.split(' ')[1];
if (!token) {
res.setHeader('WWW-Authenticate', wwwAuthValue);
return res.status(401).json({ error: 'Unauthorized' });
}
client.idp.introspectTokenLocal(token)
.then(tokenData => {
// Set the token data on the request for later use
req.user = response;
next();
})
.catch(err => {
console.error('Error in middleware:', err);
res.setHeader('WWW-Authenticate', wwwAuthValue);
return res.status(401).json({ error: 'Unauthorized' });
})
};
app.post('/mcp', authorizeTokenMiddleware, async (req, res) => {
const server = new McpServer({ name: "Demo", version: "1.0.0" });
// The server can now access the validated req.user within tool calls
server.tool("whoami", {}, async () => ({
content: [{
type: "text",
text: "You are " + JSON.stringify(req.user, null, 2),
}]
}));
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on('close', () => {
transport.close();
server.close();
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('Error handling MCP request:', error);
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
})
```
```javascript Consumer expandable theme={null}
import * as stytch from "stytch";
import express from "express";
const app = express();
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
custom_base_url: '${projectDomain}',
})
const authorizeTokenMiddleware = async (req, res, next) => {
const wwwAuthValue = `Bearer error="Unauthorized", ` +
`error_description="Unauthorized",` +
`resource_metadata="${req.get("host")}/.well-known/oauth-protected-resource"`;
const token = req.headers.authorization &&
req.headers.authorization.split(' ')[1];
if (!token) {
res.setHeader('WWW-Authenticate', wwwAuthValue);
return res.status(401).json({ error: 'Unauthorized' });
}
client.idp.introspectTokenLocal(token)
.then(tokenData => {
// Set the token data on the request for later use
req.user = response;
next();
})
.catch(err => {
console.error('Error in middleware:', err);
res.setHeader('WWW-Authenticate', wwwAuthValue);
return res.status(401).json({ error: 'Unauthorized' });
})
};
app.post('/mcp', authorizeTokenMiddleware, async (req, res) => {
const server = new McpServer({ name: "Demo", version: "1.0.0" });
// The server can now access the validated req.user within tool calls
server.tool("whoami", {}, async () => ({
content: [{
type: "text",
text: "You are " + JSON.stringify(req.user, null, 2),
}]
}));
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on('close', () => {
transport.close();
server.close();
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('Error handling MCP request:', error);
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
})
```
NextJS projects should use the [Vercel MCP Adapter](https://github.com/vercel/mcp-adapter) as well as the [Stytch](https://www.npmjs.com/package/stytch) backend SDK to perform token validation using the [Introspect Access Token](/docs/api-reference/b2b/api/connected-apps/methods/introspect-token) API method.
```javascript B2B expandable theme={null}
import { createMcpHandler, withMcpAuth } from "mcp-handler";
import * as stytch from "stytch";
const client = new stytch.B2BClient({
project_id: "PROJECT_ID",
secret: "SECRET",
custom_base_url: '${projectDomain}',
})
const handler = createMcpHandler(
server => {
// withMcpAuth will set the authInfo on the server for use in tool calls
server.tool("whoami", {}, async ({authInfo}) => ({
content: [{
type: "text",
text: "You are " + JSON.stringify(authInfo, null, 2),
}]
}));
return server
}
);
const verifyToken = async (req, token) => {
if (!token) return;
const { audience, scope, expires_at, ...rest } =
await client.idp.introspectTokenLocal(token);
// This payload will be passed as authInfo
return {
token,
clientId: audience,
scopes: scope.split(' '),
expiresAt: expires_at,
extra: rest
};
};
const authHandler = withMcpAuth(handler, verifyToken, {
required: true, // Make auth required for all requests
});
export { authHandler as GET, authHandler as POST };
```
```javascript Consumer expandable theme={null}
import { createMcpHandler, withMcpAuth } from "mcp-handler";
import * as stytch from "stytch";
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
custom_base_url: '${projectDomain}',
})
const handler = createMcpHandler(
server => {
// withMcpAuth will set the authInfo on the server for use in tool calls
server.tool("whoami", {}, async ({authInfo}) => ({
content: [{
type: "text",
text: "You are " + JSON.stringify(authInfo, null, 2),
}]
}));
return server
}
);
const verifyToken = async (req, token) => {
if (!token) return;
const { audience, scope, expires_at, ...rest } =
await client.idp.introspectTokenLocal(token);
// This payload will be passed as authInfo
return {
token,
clientId: audience,
scopes: scope.split(' '),
expiresAt: expires_at,
extra: rest
};
};
const authHandler = withMcpAuth(handler, verifyToken, {
required: true, // Make auth required for all requests
});
export { authHandler as GET, authHandler as POST };
```
[FastMCP](https://gofastmcp.com/getting-started/welcome) projects should use the builtin [JWT Verifier](https://gofastmcp.com/servers/auth/token-verification#jwt-token-verification) utility to perform token validation.
```javascript B2B expandable theme={null}
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider
from fastmcp.server.dependencies import get_access_token, AccessToken
auth = BearerAuthProvider(
jwks_uri=f"{os.getenv('STYTCH_DOMAIN')}/.well-known/jwks.json",
issuer=os.getenv("STYTCH_DOMAIN"),
algorithm="RS256",
audience=os.getenv("STYTCH_PROJECT_ID")
)
mcp = FastMCP(name="My MCP Server", auth=auth)
@mcp.tool()
def whoami() -> str:
"""Retrieve the current caller information"""
access_token = get_access_token()
member_id = access_token.claims.get("sub")
return f"member is: {member_id}"
```
```javascript Cosumer expandable theme={null}
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider
from fastmcp.server.dependencies import get_access_token, AccessToken
auth = BearerAuthProvider(
jwks_uri=f"{os.getenv('STYTCH_DOMAIN')}/.well-known/jwks.json",
issuer=os.getenv("STYTCH_DOMAIN"),
algorithm="RS256",
audience=os.getenv("STYTCH_PROJECT_ID")
)
mcp = FastMCP(name="My MCP Server", auth=auth)
@mcp.tool()
def whoami() -> str:
"""Retrieve the current caller information"""
access_token = get_access_token()
user_id = access_token.claims.get("sub")
return f"user is: {user_id}"
```
Hono applications should use the Stytch [Hono SDK](https://www.npmjs.com/package/@hono/stytch-auth) to perform token validation. Add a custom `onError` handler to set the `WWW-Authenticate`header.
```javascript B2B expandable theme={null}
import { B2B } from '@hono/stytch-auth';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPTransport } from '@hono/mcp'
const bearerAuthMiddleware = B2B.authenticateOAuthToken({
onError: (c, err) => {
console.error(err);
// Construct the WWW-Authenticate header
const url = new URL(c.req.url);
const wwwAuthValue = `Bearer error="Unauthorized", ` +
`error_description="Unauthorized",` +
`resource_metadata="${url.origin}/.well-known/oauth-protected-resource"`;
const errorResponse = new Response('Unauthorized', {
status: 401,
headers: {'WWW-Authenticate': wwwAuthValue}
});
throw new HTTPException(401, {res: errorResponse});
}
});
export default new Hono()
.use('/mcp', bearerAuthMiddleware)
.route('/mcp', async (c) => {
// The server can access the OAuth data validated by the bearerAuthMiddleware
const {claims, token: accessToken} = B2B.getOAuthData(c);
const server = new McpServer({ name: "Demo", version: "1.0.0" });
server.tool("whoami", {}, async () => ({
content: [{
type: "text",
text: "You are " + JSON.stringify(claims, null, 2),
}]
}));
const transport = new StreamableHTTPTransport()
await mcpServer.connect(transport)
return transport.handleRequest(c)
})
```
```javascript Consumer expandable theme={null}
import {Consumer} from '@hono/stytch-auth';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPTransport } from '@hono/mcp'
const bearerAuthMiddleware = Consumer.authenticateOAuthToken({
onError: (c, err) => {
console.error(err);
// Construct the WWW-Authenticate header
const url = new URL(c.req.url);
const wwwAuthValue = `Bearer error="Unauthorized", ` +
`error_description="Unauthorized",` +
`resource_metadata="${url.origin}/.well-known/oauth-protected-resource"`;
const errorResponse = new Response('Unauthorized', {
status: 401,
headers: {'WWW-Authenticate': wwwAuthValue}
});
throw new HTTPException(401, {res: errorResponse});
}
});
export default new Hono()
.use('/mcp', bearerAuthMiddleware)
.route('/mcp', async (c) => {
// The server can access the OAuth data validated by the bearerAuthMiddleware
const {claims, token: accessToken} = Consumer.getOAuthData(c);
const server = new McpServer({ name: "Demo", version: "1.0.0" });
server.tool("whoami", {}, async () => ({
content: [{
type: "text",
text: "You are " + JSON.stringify(claims, null, 2),
}]
}));
const transport = new StreamableHTTPTransport()
await mcpServer.connect(transport)
return transport.handleRequest(c)
})
```
This step is performed by your MCP Server.
The MCP Client will extract the `resource_metadata` field from the `WWW-Authenticate` header returned and issue a `GET` request to that URL to retrieve the Protected Resource Metadata (PRM) Document. The PRM Document is hosted by your MCP Server. Your MCP Server will return a JSON document containing information about how the client should request an access token.
```bash theme={null}
# The MCP Client retrieves the PRM document
curl -s "https://mcp.stytch.dev/.well-known/oauth-protected-resource" | jq
```
Here's what the Stytch MCP Server returns. Note that there are several [**Custom Scopes**](/docs/connected-apps/oauth-learn-more/oauth-scopes) used by the Stytch MCP Server that have been eliminated for brevity.
```bash theme={null}
{
"resource": "https://mcp.stytch.dev",
"authorization_servers": [
"https://rustic-kilogram-6347.customers.stytch.com"
],
"scopes_supported": ["openid", "email", "profile", "manage:project_data", ...]
}
```
| Field Name | Meaning |
| - | - |
| resource | The resource identifier - a `https://` URL. This should be the location of your MCP Server. |
| authorization\_servers | A JSON array containing a list of OAuth authorization server issuer URLs. This should be your Stytch `PROJECT_DOMAIN`. |
| scopes\_supported | A JSON array of [OAuth scopes](/docs/connected-apps/oauth-learn-more/oauth-scopes) that can be requested from your Stytch project. Some MCP clients will use the scopes returned in the `scopes_supported` array as the initial set of permissions to request. |
Here are some examples of how to implement this check in a variety of languages and runtimes:
```javascript theme={null}
import express from "express";
const app = express();
app.get('/.well-known/oauth-protected-resource/:transport?', (req, res) => {
return res.json({
resource: req.get("host"),
authorization_servers: [process.env.STYTCH_DOMAIN],
scopes_supported: ["openid", "email", "profile"]
})
})
```
Add the following at `src/app/.well-known/oauth-protected-resource/route.ts`
```javascript theme={null}
// In src/app/.well-known/oauth-protected-resource/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const response = NextResponse.json({
resource: new URL(request.url).origin,
authorization_servers: [process.env.STYTCH_DOMAIN],
scopes_supported: ["openid", "email", "profile"]
});
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
response.headers.set('Access-Control-Allow-Headers',
'Content-Type, Authorization');
return response;
}
```
```javascript theme={null}
from fastmcp import FastMCP
# Use the auth defined before
mcp = FastMCP(name="My MCP Server", auth=auth)
@mcp.custom_route("/.well-known/oauth-protected-resource", methods=["GET", "OPTIONS"])
def oauth_metadata(request: StarletteRequest) -> JSONResponse:
base_url = str(request.base_url).rstrip("/")
return JSONResponse(
{
"resource": base_url,
"authorization_servers": [os.getenv("STYTCH_DOMAIN")],
"scopes_supported": ["openid", "email", "profile"]
}
)
```
```javascript theme={null}
import {Hono} from "hono";
export default new Hono()
.get('/.well-known/oauth-protected-resource/:transport?', async (c) => {
const url = new URL(c.req.url);
return c.json({
resource: url.origin,
authorization_servers: [c.env.STYTCH_DOMAIN],
scopes_supported: ['openid', 'email', 'profile'],
})
})
```
This step is performed by Stytch.
The MCP Client will extract the `authorization_servers` field from the PRM document and issue a `GET` request to the first authorization server's Authorization Server Metadata (ASM) endpoint, as defined by [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414#section-3.1).
The ASM endpoint is hosted by Stytch, not by your server. However, the ASM endpoint needs to know the location where you host the
* `` React Component for Consumer applications
* `` React Component for B2B applications
Configure this in the Connected Apps section of the [Stytch Dashboard](https://stytch.com/dashboard/connected-apps). Enter the URL where the component is mounted into the Authorization URL field. If you don't have a value for this yet - put `https://example.com` - we'll come back to it later.
```bash theme={null}
curl -s "https://rustic-kilogram-6347.customers.stytch.com/.well-known/oauth-authorization-server" | jq
```
This will return a JSON document containing information about what functionality your Stytch project supports, and what endpoints will be used for the rest of the authorization process. For example, we see that the `` or `` React component is hosted at `https://stytch.com/oauth/authorize`, which is communicated to the MCP Client as the `authorization_endpoint`.
```bash theme={null}
{
"authorization_endpoint": "https://stytch.com/oauth/authorize",
"registration_endpoint": "https://rustic-kilogram-6347.customers.stytch.com/v1/oauth2/register",
"token_endpoint": "https://rustic-kilogram-6347.customers.stytch.com/v1/oauth2/token",
...
}
```
| Field Name | Meaning |
| - | - |
| authorization\_endpoint | URL of your Stytch Project's authorization endpoint, hosted by you and configured in the Stytch Dashboard. |
| registration\_endpoint | URL of your Stytch Project's OAuth 2.0 Dynamic Client Registration endpoint, hosted by Stytch. |
| token\_endpoint | URL of your Stytch Project's token endpoint, hosted by Stytch. |
This step is performed by Stytch.
The MCP Client needs to be given a `client_id` by Stytch, in order to uniquely identify itself. There are two ways for this to happen:
* **Pre-registration**: if the MCP Client already has a `client_id`, it may use that `client_id` and skip this step. Some MCP Clients will let users specify their own `client_id`. This is especially common in Enterprise environments where there are more limitations on what parties have access to sensitive data.
* **Dynamic registration**: the MCP Client can send a [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) (DCR) request to your Stytch Project's DCR Endpoint( for [B2B](/docs/api-reference/b2b/api/connected-apps/application-management/dynamic-client-registration), for [Consumer](/docs/api-reference/consumer/api/connected-apps/application-management/dynamic-client-registration)) and be granted a unique `client_id` on the fly.
Dynamic Client Registration is an opt-in feature, and must be enabled in the [Connected Apps](https://stytch.com/dashboard/connected-apps) dashboard.
```bash theme={null}
curl -s "https://rustic-kilogram-6347.customers.stytch.com/v1/oauth2/register" \
-H 'Content-Type: application/json' \
-d '{
"client_name": "MCP Inspector",
"redirect_uris": ["http://localhost:6274/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}' | jq
```
Stytch will return a JSON blob with the client registration information. This will be a superset of the original registration metadata, and will contain a newly-issued `client_id`. All clients created through DCR will be [Third Party Public](/docs/connected-apps/oauth-learn-more/client-types) clients within Stytch.
```bash theme={null}
{
"client_id": "connected-app-live-aa7562c6-67b6-4363-bbc0-643fed5990ec",
"client_name": "MCP Inspector",
"grant_types": ["authorization_code", "refresh_token"],
"redirect_uris": ["http://localhost:6274/oauth/callback"],
"response_types": ["code"],
...
}
```
This step is performed by the MCP Client.
The MCP client will now open a browser window to the `authorization_endpoint` previously returned in the ASM document. The MCP client will pass in various query parameters to tell us who it is and what data it is trying to access. The full set of parameters that may be passed are detailed in the [OAuth 2.1 Authorization Request](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#name-authorization-request) specification.
```bash theme={null}
const base = "https://stytch.com/oauth/authorize";
const params = new URLSearchParams({
// The client_id issued from Dynamic Client Registration in step 4
client_id: "connected-app-live-aa7562c6-67b6-4363-bbc0-643fed5990ec",
// Where the member should be redirected back with the code
// This _must_ match one of the `redirect_uris` previously registered
redirect_uri: "http://localhost:6274/oauth/callback",
// The desired response - an authorization code
response_type: "code",
// The permissions being requested
// Usually inferred from the `scopes_supported` field in the PRM
scope: "openid email profile manage:project_data",
// An opaque value used by the client to track login state
// The AS will return this value in the callback
state: "29499...",
// A PKCE code challenge - a one-time secret created by the client to secure the request
code_challenge: "meY9Iy...",
code_challenge_method: "S256",
});
// Perform a full-page navigation
window.location.href = `${base}?${params.toString()}`;
```
This step is performed by your main application.
The `authorization_endpoint` is the location of a web page hosting the Stytch `` or `` React component. This component will parse the query parameters passed by the MCP Client in step 5, validate the authorization request, and prompt the member for consent. If the member consents to share their data with the MCP Client, the member will be redirected back to the `redirect_uri` owned by the client with a `code`.
```javascript B2B React theme={null}
import { B2BIdentityProvider, useStytchMember } from '@stytch/react/b2b';
import { useEffect } from 'react';
const OAuthAuthorizePage = () => {
const { member } = useStytchMember();
// The member must be logged in before they can consent to share data
useEffect(() => {
if (!member) window.location.href = '/login';
}, [member]);
return ;
};
```
```javascript B2B Vanilla JS theme={null}
import { createStytchB2BClient, StytchB2BIdentityProvider } from '@stytch/vanilla-js/b2b';
const stytch = createStytchB2BClient('PUBLIC_TOKEN');
// The member must be logged in before they can consent to share data
if (!stytch.member.getSync()) {
throw Error('Member is not logged in; please redirect to login')
}
customElements.define('stytch-identity-provider', StytchB2BIdentityProvider);
document.getElementById('stytch-identity-provider').render({
client: stytch,
});
```
```javascript Consumer React theme={null}
import { IdentityProvider, useStytchUser } from '@stytch/react';
import { useEffect } from 'react';
const OAuthAuthorizePage = () => {
const { user } = useStytchUser();
// The user must be logged in before they can consent to share data
useEffect(() => {
if (!user) window.location.href = '/login';
}, [user]);
return ;
};
```
```javascript Consumer Vanilla JS theme={null}
import { createStytchClient, StytchIdentityProvider } from '@stytch/vanilla-js';
const stytch = createStytchClient('PUBLIC_TOKEN');
// The user must be logged in before they can consent to share data
if (!stytch.user.getSync()) {
throw Error('User is not logged in; please redirect to login')
}
customElements.define('stytch-identity-provider', StytchIdentityProvider);
// In HTML:
document.getElementById('stytch-identity-provider').render({
client: stytch,
});
```
This step is performed by Stytch.
The `` or `` React component will redirect the member/user back to the client with an authorization code that the client exchanges for tokens. The client will call the `token_endpoint` listed in the ASM response - an endpoint hosted by Stytch.
```bash theme={null}
curl -X POST "https://rustic-kilogram-6347.customers.stytch.com/v1/oauth2/token" \
-d "grant_type=authorization_code" \
-d "client_id=connected-app-live-aa7562c6-67b6-4363-bbc0-643fed5990ec" \
-d "code=$CODE" \
-d "code_verifier=$CODE_VERIFIER" \
-d "redirect_uri=http://localhost:6274/oauth/callback"
```
```bash theme={null}
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "def502...",
"token_type": "Bearer",
"expires_in": 3600
}
```
The access token can now be used by the MCP Client to make requests to the MCP server.
Finally, the MCP Client can make requests to your MCP Server using the access token embedded in the Authorization header:
```bash theme={null}
curl -X POST "https://mcp.stytch.dev/mcp" \
-H 'Authorization: Bearer $ACCESS_TOKEN'
```
The middleware we implemented in Step 1 will validate this token and process the request. Your MCP Server can now use the authenticated MCP Client's information inside tool calls.
## What's Next
You should now have a working overview of all the steps that go in to setting up authorization for MCP servers.
* Follow along with our [Cloudflare MCP Guide](/docs/connected-apps/guides/mcp-servers-cloudflare) and demo app
* Learn more about [OAuth Scopes](/docs/connected-apps/oauth-learn-more/oauth-scopes) in the Stytch platform
# Integrate with MCP servers on Cloudflare
Source: https://stytch.com/docs/connected-apps/guides/mcp-servers-cloudflare
Create a remote MCP server with Stytch authentication on Cloudflare.
In this guide, we'll walk through the creation of a Remote Model Context Protocol (MCP) server which manages authentication as a Stytch Connected App. We will be examining a simple application that manages a list of tasks (TODO app) ([GitHub project](https://github.com/stytchauth/mcp-stytch-consumer-todo-list)). The TODO app has a web browser client that communicates to the server via an HTTP API. Additionally, the server implements the MCP protocol to become accessible to an MCP-compliant Large Language Model (LLM) AI client (for example Anthropic's [Claude](https://claude.ai/)), allowing us to compare and contrast the approach taken for each implementation.
This app can be run locally and can also be deployed as a Cloudflare Worker, but the basic concepts we'll cover here generalize to any cloud platform.
## Pre-requisites
In order to complete this guide, you'll need:
* A Stytch project. If you don't have one already, or would like to create a new one, in the [**Dashboard**](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select Consumer Authentication.
* The [MCP Inspector](https://github.com/modelcontextprotocol/inspector). This is a tool that will help us ensure that our MCP server is exposing the correct interface, is running properly, and allows us to explore the features of the MCP protocol in a web-based tool.
## Create a remote MCP server on Cloudflare
Navigate to "Project Settings" to note your "Project ID" and "Public token". You will need these values later.
Navigate to "Frontend SDKs" to enable the Frontend SDK for `http://localhost:3000`
Navigate to "Connected Apps" and enable "Allow dynamic client registration".
[Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) is a protocol that allows applications to register themselves with Stytch as a Connected App client. We will dive more into how Dynamic Client Registration works later in this guide.
The companion repository for this guide is located [in GitHub](https://github.com/stytchauth/mcp-stytch-consumer-todo-list). Go to that repository, clone it, and follow the README to get the TODO app up and running on your local machine.
Once you have the example up and running, explore its functionality and start to get a sense of how the app is structured, then return here for a tour of the app's implementation.
The app exposes a straightforward REST API that implements state management and access control. This API (which can be examined at [/api/TodoAPI.ts](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/TodoAPI.ts)) exposes methods to create, read, update, and delete TODO items. State management and persistence are eventually handled by the [/api/TodoService.ts](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/TodoService.ts) class which manages the app's data in the data store.
The browser frontend includes React components that work together to render the app. In [/src/Todos.tsx](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/src/Todos.tsx), the frontend makes requests to the backend API to change the state of our data.
Take a moment to read the source code, run the app, and interact with the app's state to get a feel for how data is being modeled and what operations are valid.
The [MCP Core architecture documentation](https://modelcontextprotocol.io/docs/concepts/architecture) is a good reference for understanding how an MCP server is implemented.
MCP servers use a protocol based on JSON-RPC 2.0 for message passing between the remote MCP server (what we're building) and the client, which is the LLM that is integrating with the server. The RPC protocol uses POST requests and Server-Sent Events to pass messages back and forth between the server and client. There are a few core primitives (Resources, Prompts, Tools, etc...) defined by the MCP specification that describe the schema of the messages in this protocol and what behaviors can be expected.
This is a structurally different protocol than vanilla HTTP requests, so implementing a Remote MCP server is more nuanced than a typical HTTP request/response flow. Thankfully there are libraries to assist in implementing MCP servers. Our example uses [@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk) to handle the specifics of conforming with the MCP protocol.
In [/api/TodoMCP.ts](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/TodoMCP.ts), you can follow along as the `McpServer` class defines the resources and tools that are exposed through the MCP protocol. Note that, just as in the HTTP implementation, the MCP server uses the same TodoService class to handle application state.
The details of how exactly an MCP server defines its components are implementation-dependent, but for the purposes of this walkthrough the key concept is that MCP defines a protocol for our server to implement in order to establish communications with an LLM client. This protocol can coexist with an HTTP server or stand on its own, but the fundamentals of establishing authentication and access control are similar for each.
We'll dive more deeply into how authentication is implemented in the next step.
[Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) is an OAuth standard for allowing a client to automatically register itself with an Identity Provider (IdP) server and is included as part of the Remote MCP protocol.
The first time the MCP Inspector attempts to access our TODO app it will receive a `401 Unauthorized` response. This will cause the MCP Inspector to fetch metadata about our server. In [/api/index.ts](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/index.ts) the TODO app creates a route at `/.well-known/oauth-authorization-server` which returns metadata about the OAuth server (as covered in the [Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) standard).
Once the MCP Inspector has fetched the metadata from our TODO app Stytch and the MCP Inspector will handle the rest of the Dynamic Client Registration process to create a new [Connected Apps](/docs/connected-apps/overview) client.
All dynamically registered clients are Third Party Public apps and will show up in the Stytch Dashboard when registration is complete.
Now that the MCP Inspector is registered as a client app the flow continues through the standard Connected Apps authorization process. As the MCP Inspector is a "Third Party" app, this includes showing an access request dialog for the user to allow the MCP Inspector to access data in our app. Upon completion of this flow the MCP inspector should show a successful connection and allow access to the data managed by our TODO app.
In the web client, the app wraps components in a [parent component](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/src/Todos.tsx#L29) that enforces authentication. This component verifies that there is a current Stytch user logged in before rendering its child component. If no user is logged in, it redirects to the login page.
On the server side, authentication is handled slightly differently depending on whether the REST API or the MCP protocol are being used.
For the REST API, [Hono middleware](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/lib/auth.ts#L10) is used to grab the Stytch session cookie and check its validity. This middleware is used on each route in [/api/TodoAPI.ts](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/TodoAPI.ts). As each operation happens within the context of a single API request, wrapping each request in the middleware will enforce the validity of each operation.
Similarly, when the connection to the MCP server is first established, again using Hono middleware, the server [saves the credentials](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/lib/auth.ts#L46) from the request into properties that will be accessible to the MCP server. These credentials are used when [constructing the TodoService](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/TodoMCP.ts#L16) class.
Rather than implementing specific authorization logic, for both the HTTP and the MCP server the TodoService class implements a very basic authorization scheme by namespacing the list of TODOs [by the user's ID](https://github.com/stytchauth/mcp-stytch-consumer-todo-list/blob/main/api/TodoService.ts#L16) to keep each user's data private to that user.
If you would be interested to see how to implement more nuanced authorization rules with a Stytch B2C project, read [this guide](/docs/api-reference/consumer/api/rbac/get-rbac-policy) on how this can be achieved with Stytch metadata. Additionally, a more robust authentication implementation with Stytch Role-Based Access Control can be found in our [B2B MCP Server example app](/docs/connected-apps/guides/remote-mcp-servers).
The GitHub README covers the steps necessary to deploy the app as a Cloudflare Worker. After the deploy, be sure to note that the location of the server will have changed, which requires additional configuration in Stytch. Once you have the URL for your Cloudflare Worker, repeat the process in Step 1 to add this URL as an "Authorized Application" in the "Frontend SDK" settings.
## What's next
* View our guide on our [B2B MCP Server example app](/docs/connected-apps/guides/remote-mcp-servers) to see a similar app using B2B Authentication with OAuth scopes and Role-Based Access Control
* Familiarize yourself with [Stytch's B2B SDKs](/docs/api-reference/b2b/api/sdks) and [Consumer SDKs](/docs/api-reference/consumer/api/sdks) for a deeper dive on how to integrate your MCP server with Stytch.
* Browse the [Model Context Protocol](https://modelcontextprotocol.io/introduction) documentation for more information about Remote MCP Servers.
# Integrate with MCP servers on Vercel
Source: https://stytch.com/docs/connected-apps/guides/mcp-servers-vercel
Create a remote MCP server with Stytch authentication on Vercel.
In this guide, we'll walk through the creation of a Remote Model Context Protocol (MCP) server which manages authentication as a Stytch Connected App. We will be examining a simple application that manages a list of tasks (TODO app) ([GitHub project](https://github.com/stytchauth/vercel-mcp-example)). The web app is managed as a single page app hosted with Vercel, and additionally the server implements the MCP protocol to become accessible to a Large Language Model (LLM) MCP client. This app can be run locally and can also be deployed on Vercel, but the basic concepts we'll cover here generalize to any cloud platform capable of hosting an MCP server.
## Pre-requisites
In order to complete this guide, you'll need:
* A Stytch project. If you don't have one already, or would like to create a new one, in the [**Dashboard**](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select Consumer Authentication.
* The [MCP Inspector](https://github.com/modelcontextprotocol/inspector). This is a tool that will help us ensure that our MCP server is exposing the correct interface, is running properly, and allows us to explore the features of the MCP protocol in a web-based tool.
## Create a remote MCP server
Navigate to "Project Settings" to note your "Project ID" and "Public token". You will need these values later.
Navigate to "Frontend SDKs" to enable the Frontend SDK for `http://localhost:3000`
Navigate to "Connected Apps" and enable "Allow dynamic client registration".
[Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) is a protocol that allows applications to register themselves with Stytch as a Connected App client. We will dive more into how Dynamic Client Registration works later in this guide.
The companion repository for this guide is located [in GitHub](https://github.com/stytchauth/vercel-mcp-example). Go to that repository, clone it, and follow the README to get the TODO app up and running on your local machine.
Once you have the example up and running, explore its functionality and start to get a sense of how the app is structured, then return here for a tour of the app's implementation.
This demo is structured as a lightweight single page app / MCP server that uses Stytch's frontend SDKs both to manage auth and also implement the todo list functionality. This is done using Stytch's `untrusted_metadata` field on the `user` object to store the list data itself rather than a dedicated database--although the architecture operates in such a way that a [different data layer](https://github.com/stytchauth/vercel-mcp-example/blob/a78f87aa19c449f046d15b5067b4c09dfeaa3876/src/services/TodoService.ts#L15) can be used.)
The [MCP Core architecture documentation](https://modelcontextprotocol.io/docs/concepts/architecture) is a good reference for understanding how an MCP server is implemented.
MCP servers use a protocol based on JSON-RPC 2.0 for message passing between the remote MCP server (what we're building) and the client, which is the LLM that is integrating with the server. The RPC protocol uses POST requests to the MCP server which either return a JSON payload or uses Server-Sent Events to pass messages back and forth between the server and client. There are a few core primitives (Resources, Prompts, Tools, etc...) defined by the MCP specification that describe the schema of the messages in this protocol and what behaviors can be expected.
This is a structurally different protocol than vanilla HTTP requests, so implementing a Remote MCP server is more nuanced than a typical HTTP request/response flow. Thankfully there are libraries to assist in implementing MCP servers. Our example uses [@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk) and [@vercel/mcp-adapter](https://www.npmjs.com/package/@vercel/mcp-adapter) to handle the specifics of conforming with the MCP protocol.
The details of how exactly an MCP server defines its components are implementation-dependent, but for the purposes of this walkthrough the key concept is that MCP defines a protocol for our server to implement in order to establish communications with an LLM client. This protocol can coexist with an HTTP server or stand on its own, but the fundamentals of establishing authentication and access control are similar for each.
The implementation for the MCP server in this example can be found in [TodoMCP.ts](https://github.com/stytchauth/vercel-mcp-example/blob/a78f87aa19c449f046d15b5067b4c09dfeaa3876/src/services/TodoMCP.ts#L16)
We'll dive more deeply into how authentication is implemented in the next step.
[Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) is an OAuth standard for allowing a client to automatically register itself with an Identity Provider (IDP) server and is included as part of the Remote MCP protocol.
MCP servers should implement the Oauth 2.0 [Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9728) standard. The first time the MCP Inspector attempts to access our TODO app (the protected resource) without credentials it will receive a `401 Unauthorized` response with a `WWW-Authenticate` header. This header points to a server which has authorization metadata, which is also this app. In the app there is a route at `/.well-known/oauth-protected-resource` which returns metadata about where to find its authorization server (the Stytch subdomain for your project). Once this location is known, Stytch and the MCP Inspector will handle the rest of the Dynamic Client Registration process to create a new [Connected Apps](/docs/connected-apps/overview) client.
All dynamically registered clients are Third Party Public apps and will show up in the Stytch Dashboard when registration is complete.
Now that the MCP Inspector is registered as a client app the flow continues through the standard Connected Apps authorization process. As the MCP Inspector is a "Third Party" app, this includes showing an access request dialog for the user to allow the MCP Inspector to access data in our app. Upon completion of this flow the MCP inspector should show a successful connection and allow access to the data managed by our TODO app.
In the web client, the app wraps its components in a [parent component](https://github.com/stytchauth/vercel-mcp-example/blob/a78f87aa19c449f046d15b5067b4c09dfeaa3876/src/components/Auth.tsx#L21) that enforces authentication. This component verifies that there is a current Stytch user logged in before rendering its child component. If no user is logged in, it redirects to the login page.
For the MCP server we use Stytch's [headless frontend SDK to authenticate](https://github.com/stytchauth/vercel-mcp-example/blob/a78f87aa19c449f046d15b5067b4c09dfeaa3876/src/services/TodoMCP.ts#L11). This retains user information from when we authorized the app connecting to our MCP server and uses this to make requests to Stytch's API.
Rather than implementing specific authorization logic, the use of user-scoped `untrusted_metadata` means that each user will only see their own data.
If you would be interested to see how to implement more nuanced authorization rules with a Stytch B2C project, read [this guide](/docs/api-reference/consumer/api/rbac/get-rbac-policy) on how this can be achieved with Stytch metadata. Additionally, a more robust authentication implementation with Stytch Role-Based Access Control can be found in our [B2B MCP Server example app](/docs/connected-apps/guides/remote-mcp-servers).
Vercel naturally supports deploying by linking a Github repository; just note, after the deploy, the location of the server will have changed away from `localhost`, which requires additional configuration in Stytch. Once you have the URL for your Vercel app, repeat the process in Step 1 to add this URL as an "Authorized Application" in the "Frontend SDK" settings.
## What's next
* View our guide on our [B2B MCP Server example app](/docs/connected-apps/guides/remote-mcp-servers) to see a similar app using B2B Authentication with OAuth scopes and Role-Based Access Control
* View a similar walkthrough for deploying and MCP server as a [Cloudflare Worker](/docs/connected-apps/guides/mcp-servers-cloudflare)
* Familiarize yourself with [Stytch's B2B SDKs](/docs/api-reference/b2b/api/sdks) and [Consumer SDKs](/docs/api-reference/consumer/api/sdks) for a deeper dive on how to integrate your MCP server with Stytch.
* Browse the [Model Context Protocol](https://modelcontextprotocol.io/introduction) documentation for more information about Remote MCP Servers.
# Remote MCP servers
Source: https://stytch.com/docs/connected-apps/guides/remote-mcp-servers
Create a Remote Model Context Protocol (MCP) server to manage authentication and authorization of a Stytch Connected App.
In this guide, we'll walk through the creation of a Remote Model Context Protocol (MCP) server which manages authentication and authorization as a Stytch Connected App, including authentication and authorization through role-based access control.
We will be examining a simple server that maintains a list of Objectives and Key Results (OKR Manager) ([GitHub project](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager)). The OKR Manager has a web browser client that communicates to the app's server via an HTTP API. Additionally, the server implements the MCP protocol to become accessible to an MCP-compliant Large Language Model (LLM) AI client (for example Anthropic's [Claude](https://claude.ai/)), allowing us to compare and contrast the approach taken for each implementation. This app can be run locally and can also be deployed as a Cloudflare Worker, but the basic concepts we'll cover here generalize to any cloud platform.
## Pre-requisites
In order to complete this guide, you'll need:
* A Stytch project. If you don't have one already, or would like to create a new one, in the [**Dashboard**](https://stytch.com/dashboard), click on your existing project name in the top left corner of the Dashboard, click **Create Project**, and then select B2B Authentication.
* The [MCP Inspector](https://github.com/modelcontextprotocol/inspector). This is a tool that will help us ensure that our MCP server is exposing the correct interface, is running properly, and allows us to explore the features of the MCP protocol in a web-based tool.
## Create a remote MCP server
Navigate to "Project Settings" to note your "Project ID" and "Public token". You will need these values later.
Navigate to "Frontend SDKs" to enable the Frontend SDK for `http://localhost:3000`
Navigate to "Connected Apps" and enable "Allow dynamic client registration".
[Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) is a protocol that allows applications to register themselves with Stytch as a Connected App client. We will dive more into how Dynamic Client Registration works later in this guide.
Navigate to your [Workspace Settings > Management API](https://stytch.com/dashboard/settings/management-api) and create a new workspace management key. Copy the "Key ID" and "Secret", you will need these values later.
* These credentials are used for Stytch's [Programmatic Workspace Actions](/docs/resources/workspace-management/pwa-api), which allows applications to make changes to Stytch's configuration.
* We will use these to define roles for authorization with Stytch's [Role-Based Access Control](/docs/multi-tenant-auth/enterprise-ready/rbac/assigning-roles-to-members), which will be covered more later in this guide.
The companion repository for this guide is located [in GitHub](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager). Go to that repository, clone it, and follow the README to get the OKR Manager app up and running on your local machine.
Once you have the example up and running, explore its functionality and start to get a sense of how the app is structured, then return here for a tour of the app's implementation.
The app exposes a straightforward REST API that implements state management and access control. This API (which can be examined at [/api/OKRAPI.ts](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/OKRAPI.ts)) exposes methods to create, read, update, and delete high level Objectives, which contain zero or more Key Results to support those Objectives. State management and persistence are handled by the OKRService class, defined in [/api/OKRService.ts](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/OKRService.ts), which manages the app's data in the data store.
The browser frontend includes a number of React components that work together to render the app. In [/src/OKREditor.tsx](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/src/OKREditor.tsx) you can see how the frontend makes requests to the backend API to change the state of our data.
Take a moment to read the source code, run the app, and interact with the app's state to get a feel for how data is being modeled and what operations are valid.
The [MCP Core architecture documentation](https://modelcontextprotocol.io/docs/concepts/architecture) is a good reference for understanding how an MCP server is implemented.
MCP servers use a protocol based on JSON-RPC 2.0 for message passing between the remote MCP server (what we're building) and the client, which is the LLM that is integrating with the server. The RPC protocol uses POST requests and Server-Sent Events to pass messages back and forth between the server and client. There are a few core primitives (Resource, Prompts, Tools, etc...) defined by the MCP specification that describe the schema of the messages in this protocol and what behaviors can be expected.
This is a structurally different protocol than vanilla HTTP requests, so implementing a Remote MCP server is more nuanced than a typical HTTP request/response flow. Thankfully there are libraries to assist in implementing MCP servers. Our example uses [@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk) to handle the specifics of conforming with the MCP protocol.
In [/api/OKRManagerMCP.ts](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/OKRManagerMCP.ts), you can follow along as the \`McpServer class defines the resources and tools that are exposed through the MCP protocol. Note that, just as in the HTTP implementation, the MCP server uses the [OKRService class](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/OKRService.ts) to handle application state.
The details of how exactly an MCP server defines its components are implementation-dependent, but for the purposes of this walkthrough the key concept is that MCP defines a protocol for our server to implement in order to establish communications with an LLM client. This protocol can coexist with an HTTP server or stand on its own, but the fundamentals of establishing authorization and authentication are similar for each.
We'll dive more deeply into how authentication is implemented in the next step.
[Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) is an OAuth standard for allowing a client to automatically register itself with an Identity Provider (IdP) server and is included as part of the Remote MCP protocol.
The first time the MCP Inspector attempts to access our OKR Manager app it will receive a `` 401 Unauthorized` `` response. This will cause the MCP Inspector to fetch metadata about our server. In [/api/index.ts](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/index.ts) the OKR Manager app creates a route at `/.well-known/oauth-authorization-server` which returns metadata about the OAuth server (as covered in the [Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) standard).
Once the MCP Inspector has fetched the metadata from our OKR Manager app Stytch and the MCP Inspector will handle the rest of the Dynamic Client Registration process to create a new [Connected Apps](/docs/connected-apps/overview) client.
All dynamically registered clients are Third Party Public apps and will show up in the Stytch Dashboard when registration is complete.
Now that the MCP Inspector is registered as a client app the flow continues through the standard Connected Apps authorization process. As the MCP Inspector is a "Third Party" app, this includes showing an access request dialog for the user to allow the MCP Inspector to access data in our app. Upon completion of this flow the MCP inspector should show a successful connection and allow access to the data managed by our OKR Manager app.
Our OKR Manager app uses Stytch's [Role-Based Access Control](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy) to manage authorization. When we initially configured the app, we ran [this step](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/tree/main?tab=readme-ov-file#on-your-machine) as instructed in the GitHub README:
```bash theme={null}
// Using example credentials, replace with your own
npm run update-policy -- --key-id "workspace-key-prod-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" --secret "6ZcNGH7v9Oxxxxxxxxxx" --project-id "project-test-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
```
This command used Programmatic Workspace Actions to define roles for the OKR Manager app. On the [RBAC page](https://stytch.com/dashboard/rbac) in the Dashboard, you will now see some new Roles, Resources, and Scopes. Take a moment to examine these.
When the OKR Manager app begins the authorization flow with Stytch, the app [sets the desired scopes](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/src/Auth.tsx#L98) on the request and configures the consent screen to show the desired access permissions for the inspector. If these scopes are granted by the user, they will be checked when we manage access control in the app.
In the web client, the app [wraps the OKREditor component on export](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/src/OKREditor.tsx#L310) in parent components that enforce authentication and authorization. For authentication, we [verify that there is a current Stytch Member](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/src/Auth.tsx#L24) logged in and redirect to the `/login` page if not. For authorization, the UI uses the Stytch frontend SDK higher-order component `withStytchPermissions` to calculate the permissions available to the user and to pass them down as props to its wrapped component. These are [checked as the components are rendered](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/src/OKREditor.tsx#L78) and used to present an appropriate UI that communicates the user's permissions (for instance, graying out components for operations that are not permitted to the user).
On the server side permissions are handled slightly differently depending on whether the REST API or the MCP protocol are being used.
For the REST API, [Hono middleware](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/lib/auth.ts#L29) is used to grab the Stytch session cookie and check its validity and that its claims allow access for the given resource and action. This middleware is used on each route in [/api/OKRAPI.ts](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/OKRAPI.ts). As each operation happens within the context of a single API request, wrapping all the requests in the middleware will enforce the validity of each operation.
Since the MCP protocol uses long-lived connections that maintain an open channel for RPC operations, we need to handle security differently for the MCP server. Note that a connection can be established with valid credentials, but those credentials may expire during the lifetime of the long-lived connection. To handle this case properly we should check credential validity on every MCP *operation*, not on every *request*, yet the credentials are only passed with the initial request that establishes the connection.
When the connection to the MCP server is first established, again using Hono middleware, the server [saves the credentials](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/lib/auth.ts#L80) from the request into properties that will be accessible to the MCP server on execution of each operation. The [OKRManagerMCP](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/OKRManagerMCP.ts) class defines a helper function `withRequiredPermissions` that is used [in the implementation of each operation](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/OKRManagerMCP.ts#L105). It extracts the variables saved from when the connection was established and passes these properties on to [stytchRBACEnforcement](https://github.com/stytchauth/mcp-stytch-b2b-okr-manager/blob/main/api/lib/auth.ts#L96) to validate the credentials. This ensures that we verify access control on every operation.
The GitHub README covers the steps necessary to deploy the app as a Cloudflare Worker. After the deploy, be sure to note that the location of the server will have changed, which requires additional configuration in Stytch. Once you have the URL for your Cloudflare Worker, repeat the process in Step 1 to add this URL as an "Authorized Application" in the "Frontend SDK" settings.
## What's next
* Familiarize yourself with [Stytch's B2B SDKs](/docs/api-reference/b2b/api/sdks) and [Consumer SDKs](/docs/api-reference/consumer/api/sdks) for a deeper dive on how to integrate your MCP server with Stytch.
* Read more about [Role-Based Access Control](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy) to understand Stytch's authorization in more detail.
* Browse the [Model Context Protocol](https://modelcontextprotocol.io/introduction) documentation for more information about Remote MCP Servers.
# Client types in Stytch Connected Apps
Source: https://stytch.com/docs/connected-apps/oauth-learn-more/client-types
Understand the different types of OAuth clients
Stytch's Connected Apps feature enables Stytch-powered Applications to act as the Identity Provider (IdP) for OAuth 2.0 clients—these might be internal tools, third-party integrations, or community-built apps. All clients fall into one of four categories, defined by two key properties:
1. **Ownership**: Is the app *first-party* (owned by you) or *third-party* (built by an external developer)?
2. **Confidentiality**: Is the app *confidential* (able to securely store a client secret) or *public* (unable to store secrets securely)?
### Client types
Combining ownership and confidentiality yields four distinct client types:
* **First-Party**
* **First-Party Public**
* **Third-Party**
* **Third-Party Public**
Connected Apps always act on behalf of an end user and are constrained to a subset of that user's permissions. For non-interactive machine identities that act independently of user sessions, refer to the M2M Product.
Additionally, clients can be created in a variety of different ways depending on your use case:
* via the Stytch Platform Dashboard and APIs
* via Dynamic Client Registration
* via Client ID Metadata documents
***
## **Use Case Overview**
| Use Case | Recommended Client Type |
| :- | :- |
| Secondary sites you own and want to share user data with | First-Party |
| Native mobile apps or CLI tools developed by you | First-Party Public |
| Third-party integrations that want to access user data (e.g. plugins) | Third-Party |
| Third-party CLI tools, desktop apps, or dynamically registered clients | Third-Party Public |
***
## **Ownership**
### **First-Party Applications**
These are trusted applications developed and managed by you, the owner of the Stytch project. First party applications are:
* Often **exempt from explicit consent** screens, depending on your configuration and scopes.
* Eligible for **session exchange**, enabling session sharing across websites and devices.
### **Third-Party Applications**
These apps are built and maintained by external developers or companies.
* **End-user consent is required** for all interactions.
* **Session exchange is not supported**—access is scoped strictly to the OAuth tokens issued.
***
## **Confidentiality**
### **Confidential Clients**
Confidential clients are capable of storing secrets securely, typically traditional server-based applications. Confidential clients are:
* Issued a **client secret** which can be used via `client_secret_post` or `client_secret_basic` authentication mechanisms. **PKCE** is recommended but not required.
* Must adhere to strict **HTTPS redirect URIs** in production. `http://Localhost` or loopback redirects are not allowed.
* **Refresh tokens do not rotate**; they remain valid until revoked or expired.
### **Public Clients**
Public clients run in environments where secure storage is not possible, such as native or CLI apps. Public clients:
* Are not issued a client secret and use the `none` authentication strategy. Public clients **must use PKCE** for secure authorization code exchange.
* May use more **permissive redirect URIs**, including loopback and custom schemes.
* **Refresh tokens rotate** on use, and the platform **detects replay attempts** to guard against theft.
***
## **Client Creation Methods**
### **Stytch Platform API**
Clients can be created programmatically using the Create Connected App endpoint. This is the most common method for creating clients.
When creating a client, you specify the `` parameter to determine the type of client:
* `first_party` : Creates a First-Party Confidential Client
* `first_party_public` : Creates a First-Party Public Client
* `third_party`: Creates a Third-Party Confidential Client
* `third_party_public` : Creates a Third-Party Public Client
**Important:** When creating confidential clients `first_party` or `third_party`), the API returns a `client_secret` in the response. This is the only time you will be able to view the secret.
Stytch stores only a hash and cannot recover it if lost. Store it securely immediately. If you need a new client secret, use the **Start Secret Rotation** (for [B2B](/docs/api-reference/b2b/api/connected-apps/rotate-secret/start-secret-rotation), for [Consumer](/docs/api-reference/consumer/api/connected-apps/rotate-secret/start-secret-rotation)) endpoint to start a secret rotation flow.
You can also create and manage clients through the [Connected Apps section](https://stytch.com/dashboard/connected-apps) of the Stytch Dashboard.
### **Dynamic Client Registration**
Dynamic Client Registration (DCR) enables clients to be created at runtime instead of being pre-registered. This is most common in the [MCP](/docs/connected-apps/guides/mcp-auth-overview) ecosystem. These clients can register themselves using the Dynamic Client Registration endpoint (for [B2B](/docs/api-reference/b2b/api/connected-apps/application-management/dynamic-client-registration), for [Consumer](/docs/api-reference/consumer/api/connected-apps/application-management/dynamic-client-registration)).
**How it works:**
This feature must be enabled in the [Connected Apps section](https://stytch.com/dashboard/connected-apps) of the Dashboard. Once enabled, clients can self-register by submitting a JSON payload containing:
* Client name
* Redirect URIs
* Desired authentication method `none`, `client_secret_post`, or `client_secret_basic`)
**Considerations:**
* All dynamically registered clients are **Third-Party Public** or **Third-Party Confidential** clients, depending on the authentication method specified.
* Stytch automatically deduplicates public clients based on a hash of the metadata—if an identical client already exists, the same `client_id` is returned instead of creating a duplicate.
* Confidential clients (which are granted a `client_secret`) will always be issued a unique client ID.
CIMD is an emerging standard that offers a more secure alternative to Dynamic Client Registration, which has numerous operational issues in large enterprise environments. You can read more about the limitations of DCR [here](https://blog.modelcontextprotocol.io/posts/client_registration/).
### Client ID Metadata Documents (Beta)
Client ID Metadata Documents (CIMD) enable OAuth clients to self-describe by hosting their own registration metadata at an HTTPS URL and using that URL as their `client_id`. Instead of manually registering with Stytch ahead of time, Stytch will fetch and validate the client metadata during authorization.
**How it works:**
1. A client hosts a JSON metadata document at an HTTPS URL (e.g., [https://client.dev/oauth/metadata.json](https://client.dev/oauth/metadata.json))
2. The client uses this URL as its `client_id` when initiating OAuth flows
3. Stytch fetches the document from the URL and uses its contents as the client's configuration
This feature must be enabled in the [Connected Apps section](https://stytch.com/dashboard/connected-apps) of the Dashboard.
**Considerations:**
* Stytch will refresh the client's metadata periodically, which means that manual changes to the client via the Dashboard or the API may be overwritten at a later date.
* All clients created via CIMD are Third-Party Public clients.
* The `client_id` and `client_uri` fields must have the same origin.
**Note:** [CIMD](https://client.dev/) is an IETF draft specification. Changes in validation logic may occur before final adoption.
# OAuth basics: terminology and grant types
Source: https://stytch.com/docs/connected-apps/oauth-learn-more/oauth-basics
Key OAuth concepts including OAuth terminology and grant types.
Connected Apps is a Stytch product that enables your application to become an OAuth and OIDC Authorization server. Understanding some key OAuth concepts will help you as you get started implementing Connected Apps.
### **OAuth terminology**
There are four key entities involved in an OAuth transaction: the client, the resource owner, the authorization server, and the resource server.
* **Client**: The client is the entity seeking authorization to obtain access to the resource owner's account or resources. The client is often also referred to as the application or relying party.
* **Resource owner**: The resource owner, or the end user, is the entity that owns the resources the client is trying to access. The resource owner grants authorization to the client.
* **Authorization server**: The authorization server is the service that verifies the identity of the user and the client, and mints access tokens for the client to use at the resource server. The authorization server is often referred to as the Identity Provider (IDP).
* **Resource server**: The resource server is the service that hosts the resource owner's resources. The resource server validates the access token minted by the authorization server.
### OAuth Grant Types
There are multiple flows that the client, resource owner, and authorization server can participate in that result in an access token. These flows are referred to as "grant types". Different grant types can be used depending on the circumstances of the interaction.
* The **Authorization Code Flow**, also known as the Three-legged OAuth flow, is based on browser redirects with active user involvement. Authorization code flows grant a client access to a specific user's data on behalf of that user. This is the most common OAuth flow.
* The **Refresh Token Flow** is an optional additional procedure that clients can use for long-term background access to a user's account after the initial authorization code flow is complete.
* The **Client Credentials Flow** grants a client access to data based on the client's own identity, without any user involvement whatsoever. In Stytch, Client Credentials flows are built using a separate M2M product.
* The **Cross App Access (XAA) Flow** (also known as the Identity Assertion Authorization Grant or ID-JAG) allows a client to exchange a JWT issued by an external workforce identity provider for a Stytch access token, without requiring browser-based user interaction. This is useful for service-to-service contexts where the user has already authenticated with an external IdP. Learn more in the [Cross App Access guide](/docs/connected-apps/guides/cross-app-access).
# OAuth scopes
Source: https://stytch.com/docs/connected-apps/oauth-learn-more/oauth-scopes
Understanding OAuth Scopes in Stytch Connected Apps
During an OAuth flow, a Connected App requests authorization from a user via the `scope` parameter - a space-delimited list of capabilities that Connected App wishes to be granted. Clients should request access tokens with the minimal set of capabilities required for that client to function.
For example, a client may need "read" access to a user's resources, but doesn't need to update resources. This client should request only read-only access, and obtain an access token that cannot be used to update resources.
A Connected App Client can request a token with the scope of openid profile read:data as shown:
```javascript theme={null}
const scopes = ['openid', 'profile', 'read:data']
const params = new URLSearchParams({
client_id: 'connected-app-test-d731954d-dab3-4a2b-bdee-07f3ad1be888',
redirect_uri: 'https://app.example/oauth/callback',
response_type: 'code',
scope: scopes.join(' '), // space-delimited string
state: 'some-state-value',
});
// Pass these params to the Authorization URL page in your application
window.location.href = `https://idp.example/oauth2/authorize?${params.toString()}`;
```
[Access Tokens](/docs/api-reference/b2b/api/connected-apps/tokens/connected-app-access-token-object) granted to Stytch Connected Apps will contain a `scope` field representing the capabilities granted to the token.
***
## **Built-in Scopes**
Built-in scopes are preconfigured scopes within the Stytch platform.
### **Built-in OIDC Scopes**
The Stytch platform supports all scopes defined by the [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) specification with no prior configuration required.
These scopes control whether an ID Token (for [B2B](/docs/api-reference/b2b/api/connected-apps/tokens/connected-app-id-token-object), for [Consumer](/docs/api-reference/consumer/api/connected-apps/tokens/connected-app-id-token-object)) is granted to the Connected App, and what claims are present in the ID Token and in the `UserInfo` API response (for [B2B](/docs/api-reference/b2b/api/connected-apps/methods/get-userinfo), for [Consumer](/docs/api-reference/consumer/api/connected-apps/methods/get-userinfo)).
| Name | Effect when granted |
| :- | :- |
| `openid` | An OIDC `id_token` will be returned from the `/oauth2/token` endpoint |
| `email` | The `email` and `email_verified` claims will be returned in the `id_token` |
| `phone` | The `phone_number` and `phone_number_verified` claims will be returned in the `id_token` |
| `profile` | The `name`, `given_name`, `middle_name`, `family_name`, `picture`, and `locale` claims will be returned in the `id_token` |
The Stytch platform also supports scopes defined for [Offline Access](https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess) of user data.
| Name | Effect when granted |
| :- | :- |
| `offline_access` | A `refresh_token` will be returned from the `/oauth2/token` endpoint for use in subsequent refresh token flows |
### **Built-in Stytch Platform Scopes**
The Stytch platform supports additional scopes used for specific actions in the Stytch API. [**First Party**](/docs/connected-apps/oauth-learn-more/client-types) OAuth clients can request the `full_access` scope to enable access to the Exchange Access Token endpoint (for [B2B](/docs/api-reference/b2b/api/sessions/exchange-access-token), for [Consumer](/docs/api-reference/consumer/api/sessions/exchange-access-token)).
| Name | Effect when granted |
| :- | :- |
| `full_access` | Enables the Exchange Access Token API call |
## **Custom Scopes**
Your application can additionally define custom scopes tied to your **RBAC (Role-Based Access Control)** system (for [B2B](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy), for [Consumer](/docs/consumer-auth/authorization/create-rbac-policy)). Create custom scopes to support the different use-cases
### Defining Custom Scopes
Your application's RBAC policy is managed in the [Stytch RBAC dashboard](https://stytch.com/dashboard/rbac). Before defining custom scopes, you must define **Resources** (for [B2B](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy), for [Consumer](/docs/consumer-auth/authorization/create-rbac-policy)) for those scopes to act on. Each resource has a set of actions that can be performed upon that resource. Scopes are then composed of **permissions**, where each permission is an `action` applied to a `resource`.
For example, given the resources of:
```json theme={null}
[
{
'resource': 'images',
'actions': ['read', 'write', 'delete']
},
{
'resource': 'documents',
'actions': ['read', 'write', 'comment', 'delete']
}
];
```
You could define the corresponding scopes:
```json expandable theme={null}
[
{
'scope': 'read:data',
'description': 'Read access to company data',
'permissions': [
{ 'resource': 'documents', 'actions': ['read'] },
{ 'resource': 'images', 'actions': ['read'] }
]
},
{
'scope': 'write:data',
'description': 'Write access to company data',
'permissions': [
{ 'resource': 'documents', 'actions': ['write'] },
{ 'resource': 'images', 'actions': ['write'] }
]
},
{
'scope': 'data:*',
'description': 'Full access to company data',
'permissions': [
{ 'resource': 'documents', 'actions': ['*'] },
{ 'resource': 'images', 'actions': ['*'] }
]
}
];
```
Stytch supports flexible naming conventions for scopes (e.g., `readonly:content`, `read:*`, and `content:readonly` are all allowed).
### What custom scopes can be granted?
The end user authorizing the application can only grant scopes that correspond to **permissions they themselves possess**:
* Permissions can be spread across **multiple roles**.
* Grantable scopes are determined by checking if the union of the user's roles covers all required permissions.
**Example:**
* Scope `write:data` requires `write` permission on both `documents` and `images`
* End User Roles:
* `document_manager` ➔ grants `write` on `documents`
* `image_uploader` ➔ grants `write` on `images`
✅ Scope is **grantable** because combined roles satisfy all requirements.
**Unmet permissions** will result in the scope being filtered out during the OAuth consent flow.
### Consent screen customization
By default, the consent screen will show the `description` of each scope being requested. The consent screen can be tailored using the `getIDPConsentManifest` SDK callback. Use this callback to group scopes by logical domain, provide additional documentation, or otherwise customize the language displayed to the end user.
```javascript expandable theme={null}
const isProfileScope = (scope) => scope === 'openid' || scope === 'email' || scope === 'profile';
const isDataScope = (scope) => !isProfileScope(scope);
const getScopeDescription = (scope) => {
// ...
};
const getManifest = ({ scopes, clientName }) => [
{
header: `${clientName} wants to view your Profile`,
items: [
{
text: 'View information stored in your Profile about your user.',
details: scopes.filter(isProfileScope).map(getScopeDescription),
},
],
},
{
header: `${clientName} wants to access your Data`,
items: scopes.filter(isDataScope).map(getScopeDescription),
},
];
```
This allows branding-friendly and understandable displays of scope groupings to end users.
### Access Token Scope Validation
Many Access Token JWT libraries will check for a particular scope within the token before letting the call proceed. While this approach is satisfactory for many applications, applications with complex RBAC requirements should check for the *underlying permission* that the scope grants instead. Authorizing at the permission level instead of the scope level creates a level of abstraction that makes it easier to introduce new scopes, or change the permissions attached to a scope over time.
For example, both the `write:data` and the `data:*` scope grant permission to write to a document. Checking for the underlying permission makes it easier to add new scopes (e.g. `documents:*`) in the future without requiring application code changes.
Use the Stytch SDK [Authenticate Access Token](/docs/api-reference/b2b/api/connected-apps/methods/authenticate-access-token-local) method:
```typescript theme={null}
stytch.idp.introspectTokenLocal({
access_token: 'eyJ...',
authorization_check: {
resource: 'documents',
action: 'write'
}
});
```
If the token lacks the required permission, this call will fail.
# Stytch Connected Apps
Source: https://stytch.com/docs/connected-apps/overview
Enable your application to become an OAuth and OIDC Authorization server.
Connected Apps is a Stytch product that enables your application to become an OAuth and OIDC Authorization server. Being an Authorization Server means that your Stytch-powered app can safely and securely share user data with other applications or services. This enables users of your Stytch app to use their in-house tools, external integrations, desktop apps, AI agents, or any other compatible tools to access data stored in your Stytch application.
### **OAuth and OIDC**
[OAuth](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13) (open authorization) is a framework of protocols that allow an end user to grant websites or applications access to the user's account data, without sharing critical account credentials like passwords. OAuth defines procedures for clients to obtain access tokens from an authorization server with the consent of the end user. OAuth also defines protocols for clients to present these access tokens to resource servers to access protected resources.
[OIDC](https://openid.net/specs/openid-connect-core-1_0.html) (OpenID Connect) is an additional identity layer built on top of the OAuth framework. OIDC defines mechanisms that enable applications to verify the identity of the end user and view profile information such as their email address. Because OIDC further standardizes OAuth, it is a preferred integration approach for many off-the-shelf tools. Many OAuth authorization servers are also OIDC authorization servers.
Learn more about OAuth [here](/docs/connected-apps/oauth-learn-more/oauth-basics), including important terminology and more about OAuth grant types.
### **Implementing OAuth with Stytch Connected Apps**
Unlike most CIAM providers, rather than requiring you to use a separate authorization server, Stytch provides a set of APIs, SDKs, and UI components that you can use to easily build your own authorization server. This approach means that the user experience feels like your users never leave your application rather than navigating to a separate site with its own design and user experience idioms. This provides reassurance to users who might otherwise feel uncertain about taking sensitive actions with a third party (like providing authentication credentials). Additionally, you can customize different parts of the flow to use your own domain, codebase, UI components, etc.
Specifically, your app is responsible for hosting the [Authorization Endpoint](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#name-authorization-endpoint). The Authorization Endpoint is a web page that the end-user's User Agent is redirected to by the client. For example, the client app might have a `"Log in with Your App"` button that redirects the user to your endpoint. The web page validates that the user is logged in, and if necessary, prompts them for consent to share their data. After the user responds, the web page redirects the user back to the client with an authorization code. The client can then exchange the authorization code for an access token for future use.
The Authorization Endpoint UI experience can be built using Stytch's Frontend SDKs directly embedded in your app or with Stytch's API endpoints for complete control over the interaction.
### **Demo Application Quickstarts**
Stytch maintains several MCP Server Connected App demos in the [Demo Monorepo](https://github.com/stytchauth/mcp-examples) for convenient testing and access. Learn how Connected Apps can be used in a variety of languages and runtimes.
## **What's next**
Read on for next steps:
* Get started with the [Frontend SDKs](/docs/connected-apps/build-login-flow/login-flow)
* Learn about the different [types of clients](/docs/connected-apps/oauth-learn-more/client-types) that can be used
* Define [custom scopes and resources](/docs/connected-apps/oauth-learn-more/oauth-scopes) to control how data is shared with clients
* Log users in with other commonly used IdPs such as Google and Microsoft ActiveDirectory with OAuth [multi-tenant](/docs/multi-tenant-auth/authentication/oauth/overview) and [consumer](/docs/consumer-auth/authentication/oauth/overview) applications
* Learn more about Machine-to-Machine authentication with M2M for [multi-tenant](/docs/multi-tenant-auth/authentication/m2m/authenticate-client) and [consumer](/docs/consumer-auth/authentication/m2m/authenticate-client) applications
# Consent management
Source: https://stytch.com/docs/connected-apps/resources/consent-management
Learn how consent is requested, tracked, enforced, and revoked using Stytch.
Stytch's Connected Apps infrastructure allows your application to serve as an Identity Provider (IdP), letting end users authenticate with first-party and third-party applications using OAuth. Consent management is a critical part of this flow, ensuring secure and auditable access control across users and applications.
## **Consent**
Consent is the explicit authorization an end user gives to a Connected App to access specific data or perform actions on their behalf. This happens during the OAuth authorization flow and is recorded and enforced by Stytch.
Explicit consent is not always required for an application to receive access to an end user's data. The rules for when consent can be bypassed are different for First Party and Third Party clients.
### **First Party Client Consent**
In Stytch's Connected Apps model, first-party applications are those that are developed and managed by you, the developer. These apps are considered **trusted by default**, and the consent flow is typically streamlined to reduce unnecessary friction. Consent is often not required for first-party applications, depending on your configuration and scopes. However, the consent management endpoints can still be used to view how an end user's data is shared and to revoke access and refresh tokens issued to a particular client.
### **Third Party Client Consent**
Third-party applications are external services not developed or maintained by you. Because these apps introduce new trust boundaries, Stytch enforces stricter consent and policy checks to ensure data is shared securely. These apps are considered **untrusted by default** and an end user must grant access via a consent screen before tokens can be issued. After consent has been granted, future OAuth flows will skip the consent screen unless the set of scopes being requested changes.
### **Shared Consent Requirements**
Certain OAuth flows will always require consent, regardless of the client type or whether consent has been previously granted. The scenarios where consent is always required are as follows:
1. The OAuth request passes the `?prompt=consent` query parameter, which always forces a consent screen even if one wouldn't normally be shown.
2. The OAuth request asks for the `offline_access` scope in order to retrieve a `refresh_token`. Requiring consent to obtain a `refresh_token` is recommended by the [OIDC spec](https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess). This case can be bypassed by enabling the `Bypass user consent for offline_access scope` flag on that client's page in the Dashboard. This flag is only available for first-party applications.
3. The `redirect_uri` uses a non`https://` scheme, such as `myapp://oauth-callback`. Explicit consent and user interaction is required for custom schemes to avoid [impersonation of native apps](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.3.1). Developers are recommended to make use of schemes such as Apple's [Universal Links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/) or Android's [App Links](https://developer.android.com/training/app-links) instead of custom schemes when possible.
4. The `redirect_uri` uses a loopback address like `127.0.0.1` or `[::1]`, which are similarly at risk for impersonation.
## Granting Consent
End users are prompted to grant consent through the `` UI Component for B2B applications or the `` UI Component for Consumer applications.
This screen presents:
* The application name and branding
* The scopes (permissions) being requested
* A binary choice to approve or deny the request
Once explicitly granted, Consent is tracked and future login attempts will proceed automatically when possible.
## Managing Consent
### **Managing Individual User/Member Access**
Connected App consent grants can be managed programmatically.
Use the [Get Connected Apps](/docs/api-reference/b2b/api/connected-apps/application-management/get-connected-app) endpoint to retrieve all Connected Apps a member has authorized. Unwanted grants can then be revoked programmatically via the [Revoke Connected App](/docs/api-reference/b2b/api/connected-apps/consent-management/revoke-connected-app-access-to-member) endpoint
Developers can also view and manage consent grants at both the member and the organization level from the Dashboard UI.
Use the [Get Connected Apps](/docs/api-reference/consumer/api/connected-apps/consent-management/get-connected-apps-user) endpoint to retrieve all Connected Apps a user has authorized. Unwanted grants can then be revoked programmatically via the [Revoke Connected App](/docs/api-reference/consumer/api/connected-apps/consent-management/revoke-connected-app-access) endpoint
Developers can also view and manage consent grants for a user from the Dashboard UI.
When a consent grant has been deleted, all access and refresh tokens issued to that connected app on behalf of the user/member will be revoked. A new OAuth flow must be completed in order to receive a new set of tokens.
### **Organization App Policies**
Only applicable for multi-tenant auth uses of Connected Apps
Admins of B2B organizations can configure an organization-wide app access policy under Connected Apps settings with the following options:
* **Allow all Connected Apps** – All apps may be authorized by members.
* **Restrict to an allowlist** – Only apps explicitly approved by the admin can be authorized.
* **Deny all Connected Apps** – Prevent all third-party apps from being used within the org.
Separate policies can be configured for first-party and third-party clients. For example, an organization can allow all first-party applications while restricting third-party applications to an allowlist.
These policies are driven by the following fields on the [Organization](/docs/api-reference/b2b/api/connected-apps/consent-management/get-connected-apps-organization) object:
* `first_party_connected_apps_allowed_type`
* `allowed_first_party_connected_apps`
* `third_party_connected_apps_allowed_type`
* `allowed_third_party_connected_apps`
# Custom domain
Source: https://stytch.com/docs/connected-apps/resources/custom-domains
Configure Stytch to use your custom domain.
## **Why domains matter in OAuth**
In OAuth and OpenID Connect (OIDC), many core features rely on a single authoritative domain.
The most important of these is the **issuer** — a required field in Access and ID token JWTs that uniquely identifies your Stytch application as the entity that created the token. Consumers of these JWTs use the issuer during validation to ensure the token's authenticity.
Another key feature is the set of **well-known metadata URLs**, which OAuth clients use to automatically discover server configuration. These endpoints expose metadata about available capabilities and supported flows.
* For OAuth: `https:///.well-known/oauth-authorization-server`
* For OIDC: `https:///.well-known/openid-configuration`
Stytch will automatically provision a default domain for your project upon creation. Default domains are intended for convenient testing of an initial integration. However, this domain is frequently shared with external consumers of your application. We recommend setting up a [Branded Custom Domain](/docs/resources/branding/custom-domains) as a subdomain of your main website before going live with your integration.
## **Configuring Stytch SDKs to use your domain**
Stytch SDKs must be configured to use the correct domain for your project.
For B2B applications:
```javascript Node SDK theme={null}
const client = new stytch.B2BClient({
project_id: "PROJECT_ID",
secret: "SECRET",
custom_base_url: "${projectDomain}",
});
```
```python Python SDK theme={null}
client = B2BClient(
project_id="PROJECT_ID",
secret="SECRET",
custom_base_url="${projectDomain}",
)
```
```ruby Ruby SDK theme={null}
client = StytchB2B::Client.new(
project_id: "PROJECT_ID",
secret: "SECRET"
env: "${projectDomain}",
)
```
```go Go SDK theme={null}
stytchClient, err := b2bstytchapi.NewClient(
"PROJECT_ID",
"SECRET",
b2bstytchapi.WithBaseURI("${projectDomain}")
)
```
```javascript Frontend Javascript SDK theme={null}
const client = createStytchB2BClient(
"PUBLIC_TOKEN",
{
customBaseUrl: "${projectDomain}",
}
);
```
For Consumer applications:
```javascript Node SDK theme={null}
const client = new stytch.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
custom_base_url: "${projectDomain}",
});
```
```python Python SDK theme={null}
client = Client(
project_id="PROJECT_ID",
secret="SECRET",
custom_base_url="${projectDomain}",
)
```
```ruby Ruby SDK theme={null}
client = Stytch::Client.new(
project_id: "PROJECT_ID",
secret: "SECRET"
env: "${projectDomain}",
)
```
```go Go SDK theme={null}
stytchClient, err := stytchapi.NewClient(
"PROJECT_ID",
"SECRET",
stytchapi.WithBaseURI("${projectDomain}")
)
```
```javascript Frontend Javascript SDK theme={null}
const client = createStytchClient(
"PUBLIC_TOKEN",
{
customBaseUrl: "${projectDomain}",
}
);
```
## **How Custom Domains affect Connected Apps**
Stytch will dynamically determine JWT issuers and well-known URL responses based on the domain used to access the Stytch service.
### **Using the default project domain**
All Stytch projects are provisioned a unique default domain upon creation.
* In `test` environments, the domain will look like `https://{noun}-{verb}-{number}.customers.stytch.dev`.
* In `live` environments, the domain will look like `https://{noun}-{verb}-{number}.customers.stytch.dev`.
API calls occurring over this domain will use the domain value while processing the request.
For example, the Access token and ID token returned from this Exchange Authorization Code (for [B2B](/docs/api-reference/b2b/api/connected-apps/exchange-authorization-code), for [Consumer](/docs/api-reference/consumer/api/connected-apps/exchange-authorization-code)) call will have an `issuer` of `https://{noun}-{verb}-{number}.customers.stytch.dev`.
```bash theme={null}
curl -X POST https://{noun}-{verb}-{number}.customers.stytch.dev/oauth2/token \
-H 'Content-Type: application/json' \
-d '{
"client_id": "connected-app-test-d731954d-dab3-4a2b-bdee-07f3ad1be888",
"client_secret": "NHQhc7ZqsXJVtgmN2MXr1etqsQrGAwJ-iBWNLKY7DzJq",
"redirect_uri": "https://example.com/callback",
"grant_type": "authorization_code",
"code": "PvC5UudZ7TPZbELt95yXAQ-8MeEUCRob8bUQ-g52fIJs"
}'
# => { "access_token": "eyJ...", "id_token": "eyJ...", ... }
```
Similarly, the metadata returned from this Get OpenID Configuration (for [B2B](/docs/api-reference/b2b/api/connected-apps/configuration/get-openid-configuration), for [Consumer](/docs/api-reference/consumer/api/connected-apps/configuration/get-openid-configuration)) call will use the `https://{noun}-{verb}-{number}.customers.stytch.dev`:
```bash theme={null}
curl --request GET \
--url https://{noun}-{verb}-{number}.customers.stytch.dev/.well-known/openid-configuration
```
like so:
```bash theme={null}
{
"registration_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/register",
"token_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/token",
"userinfo_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/userinfo",
...
}
```
### **Using a custom auth domain**
When you configure a [custom auth domain](/docs/resources/branding/custom-domains) (e.g. `https://login.yourcompany.com`) and make API calls using that domain, Stytch will use that branded domain instead.
The Access token and ID token returned from this call will have an `issuer` of `https://login.yourcompany.com`.
```bash theme={null}
curl -X POST https://login.yourcompany.com/oauth2/token \
-H 'Content-Type: application/json' \
-d '{
"client_id": "connected-app-test-d731954d-dab3-4a2b-bdee-07f3ad1be888",
"client_secret": "NHQhc7ZqsXJVtgmN2MXr1etqsQrGAwJ-iBWNLKY7DzJq",
"redirect_uri": "https://example.com/callback",
"grant_type": "authorization_code",
"code": "PvC5UudZ7TPZbELt95yXAQ-8MeEUCRob8bUQ-g52fIJs"
}'
# => { "access_token": "eyJ...", "id_token": "eyJ...", ... }
```
Similarly, the metadata returned from this call will use the `https://login.yourcompany.com` domain:
```bash theme={null}
curl --request GET \
--url https://login.yourcompany.com/.well-known/openid-configuration
```
like so:
```bash theme={null}
{
"registration_endpoint": "https://login.yourcompany.com/v1/oauth2/register",
"token_endpoint": "https://login.yourcompany.com/v1/oauth2/token",
"userinfo_endpoint": "https://login.yourcompany.com/v1/oauth2/userinfo",
...
}
```
## Migrating from **api.stytch.com**
If you were an early customer of Connected Apps, you may be accessing the product over `api.stytch.com` - e.g. `https://api.stytch.com/v1/public/${projectId}/oauth2/token`. When accessed this way, Stytch will use the project ID (`stytch.com/$projectId`) as the issuer. These issuers are not [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) compliant, as they are not HTTPS URLs.
### **Why does this matter?**
* Many OIDC clients and libraries will reject tokens if the `issuer` does not match the expected `https` URL.
* Using a custom domain ensures maximum compatibility and security for your integrations.
### **To get a fully OIDC-compliant issuer:**
1. [Set up a custom domain (CNAME)](/docs/resources/branding/custom-domains) in your DNS that points to Stytch and configure Stytch to recognize this domain.
2. [Configure your application](#Configuring-Stytch-SDKs-to-use-your-domain) to make all API calls to Stytch over this domain.
3. Use this domain for all authentication flows.
All Stytch backend SDKs will accept both the old and new format issuers when performing JWT validation to ensure a seamless transition.
# Testing integrations
Source: https://stytch.com/docs/connected-apps/resources/testing-integrations
Ensure your Stytch-powered app is functioning properly as an Authorization Server.
In this guide, you'll learn how to test that your Stytch-powered app is functioning properly as an Authorization Server. We will perform a complete OAuth 2.0 / OIDC Authorization Code flow using the [OAuth Debugger](https://oauthdebugger.com/). The OAuth Debugger will function as a **Connected App** - it will build an authorization request to your application and assist in obtaining access tokens.
## Pre-requisites
Before testing, follow one of the Getting Started or Integration guides to create your initial integration. You should have implemented an **Authorization URL** within your application that handles the OAuth Consent flow.
The Authorization URL page requires the user to already be logged in to your app; redirect unauthenticated users to your login flow before rendering the consent screen
## Test your Integration
Create a new Connected App client for use with the OAuth Debugger.
1. Navigate to the Connected Apps page in the [**Dashboard**](https://stytch.com/dashboard/connected-apps) and click `Add Application`.
2. Follow the wizard to create a new `Third Party application` using `Public` authentication.
* You can also create a `First Party` application, which may skip the consent screen. See the [**Consent Guide**](/docs/connected-apps/resources/consent-management) for details.
3. Name the client "OAuth Debugger" and add `https://oauthdebugger.com/debug` as a Login Redirect URL.
4. Save your changes. You can leave the other fields as-is for now.
5. Copy the `client_id` for use in the next step.
While you are in the Dashboard, it is good to verify that the **Authorization URL** is configured correctly:
1. Navigate to the `Settings` tab.
2. Ensure there is a valid URL in the `Authorization URL` input.
Open the [OAuth Debugger](https://oauthdebugger.com/) and fill in:
* **Authorize URI**: Your app's **Authorization URL** (this is **your page** within your application). Example: `https://example.com/oauth/authorize`.
* **Redirect URI**: `https://oauthdebugger.com/debug`.
* **Client ID**: the Connected App's `client_id` from Step 1.
* **Scope**: Start with `profile email` for now.
* **State**: Leave the default value for now.
* **Nonce**: Leave the default value for now.
* **Response Type**: Leave as `code`.
* **Use PKCE?**: Yes.
* Make sure `SHA-256` is selected.
* Use the default Code Verifier and Code Challenge.
* Token URI: Your project's unique Token Endpoint (for [B2B](/docs/api-reference/b2b/api/connected-apps/exchange-authorization-code), for [Consumer](/docs/api-reference/consumer/api/connected-apps/exchange-authorization-code)).
* Should be of the form `https://${projectDomain}/v1/oauth2/token`.
* You can find your Project Domain on the Project Overview page in the [**Dashboard**](https://stytch.com/dashboard).
* **Response mode**: Leave as `query`.
Click **Send Request**. You'll be redirected to your Authorization URL, the user will be prompted for [**consent**](/docs/connected-apps/resources/consent-management) when applicable, and then redirect back to OAuth Debugger with a `code`.
The OAuth Debugger should automatically exchange the `code` that was returned for a set of tokens. You should see an `access_token` JWT in the response. Copy the access token and paste it into a JWT decoder such as [jwts.dev](http://jwts.dev).
You should be able to validate the following:
* **Scopes**: The `scope` claim in the access token should be what you initially requested.
* **Issuer**: The `iss` claim should be your unique Stytch project domain.
* **Audience**: The `aud` claim should be your unique Stytch project ID.
* **Subject**: The `sub` claim should be the ID of the Stytch member that granted access.
You can vary the parameters in the OAuth debugger to simulate different scenarios:
* **ID Tokens**: When the `openid` scope is requested, an `id_token` will be returned in the response.
* **Refresh Tokens**: When the `offline_access` scope is requested, a `refresh_token` will be returned in the response.
* **Custom and other built-in scopes**: If your project defines any [Custom Scopes](/docs/connected-apps/oauth-learn-more/oauth-scopes#custom-scopes), you may test them here as well.
## Troubleshooting
* **Redirect mismatch**\
Ensure the exact oauthdebugger redirect `https://oauthdebugger.com/debug`) is registered on the Connected App. Mismatches will fail validation.
* **Consent not shown**\
First-party clients may skip explicit consent depending on configuration; third-party clients always show consent. Refer to the [Consent Guide](/docs/connected-apps/resources/consent-management) for details on when consent is required.
* **PKCE errors**\
If the token exchange fails due to PKCE, confirm that the `code_verifier` corresponds to the original `code_challenge` `S256`).
# Custom auth domains
Source: https://stytch.com/docs/resources/branding/custom-domains
Use your custom domain for Stytch assets and API calls.
## What is a custom auth domain?
When using Stytch as your authentication provider, your users' browsers will make API calls and request assets that are handled by Stytch's servers. While it is often workable to have these requests go directly to Stytch, it can be desirable and sometimes necessary to serve these requests from your own domain.
## Why use a custom auth domain?
Many Stytch-supplied URLs are shared externally with your customers and partners. Using your own domain allows you to abstract away the fact that you are using Stytch.
We recommend using a custom auth domain for for various use cases, including:
* OAuth, which allows you to display your own domain during the OAuth flow.
* Fraud protection, which is more likely to be blocked by ad blockers when using the `stytch.com` domain.
* Connected Apps, which may not function properly with all third party software without using a custom domain.
Learn more about how custom auth domains interact with our products [below](#stytch-products-that-use-custom-auth-domains).
## Default project domains
Stytch will automatically provision a default project domain for your project upon creation. Default project domains are intended for convenient testing of an initial integration and can be used to test some features that require a unique domain, such as Connected Apps. However, they are still linked to the Stytch brand and website, and cannot be used to test features that require a domain linked to your application, like HttpOnly Cookies.
* In `test` environments, the default domain looks like `https://{noun}-{verb}-{number}.customers.stytch.dev`.
* In `live` environments, the default domain looks like `https://{noun}-{verb}-{number}.customers.stytch.com`.
You can view your Stytch project's default domain on the [Project overview](https://stytch.com/dashboard) page in the Stytch Dashboard.
## Setting up a custom auth domain
We recommend setting up a custom auth domain as a subdomain of your main website early in your integration to avoid a migration if you later start using a feature that requires them.
**Pre-existing implementations**
If you have a pre-existing Stytch implementation, setting up a custom domain will change the behavior of some Stytch products.
We recommend carefully reviewing the [product-specific behavior](#stytch-products-that-use-custom-auth-domains) below in order to avoid any downtime or unintended consequences.
In order to configure a custom domain, there are two sources that you will need to update:
* You'll need to add a new CNAME record to your DNS setup. This will create a subdomain on your site that acts as an alias for a subdomain of `stytch.com`.
* You'll need to add this subdomain to your Stytch configuration.
It's best to approach this process by doing both steps in parallel.
Go to [Custom Domains](https://stytch.com/dashboard/custom-domains) in the Stytch Dashboard.
Click the "+ Add New" button. Take note of the URL provided, `{SUBDOMAIN}.stytch.com`, as we will need it for the next step.
A CNAME record in your domain should be updated to point to this URL.
Stytch custom domains cannot use the same subdomain across the different environments in your Stytch project.
The process for this varies by DNS provider, but most providers will provide instructions. Here are instructions for some popular providers:
* [Amazon Web Services (Route 53)](https://aws.amazon.com/premiumsupport/knowledge-center/create-subdomain-route-53/)
* [Google Cloud Platform (Cloud DNS)](https://cloud.google.com/dns/docs/set-up-dns-records-domain-name#create_a_cname_record_for_the_www_subdomain)
* [Cloudflare](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-subdomain/)
* If you have [Zone Holds enabled](https://blog.cloudflare.com/protect-your-domain-with-zone-holds/), you will need to release it during this process to ensure that we can successfully issue certificates.
* [GoDaddy](https://www.godaddy.com/help/add-a-subdomain-4080)
* [Namecheap](https://www.namecheap.com/support/knowledgebase/article.aspx/9646/2237/how-to-create-a-cname-record-for-your-domain/)
Finish the configuration process in Stytch by entering your full domain in the final field of the dialog box and clicking "Verify".
DNS records may take up to 48 hours to propagate and be verified; however, usually this happens within minutes to hours rather than days. If you are able to verify the existence of your DNS changes exist via a `dig` on your machine, but they haven't yet been verified with Stytch after an hour, please reach out to [support@stytch.com](mailto:support@stytch.com) and we can help troubleshoot.
## Configuring Stytch SDKs to use a custom auth domain
Stytch SDKs must be configured to use your custom auth domain after it is set up.
For Consumer auth:
```javascript Node SDK theme={null}
const client = new stytch.Client({
project_id: "{user.project?.id}",
secret: "{user.project?.secret}",
custom_base_url: "{user.project?.domain}",
});
```
```python Python SDK theme={null}
client = Client(
project_id="{user.project?.id}",
secret="{user.project?.secret}",
custom_base_url="{user.project?.domain}",
)
```
```ruby Ruby SDK theme={null}
client = Stytch::Client.new(
project_id: "{user.project?.id}",
secret: "{user.project?.secret}"
env: "{user.project?.domain}",
)
```
```go Go SDK theme={null}
stytchClient, err := stytchapi.NewClient(
"{user.project?.id}",
"{user.project?.secret}",
stytchapi.WithBaseURI("{user.project?.domain}")
)
```
```javascript Frontend JavaScript SDK theme={null}
const client = createStytchClient(
"{user.project?.publicToken}",
{
customBaseUrl: "{user.project?.domain}",
}
);
```
For B2B auth:
```javascript Node SDK theme={null}
const client = new stytch.B2BClient({
project_id: "{user.project?.id}",
secret: "{user.project?.secret}",
custom_base_url: "{user.project?.domain}",
});
```
```python Python SDK theme={null}
client = B2BClient(
project_id="{user.project?.id}",
secret="{user.project?.secret}",
custom_base_url="{user.project?.domain}",
)
```
```ruby Ruby SDK theme={null}
client = StytchB2B::Client.new(
project_id: "{user.project?.id}",
secret: "{user.project?.secret}"
env: "{user.project?.domain}",
)
```
```go Go SDK theme={null}
stytchClient, err := b2bstytchapi.NewClient(
"{user.project?.id}",
"{user.project?.secret}",
b2bstytchapi.WithBaseURI("{user.project?.domain}")
)
```
```javascript Frontend JavaScript SDK theme={null}
const client = createStytchB2BClient(
"{user.project?.publicToken}",
{
customBaseUrl: "{user.project?.domain}",
}
);
```
## Stytch products that use custom auth domains
Custom domains can have a far-reaching impact on how your app interacts with Stytch. These are some of our products that benefit from a custom domain:
### Stytch-issued JWTs
Several Stytch products - Sessions, M2M, and Connected Apps - issue JSON Web Tokens (JWTs) for local validation. Stytch will determine the JWT issuer claim from the domain used to access the Stytch backend.
* **With a default domain:** JWTs issued by Stytch when called via your default domain (e.g. `https://{noun}-{verb}-{number}.customers.stytch.dev`) as the issuer.
* **With a custom domain (CNAME):** JWTs issued by Stytch when called via your custom domain (e.g., `https://login.yourcompany.com`) as the issuer.
* **Without a custom domain:** JWTs issued by Stytch when called via the public API (e.g. `https://test.stytch.com/public/${project_id}`) will use `stytch.com/$project_id` as the issuer.
Use the same domain across all Stytch products for simpler JWT validation logic. For example, if you share the `https://login.yourcompany.com/v1/oauth2/token` endpoint externally for Connected Apps API calls, Connected App access tokens will have an issuer of `https://login.yourcompany.com`. If you use the `https://api.stytch.com` domain for backend API calls, then Session JWTs will have an issuer of `stytch.com/$project_id`, which will make validation logic more difficult.
All Stytch SDKs performing JWT validation will honor both `stytch.com/$project_id` issuers and custom domain issuers, enabling you to switch from one to the other without downtime. Other libraries or 3rd party tools may not support the issuer changing without reconfiguration.
### HttpOnly cookies
The HttpOnly cookie option in our frontend JavaScript SDK can only be used once you set up a custom auth domain.
Magic links in emails can also be configured to use your custom domain in the [Branding](https://stytch.com/dashboard/templates) tab in the Dashboard. When enabled, the URL of the link sent in Magic Link emails will use your custom domain instead of Stytch's API domain.
### OAuth
For some OAuth providers (such as Google), the domain used for logging in must be authorized by the provider in order to properly display your app's [consent screen](https://developers.google.com/identity/protocols/oauth2/production-readiness/brand-verification#oauth-consent-screen). In order for the consent screen to indicate that it is returning to your app instead of to `stytch.com`, the redirect URI provided to the login flow must be owned by your authorized domain.
As soon as you set up a custom domain, Stytch will start using it in the OAuth callback URL used during the OAuth flow.
If you have existing [OAuth providers](https://stytch.com/dashboard/oauth) set up, you will need to add the new version of your OAuth callback URL with your custom domain to the redirect URI allowlist in each OAuth provider’s developer dashboard. Otherwise, your OAuth requests will fail.
### Single Sign-On (SSO)
Single Sign-On flows support custom auth domains. Since SSO endpoints are exposed to the user, using a custom domain allows you to associate these endpoints with your brand and obfuscate Stytch's role as an auth provider.
Any SSO Connection created *after* successfully setting up a custom domain will use the custom domain. Any SSO Connections created *before* setting up a custom domain will not be modified and will continue using the Stytch domain.
### Fraud protection
Our [Device Fingerprinting product](/docs/fraud-risk/device-fingerprinting/overview) is more effective in various ways when it is hosted from your own domain. Notably, the chances that Device Fingerprinting will be blocked by an ad blocker decrease significantly when using a custom domain.
### Connected Apps
Stytch's Connected Apps feature enables Stytch-powered Applications to act as the Identity Provider (IdP) for OAuth and OIDC compliant applications. Stytch recommends using a custom domain when implementing Connected Apps for various reasons:
1. OAuth endpoints like `/oauth2/token` or `.well-known/jwks.json` are often shared externally with customers or third parties. Using a custom domain allows you to associate these endpoints with your brand, (e.g. `https://auth.yourcompany.com/v1/oauth2/token` instead of `https://api.stytch.com/v1/public/${project_id}/oauth2/token`) and avoid needing to update callers if you stop using Stytch in the future.
2. Certain specifications within the OAuth and OIDC family of protocols require a unique domain to be used. For example, OIDC-compliant Issuers must be `https://` URLs without query or path parameters. Legacy issuers from Stytch's Session JWT product (`stytch.com/${project_id}`) will not be accepted by certain software.
More on configuring custom domains for Connected Apps [here](/docs/connected-apps/resources/custom-domains#using-a-custom-auth-domain).
## Next steps
Visit the [Custom Domains](https://stytch.com/dashboard/custom-domains) page in the Dashboard to set up your custom domain.
For questions, reach out to [support@stytch.com](mailto:support@stytch.com).
# Custom email domains
Source: https://stytch.com/docs/resources/branding/custom-email-domains
Use your custom domain to send Stytch emails.
Custom email domains require purchasing the **Full email customization and Stytch brand removal** add-on. See Stytch's [Pricing page](https://stytch.com/pricing) for more details.
## What is a custom email domain?
Setting up a custom email domain allows you to send Stytch authentication emails to your users from your own custom email address.
If you do not set up a custom email domain, emails sent by Stytch to your users (for instance, when using Email Magic Links) will be sent from a Stytch email address (e.g. `login@stytch.com`).
Delivering emails from your own domain can help ensure consistent branding, but it does come with some risks.
Stytch maintains a [strong email domain reputation](/docs/resources/policies/messaging/deliverability/email-deliverability) and sends enough emails per month to ensure that email providers, like Gmail and Outlook, land our login emails in your user's inbox quickly and reliably. If you use a custom email domain, you will need ensure that your domain also holds a strong reputation and sends enough email volume (a good rule of thumb is 100,000 login emails per month) so that email providers do not flag emails from your domain as spam.
## Setting up a custom email domain
You can configure a custom email domain via the [Custom domains](https://stytch.com/dashboard/custom-domains) page in the Stytch Dashboard.
See the [Custom email templates](/docs/resources/branding/custom-email-templates) resource for additional setup instructions.
# Custom email templates
Source: https://stytch.com/docs/resources/branding/custom-email-templates
Set up custom email templates to use in your Stytch authentication requests.
Custom email templates require purchasing the **Full email customization and Stytch brand removal** add-on. See Stytch's [Pricing page](https://stytch.com/pricing) for more details.
## Setting up custom email templates
Complete the following steps to set up and use a custom email template in your Stytch requests.
Stytch's **Full email customization and Stytch brand removal** add-on is required in order to use custom email templates. You can enable this add-on in the **Optional Add-Ons** section of [the Billing page](https://stytch.com/dashboard/settings/billing) in the Stytch Dashboard.
Next, set up a custom email domain for sending Stytch emails by following the setup steps in the **Custom email domains** section of the [Stytch Dashboard.](https://stytch.com/dashboard/custom-domains).
Make sure that you set up a **Custom email domain**, rather than a **Custom auth domain**, which serves a different purpose.
Note that in order to protect our domain reputation, it is not possible to send custom email templates from the default Stytch sender domains. Learn more about [custom email domains here](/docs/resources/branding/custom-email-domains).
Now that your custom email domain is set up, you can create a custom email template by navigating to [the Branding tab](https://stytch.com/dashboard/templates) in the Stytch Dashboard and clicking **Create new template**.
In the **Template type** dropdown, select **Custom HTML template**.
You can now use the email template editor to edit the email subject line, the email body HTML, and the sender information such as the sender address, sender display name, and the reply-to address.
You can also insert variables such as the `magic_link_url`, `otp_code`, `user_email`, etc. into your template. Note that the available variables depend on the email template type and specific authentication flow, and not all variables are available in all contexts.
Alternatively, you can use our [Programmatic Workspace Actions endpoints](/docs/api-reference/pwa/api/v3/overview) or our [Terraform provider](https://registry.terraform.io/providers/stytchauth/stytch/latest/docs/resources/email%5Ftemplate) to create and manage your custom email templates.
Once you've finished creating your custom email template, you can instruct Stytch to use it by specifying its `template_id` in your authentication requests. Stytch endpoints that trigger an email include parameters like `login_template_id`, `signup_template_id`, and `reset_password_template_id` where you can specify the `template_id` of your custom email template.
Here are some example requests that specify a custom email template via the `login_template_id` parameter:
For [Send OTP by Email](/docs/api-reference/consumer/api/otp/via-email/send) in Consumer auth:
```sh cURL theme={null}
curl --request POST \
--url https://test.stytch.com/v1/otps/email/send \
-u '${projectId}:${secret}' \
-H 'Content-Type: application/json' \
-d '{
"email": "email@example.com",
"login_template_id": "yourCustomEmailTemplateId"
}'
```
```javascript Frontend JavaScript SDK theme={null}
stytchClient.otps.email.send('email@example.com', {
login_template_id: 'yourCustomEmailTemplateId',
});
```
For [Send Discovery Email OTP](/docs/api-reference/b2b/api/email-otp/discovery/send-discovery-otp) in B2B auth:
```sh cURL theme={null}
curl --request POST \
--url https://test.stytch.com/v1/b2b/otps/email/discovery/send \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email_address": "email@example.com",
"login_template_id": "yourCustomEmailTemplateId"
}'
```
```javascript Frontend JavaScript SDK theme={null}
stytchClient.otps.email.discovery.send({
email_address: 'email@example.com',
login_template_id: 'yourCustomEmailTemplateId'
});
```
You can also set a custom email template as the **default template** for its specific template type. When an email template is set as the default, we'll send that template to the user if you don't specify a `template_id` in your request.
# Changelog
Source: https://stytch.com/docs/resources/changelog
Stytch product updates and annoucements
# Product updates and improvements
* The [SSO Start API endpoint](/docs/api-reference/b2b/api/sso/shared/start-sso-authenticate#param-provider-parameter-name) now supports `provider_` params in OIDC flows for forwarding parameters to the IdP, similar to OAuth flows.
* We now support browser session-lifetime cookies for Frontend SDK users with [`HttpOnly` cookies](/docs/multi-tenant-auth/manage-sessions/cookies#httponly-cookies) enabled. If interested, please [reach out to support](/docs/resources/get-support).
* We no longer support several additional countries for WhatsApp sending due to Meta requirements. Please refer to the [list of unsupported countries](/docs/resources/policies/messaging/unsupported-countries#whatsapp).
* Connected Apps now supports advertising `authorization_response_iss_parameter_supported` in the Authorization Server metadata, to comply with the 2026-07-28 MCP spec.
* The [B2B prebuilt UI](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/login/configuration#param-email-magic-links-options) now supports configuring email magic link expiration via the `loginExpirationMinutes`, `signupExpirationMinutes`, and `discoveryExpirationMinutes` parameters.
* The [prebuilt Admin Portal Member Management UI](/docs/api-reference/b2b/frontend-sdks/react/prebuilt-ui/admin-portal/member-management#param-invite-expiration-minutes) now supports configuring email magic link expiration via the `inviteExpirationMinutes` parameter.
* [Custom claim templates](/docs/multi-tenant-auth/manage-sessions/custom-claims#custom-claim-templates) now support `OR` operators, for example `"userID": {{ member.external_id || member.member_id }}` for both Consumer and B2B auth.
# New languages for SMS and WhatsApp OTP messages
We've added the following new language options for our SMS and WhatsApp OTP messages:
* Japanese (`ja`)
* Russian (`ru`)
* Dutch (`nl`)
* Polish (`pl`)
* Turkish (`tr`)
* Persian (`fa`)
* Vietnamese (`vi`)
* Czech (`cs`)
In order to specify which language an SMS or WhatsApp OTP message should be sent in, set the `locale` parameter to your desired language code when making the OTP request.
If you'd like to request support for additional langauges, please [reach out to us](/docs/resources/get-support)!
# Email Risk beta & update to Event Log Streaming
## Email Risk API in beta
Email addresses reveal information that is useful for fraud detection. For example, fraudsters often use disposable email addresses or "tumbling" to sign up for multiple accounts and exploit trials or giveaways.
With Stytch Email Risk, you get high-confidence email and domain signals with recommended actions, so that you can block bad signups at the front door.
### How it works
Call the Check Email Risk API with the email address you want to check: [https://stytch.com/docs/fraud/api/check-email-risk](https://stytch.com/docs/fraud/api/check-email-risk)
Stytch will return a recommended action (`ALLOW`, `BLOCK`, or `CHALLENGE`), a risk score, and detailed information about the email address and domain.
### Getting started
To get access to the beta, reach out to us at [fraud-team@stytch.com](mailto:fraud-team@stytch.com) or [via Slack](/docs/resources/get-support) to get started.
## Event Log Streaming event changes
We'll be making two changes to our Event Log Streaming (ELS) feature starting on 2026-01-30.
### Removing Start events
Today, our ELS feature logs every API event twice; once when it starts, a Start event, and again when it finishes, as either a Success or an Error event. This means API events are effectively duplicated and your logging destination receives twice as many events as necessary, which can drive up your storage and processing costs.
On 2026-01-30 we'll be removing all Start events from our log stream. This will halve the number of incoming events to your destination.
If you currently use Start events in your alerting or monitoring, we recommend moving to the corresponding Success or Error events instead.
### Adding new frontend SDK events
On 2026-01-30 we'll also be adding in a new set of events specific to our frontend SDKs.
Previously we did not include Error events coming from our frontend SDKs but we'll now begin sending those Error events to your ELS destination.
If you're using one of our frontend SDKs, you should expect a small increase in volume for any errors that they generate.
# A new chapter begins: Stytch joins Twilio
It's official, Twilio's acquisition of Stytch is now final!
On October 30, we shared that Twilio was acquiring Stytch to combine our identity platform with Twilio's trusted communications infrastructure. As of today, the acquisition is officially complete.
That means Stytch is now part of the Twilio family. We're excited about what's ahead for our developer community, but first, we want to share a few key updates around data governance and the road forward.
## Data governance & privacy notice
With the acquisition now complete, Twilio Inc. is officially the parent company of Stytch, Inc. **This means Twilio is now responsible for overall data governance and compliance with global privacy regulations such as GDPR and CCPA.**
In practice:
* **Stytch, Inc. remains your contracted service provider** and day-to-day data processor for the Stytch platform.
* **Twilio Inc., as parent company, now holds ultimate oversight of data governance** across the combined organization.
* **You don't need to take any action,** but if your internal records list parent entities, you may want to note that Stytch Inc. is now part of Twilio Inc.
## What's next
As part of Twilio, we'll continue investing in the developer experience that defines Stytch, building on stronger infrastructure, broader reach, and new capabilities through Twilio's global network. Together, we're shaping what's next for identity and communications, helping you build secure, intelligent experiences for people and AI agents alike.
Thank you for being part of our journey. Your trust and partnership have brought us here, and we can't wait to keep building what's next — together.
**Reed McGinley-Stempel & Julianna Lamb**
Co-founders, Stytch
# Stytch is joining Twilio: same product, bigger mission
Today we're sharing some big news: [Twilio is acquiring Stytch](https://stytch.com/blog/stytch-joins-twilio). Our mission remains the same: to eliminate friction on the internet by helping developers build better identity experiences – for your users and, now, the AI agents acting on their behalf.
By bringing together Stytch's identity stack and agent-ready integrations with Twilio's trusted communication infrastructure and extensive reputation data, developers will have a stronger foundation to build and secure the next generation of AI-powered, context-aware applications.
In joining forces with Twilio, we expect to accelerate our roadmap and infuse even more intelligence into authentication – helping you secure the next generation of AI-driven applications.
## What this means for you
* **No immediate changes** to your contracts, pricing, SDKs, API keys, integrations or overall Stytch experience.
* **Greater scale.** Expect deeper fraud defenses, richer verification options, and more global coverage by combining Stytch's identity stack with Twilio's infrastructure and data.
* **Continued investment in cutting edge identity products.** We're deepening our focus on building for the future where humans and agents both need to be authenticated and authorized. We'll continue to stay one step ahead of emerging trends and protocols so you can easily adapt to the future of agents, MCP, and whatever comes next.
## What we'll be able to offer with Twilio
* **Global, channel-aware identity.** Twilio's channels (Voice, Messaging, Email) + Stytch's auth and session tooling = **channel-aware identity** out of the box. Customers get verification, step-up, and recovery that just works worldwide.
* **Stronger fraud defenses.** Twilio's phone & email reputation signals paired with Stytch device intelligence mean you can **reduce false friction and detect abuse earlier**.
* **Built for the next interface.** We'll keep investing in agentic workflows, scoped tokens, granular permissions, and human-in-the-loop confirmations over your users' preferred channels.
## What's next
There's nothing you need to do.
As we roll out improvements, you'll be able to take advantage of those new features. We expect the transaction to close in mid-November, subject to customary closing conditions.
Thank you for building Stytch with us. Your feedback, trust, and collaboration over the past five years have been instrumental in shaping our journey. As we join forces with Twilio, we're excited to keep innovating and building the future of identity, together.
**Reed McGinley-Stempel & Julianna Lamb**
Co-founders, Stytch
# Support
Source: https://stytch.com/docs/resources/get-support
Learn about Stytch support resources and packages.
Join our community channel and speak directly with a Stytch auth expert.
Contact us at [support@stytch.com](mailto:support@stytch.com) with any questions.
# Support packages
All customers have access to our **Free** support tier. Our team is available to answer your questions about Stytch, help you design your integration, and troubleshoot issues.
For customers who need more robust support, we offer **Premium** and **Enterprise** support packages that offer dedicated Slack channels, priority support, and guaranteed response times. [Contact our Sales team](https://stytch.com/contact) if you're interested in upgrading your support package.
| | Free | Premium | Enterprise |
| - | :-: | :-: | :-: |
| Developer support Slack | ✅ | ✅ | ✅ |
| Email support | ✅ | ✅ | ✅ |
| Dedicated Slack channel | | ✅ | ✅ |
| Priority response times | | ✅ | ✅ |
| Support onboarding session | | ✅ | ✅ |
| Response time SLAs | | | ✅ |
| Named support | | | ✅ |
| Root cause analysis | As available | Reasonable efforts | All P1 incidents |
| Pricing | Free | \$1,000/month | Contact sales |
# Glossary
Source: https://stytch.com/docs/resources/glossary
Definitions of key terms and concepts
# Glossary
Comprehensive glossary of authentication, security, and Stytch-specific terms.
## A
**API (Application Programming Interface)**: A set of protocols and tools for building software applications.
**Authentication**: The process of verifying the identity of a user or system.
**Authorization**: The process of determining what actions an authenticated user is allowed to perform.
## B
**Biometrics**: Authentication method using unique biological characteristics like fingerprints or facial recognition.
## C
**Challenge**: An additional verification step required when a user's risk level is uncertain.
**Claims**: Statements about a user contained within a token (typically JWT).
## D
**Device Fingerprinting**: Technology that identifies devices based on their unique characteristics.
**DFP**: Device Fingerprinting Platform - Stytch's fraud detection system.
**Discovery**: Centralized login flow that allows users to view all Organizations they have access to, including pending invites and Organizations they are allowed to automatically join based on their verified email domain.
## E
**Enumeration**: An attack method attempting to discover valid user accounts.
## F
**Factor**: A category of authentication method (something you know, have, or are).
## I
**IdP**: Identity provider; a workforce application that allows companies to centrally manage their employees’ identity information as well as their access to company resources and applications.
## J
**JWT (JSON Web Token)**: A compact, URL-safe token format for securely transmitting information.
## M
**Magic Link**: A passwordless authentication method using time-limited URLs sent via email.
**MFA (Multi-Factor Authentication)**: Security process requiring multiple authentication factors.
## O
**OAuth**: An authorization protocol allowing third-party applications to access user resources.
**Org-specific**: Organization-specific login flow where end users go to a specialized login page for their tenant, often a subdomain or route that contains the Organization Slug (e.g. acme-corp.yourapp.com or yourapp.com/team/acme-corp).
**OTP (One-Time Password)**: A password valid for only one login session or transaction.
## P
**Passkey**: A passwordless authentication standard using cryptographic key pairs.
**PKCE (Proof Key for Code Exchange)**: Security extension for OAuth authorization code flow.
## R
**RBAC (Role-Based Access Control)**: Access control method based on user roles within an organization.
## S
**SCIM (System for Cross-domain Identity Management)**: Standard for automating user provisioning.
**Session**: A semi-permanent interactive information exchange between user and application.
**SSO (Single Sign-On)**: Authentication process allowing users to access multiple applications with one set of credentials.
## T
**TOTP (Time-based One-Time Password)**: Algorithm generating time-limited passwords.
**Token**: A piece of data representing authentication or authorization credentials.
## W
**WebAuthn**: Web standard for passwordless authentication using public key cryptography.
# Additional migration considerations
Source: https://stytch.com/docs/resources/migrations/additional-migration-considerations
Deploying a new authentication service is not always limited to just changing login and signup logic.
Deploying a new authentication service is not always limited to just changing login and signup logic. If your backend reads session and user data or listens to auth related events, you may need to refactor this logic to point to Stytch's APIs.
## Ingesting events and managing visibility
If your legacy auth provider requires you to set up log streams or webhooks for event visibility, here are a few examples of how to move that logic to Stytch.
With Stytch, there are multiple ways to gain access to authentication-related logs.
### Option 1: Use webhooks
You can enroll in webhooks through the Stytch Dashboard by specifying URL(s) at which you wish to receive the webhook POST requests and the type of events you wish to receive.
Check out our [webhooks guide](/docs/api-reference/consumer/api/overview) to learn how to keep your internal database records in sync with any changes that occur in Stytch's Consumer Authentication API.
```js theme={null}
// Example user create via dashboard (dashboard.user.create)
{
"project_id": "project-live-123-...",
"event_id": "event-live-123-...",
"action": "CREATE",
"object_type": "user",
"source": "DASHBOARD",
"id": "user-live-123...",
"timestamp": "2024-11-21T06:47:49.555247073Z",
"user": { ... }
}
// Example update user event via API or SDK call (direct.user.update)
{
"project_id": "project-live-123-...",
"event_id": "event-live-123-...",
"action": "UPDATE",
"object_type": "user",
"source": "DIRECT",
"id": "user-live-123...",
"timestamp": "2024-11-21T06:47:49.555247073Z",
"user": { ... }
}
```
### Option 2: Log calls and responses made to Stytch from within your own application
Frontend SDK example, using email magic links with JS:
```js theme={null}
const onSubmit: FormEventHandler = async (e) => {
if (isValidEmail(email)) {
const resp = await stytchClient.magicLinks.email.send(email);
// Send the response of the call to your logging tool
// This includes the user info and response code
if (resp.status === 200) {
Bugsnag.notify(resp);
setEMLSent(STATUS.SENT);
} else {
Bugsnag.notify(e);
setEMLSent(STATUS.ERROR);
}
}
};
```
Backend SDK/API example, using Python:
```python theme={null}
@app.route("/login_or_create_user", methods=["POST"])
def login_or_create_user() -> str:
resp = stytch_client.magic_links.email.login_or_create(
email=request.form["email"],
login_magic_link_url=MAGIC_LINK_URL,
signup_magic_link_url=MAGIC_LINK_URL,
)
# Send the response of the call to your logging tool
# This includes the user info and response code
if resp.status_code == 200:
bugsnag.notify(resp)
if resp.status_code != 200:
bugsnag.notify(resp)
print(resp)
return "something went wrong sending magic link"
return render_template("emailSent.html")
```
### Option 3: Check out the event logs in the dashboard
From the Activity section in the Dashboard, you can view the [Event Logs](https://stytch.com/dashboard/activity) to debug your authentication flows.
## Pre-production testing
If you run any automated or pre-production testing that includes authentication, here are a few best practices to move that to Stytch:
* If you have multiple pre-production environments that need their own user bases, use the Stytch Dashboard to create multiple test environments and have each set of test keys map to each of your internal environments.
* Stytch supports [sandbox values](/docs/api-reference/consumer/api/resources/sandbox-values) to use for most primary login factors. Check out our [E2E Testing resource](/docs/api-reference/consumer/api/resources/e2e-testing) for additional information.
## Reconciling deduplication
Stytch uses email or phone number as a primary key for identifying users. This means if you are using a combination of the Stytch Email Magic Links product and OAuth Logins then Stytch will automatically deduplicate users with matching email addresses. For example, if you create a user via Email Magic Links using the email address [example@stytch.com](mailto:example@stytch.com), and then later login with a Google account which is tied to [example@stytch.com](mailto:example@stytch.com), Stytch will treat these as the same user with the same `user_id`.
For this reason, you may need to merge your users' accounts before migrating to Stytch. If your script attempts to manually create two users in Stytch with the same email address, you will receive a `duplicate_email` error.
## Linking Stytch users back to your DB
You should continue to maintain your own internal user table in your application, and link Stytch's `user_id` in as a column in your table. You can also add your internal reference of the user as a `trusted_metadata` field on the Stytch user.
## Migrating over additional user data
Our `trusted_metadata` supports nested JSON and can be used to migrate and store any additional user data.
We recommend to keep this data limited to information that is required in the context of authentication, for example: user role or other claims.
**Keeping identifiers consistent**: If you have analytics or other internal processes that key off of your internal user ID reference, and would like these IDs to stay consistent, we recommend to add your internal user ID as `trusted_metadata`. Your backend should then default to reading ID from this value, but fall back to the Stytch `user_id` if it does not exist. Stytch does not currently support seeding user GUIDs.
## Email/phone verification status
Stytch manages the email and phone verification status of users. When a user is created in Stytch, it will have an `email.verified` or `phone.verified` value of `false` until the user completes their first login that verifies ownership of that account (for example an email OTP, SMS OTP flow or OAuth login).
See more information in our guides [here](/docs/consumer-auth/authentication/passwords/email-verification/overview).
If you have users who already have verified emails or phone numbers and would like to migrate those users with a verified status, call the [Migrate Password](/docs/api-reference/consumer/api/passwords/migrate) endpoint with the params `set_email_verified: true` and `set_phone_number_verified: true` respectively. If the user does not have a password in your existing auth system, you can pass in a dummy password hash, then [Delete](/docs/api-reference/consumer/api/users/delete/delete-user-password) it afterwards. Note that improperly setting email addresses or phone numbers as verified can open up a security gap in your application.
You will not be able to add additional factors onto new or migrated users unless their primary authentication factor is verified, to prevent account takeover attacks.
We strongly recommend to only migrate both factors for users who have verified ownership over both of these factors, in order to prevent account takeovers.
## Password strength requirement changes
See our Docs for password strength configuration [here](/docs/consumer-auth/authentication/passwords/strength-policy).
The default password strength policy is [zxcvbn](https://github.com/dropbox/zxcvbn), but Stytch offers configurability of this policy via the [Dashboard](https://stytch.com/dashboard/password-strength-config).
For those migrating users with passwords, our recommendations are to:
1. Turn off breach and strength checks on authentication, so that migrated users aren't subject to any changes in password strength requirements unless they ever voluntarily reset their password. This can be done in the Stytch [Dashboard](https://stytch.com/dashboard/password-strength-config).
2. You may configure your strength requirements to match your current requirements, but we advise for all customers to adopt our strongest policy, [zxcvbn](https://github.com/dropbox/zxcvbn), in order to improve your security posture. You may have this new policy only apply to new users and existing users who are resetting their password via step 1.
Deploying a new authentication service is not always limited to just changing login and signup logic. If your backend logs auth-related events or has a pre-existing RBAC implementation, you may need to refactor this logic to point to Stytch’s APIs.
## Migrating SSO connections
There are two ways to migrate over SSO connections from your existing auth system to Stytch:
### Option 1: Proxy connections
This option is only available for SAML IdPs that are currently configured with redirect URLs to your domain.
You will need to create a [SAML SSO connection](/docs/api-reference/b2b/api/sso/saml-connection-object) in Stytch for each existing SSO connection you plan to migrate - use the `alternative_audience_uri` parameter to pass Stytch the existing Audience. You can then forward requests from the URLs you already have set up to the relevant Stytch Connection ACS URL we provide:
Below is an example of how you can set up a simple redirect service to redirect requests from the originally configured redirect URL to Stytch, thus preserving your customers' IdP setups while migrating the SAML response processing to Stytch:
```js theme={null}
const getStytchConnectionId = async (oldIdentifier) => {
// Function to get Stytch connection_id by old identifier from your database, where you'd keep a mapping
};
app.get('/old-sso/:identifier', async (req, res) => {
const { identifier } = req.params;
try {
const connectionId = await getStytchConnectionId(identifier);
// Redirect to Stytch's SSO callback URL with the connection_id
const stytchCallbackUrl = `https://api.stytch.com/v1/b2b/sso/callback/SSO_CONNECTION_ID`;
//This can be a 307 or a 303 redirect
return res.redirect(307, stytchCallbackUrl);
} catch (error) {
return res.status(500).send('Internal server error');
}
});
```
### Option 2: Create new SSO connections
For this option, you will need to change the SSO settings in your customer's IdPs to point to Stytch's ACS URLs directly. Follow these [SSO guides](/docs/multi-tenant-auth/authentication/sso/overview) for more information on how to set up SSO.
## Ingesting events and managing visibility
If your legacy auth provider requires you to set up log streams or webhooks for event visibility, here are a few examples of how to move that logic to Stytch.
With Stytch, there are multiple ways to gain access to authentication-related logs.
### Option 1: Use webhooks
You can enroll in webhooks through the Stytch Dashboard by specifying URL(s) at which you wish to receive the webhook POST requests and the type of events you wish to receive.
Check out our [webhooks guide](/docs/resources/workspace-management/webhooks) to learn how to keep your internal database records in sync with any changes that occur in Stytch's B2B SaaS API.
```js theme={null}
// Example organization create via dashboard (dashboard.organization.create)
{
"project_id": "project-live-123-...",
"event_id": "event-live-123-...",
"action": "CREATE",
"object_type": "organization",
"source": "DASHBOARD",
"id": "organization-live-123-...",
"timestamp": "2024-03-07T18:49:32.760777783Z",
"organization": { ... }
}
// Example update member event via SCIM (scim.member.update)
{
"project_id": "project-live-123-...",
"event_id": "event-live-456-...",
"action": "UPDATE",
"object_type": "member",
"source": "SCIM",
"id": "member-live-123-...",
"timestamp": "2024-03-07T18:49:32.760777783Z",
"member": { ... }
}
// Example delete SAML connection event via JS SDK or API (direct.saml_connection.delete)
{
"project_id": "project-live-123-...",
"event_id": "event-live-789-...",
"action": "DELETE",
"object_type": "saml_connection",
"source": "DIRECT",
"timestamp": "2024-03-07T18:49:32.760777783Z",
"id": "saml-connection-live-123-..."
}
```
### Option 2: Log calls and responses made to Stytch from within your own application
Frontend SDK example, using Email Magic Links with JavaScript:
```js theme={null}
const onSubmit: FormEventHandler = async (e) => {
if (isValidEmail(email)) {
const resp = await stytchB2BClient.magicLinks.email.send({
email_address: 'sandbox@stytch.com',
organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
});
};
// Send the response of the call to your logging tool
// This includes the user info and response code
if (resp.status === 200) {
Bugsnag.notify(resp);
setEMLSent(STATUS.SENT);
} else {
Bugsnag.notify(e);
setEMLSent(STATUS.ERROR);
}
};
```
Backend SDK/API example, using Python:
```py theme={null}
@app.route("/login_user", methods=["POST"])
def login_user() -> str:
resp = stytch_b2b_client.magic_links.email.send(
email=request.form["email"],
login_magic_link_url=MAGIC_LINK_URL,
signup_magic_link_url=MAGIC_LINK_URL,
organization_id=org_id
)
# Send the response of the call to your logging tool
# This includes the user info and response code
if resp.status_code == 200:
bugsnag.notify(resp)
if resp.status_code != 200:
bugsnag.notify(resp)
print(resp)
return "something went wrong sending magic link"
return render_template("emailSent.html")
```
### Option 3: Check out the event logs in the dashboard
View the [Event Logs](https://stytch.com/dashboard/activity) in the Dashboard to debug your authentication flows.
## Migrating roles and permissions
If your application has an authorization system that resembles role-based access control (RBAC), you can transfer roles and permissions over to Stytch's RBAC framework.
Refer to our [RBAC guides](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy) for more information and instructions on how to create roles, resources, and permissions, and how to programmatically assign roles to Members.
## Pre-production testing
If you run any automated or pre-production testing that includes authentication, here are a few best practices to move that to Stytch:
* If you have multiple pre-production environments that need their own user bases, use the Stytch Dashboard to create multiple test environments and have each set of `test` keys map to each of your internal environments.
* We recommend creating test users and organizations that can be used for any authentication testing and QA flows.
## Migrating over additional user data
Our `trusted_metadata` supports nested JSON and can be used to migrate and store any additional user data.
We recommend keeping this data limited to information that is required in the context of authentication; for example, internal identifiers, profile information, and other claims.
**Keeping identifiers consistent**: If you have analytics or other internal processes that key off of your internal user ID reference, and would like these IDs to stay consistent, we recommend adding your internal user ID as `trusted_metadata`. Your backend should then default to reading ID from this value, but fall back to the Stytch `member_id` if it does not exist. Stytch does not currently support seeding user GUIDs.
## Email/phone verification status
Stytch manages the email and phone verification status of users. When a Member is created in Stytch, the value of `email_address_verified` will be `false` until the user completes their first login that verifies ownership of that account (e.g., an Email Magic Link or OAuth login). The value of `mfa_phone_number_verified` will be `false` until the member completes a successful [SMS authenticate](/docs/api-reference/b2b/api/mfa/otp/authenticate-sms-otp) flow.
The one exception is our [Passwords Migrate endpoint](/docs/api-reference/b2b/api/passwords/migrate), which will mark the Member's email as verified. Note that improperly setting email addresses as verified can open up a security gap in your application.
## Password strength requirement changes
See our Docs for password strength configuration [here](/docs/multi-tenant-auth/authentication/passwords/strength-policy).
The default password strength policy is [zxcvbn](https://github.com/dropbox/zxcvbn), but you can configure a different password policy via the [Dashboard](https://stytch.com/dashboard/password-strength-config).
For those migrating users with passwords, our recommendations are to:
1. Turn off breach and strength checks on authentication, so that migrated users aren't subject to any changes in password strength requirements unless they ever voluntarily reset their password. This can be done in the Stytch [Dashboard](https://stytch.com/dashboard/password-strength-config).
2. You may configure your strength requirements to match your current requirements, but we advise all customers to adopt our strongest policy, [zxcvbn](https://github.com/dropbox/zxcvbn), in order to improve your security posture. You may have this new policy only apply to new users and existing users who are resetting their password via step 1.
# Exporting from Stytch
Source: https://stytch.com/docs/resources/migrations/exporting-from-stytch
To export your passwordless Users from Stytch, use our Search users endpoint to export what you need.
To export your passwordless Users from Stytch, use our [Search users](/docs/api-reference/consumer/api/users/search-users) endpoint to export what you need. You can pull the full User object, which will contain the user's email(s), phone number(s), and other data such as crypto wallet addresses and [user metadata](/docs/api-reference/consumer/api/resources/metadata).
The following Github repository contains a utility that exports all of your User data to a CSV or JSON file:
* [Stytch User export utility](https://github.com/stytchauth/stytch-node-export-users)
If you're looking to export Stytch Users that contain a password, contact us at [support@stytch.com](mailto:support@stytch.com) and we will work with you to provide a smooth export of your hashed password data. We can also work with you to provide other data necessary to your migration, such as biometric public keys.
To export your Organizations and passwordless Member data from Stytch, use our [Search Organizations](/docs/api-reference/b2b/api/organizations/search-organizations) endpoint and [Search Members](/docs/api-reference/b2b/api/members/search-members) endpoint to export what you need. You can pull the full Organization and Member objects, which will contain the user's email, phone number, and metadata as well as the Organization's slug, name and authentication settings.
If you're looking to export Stytch Members that contain a password, contact us at [support@stytch.com](mailto:support@stytch.com) and we will work with you to provide a smooth export of your hashed password data. We can also work with you to provide other data necessary to your migration, such as biometric public keys.
# External IDs
Source: https://stytch.com/docs/resources/migrations/external-ids
Assign and use external IDs for Stytch resources in place of Stytch-assigned UUIDs.
When a User is created in Stytch, they will be assigned a unique identifier that looks like a UUID. For your convenience, you may choose to additionally assign an "external ID" to a user which can then be used in any place where the Stytch `user_id` is expected.
## Assigning an external ID at creation
When calling the [Create User](/docs/api-reference/consumer/api/users/create-user) endpoint, you may pass in an optional `external_id` parameter. The `external_id` is a string consisting of alphanumeric, ., `_`, or `-` characters with a maximum length of 128 characters. External IDs must be unique across users in your project.
You may also set an external ID during a call to the [Migrate Password](/docs/api-reference/consumer/api/passwords/migrate) endpoint, but only if the user does not yet exist and is created. If the user already exists, you must use the [Update User](/docs/api-reference/consumer/api/users/update-user) endpoint to assign or reassign the `external_id`.
## Assigning an external ID later
If a user has already been created, you may assign it an `external_id` *later* with the [Update User](/docs/api-reference/consumer/api/users/update-user) endpoint. Similar to creation, pass in an `external_id` parameter and it will then be assigned to that user. Again, remember that external IDs must be unique within your project.
## Updating a user's external ID
If you wish to change a user's external ID, you may use the same [Update User](/docs/api-reference/consumer/api/users/update-user) endpoint listed above. After this point, you will no longer be able to refer to that user by its *old* `external_id`.
## Using external IDs
In any endpoint that expects a `user_id`, you may instead choose to pass in your custom-defined `external_id`. For clarity, all responses *from* Stytch will continue to use the Stytch-defined `user_id`, though `external_id` will exist as a subfield within the User object.
When a Member or Organization is created in Stytch, they will be assigned a unique identifier that looks like a UUID.
For your convenience, you may choose to additionally assign an "external ID" to a member which can then be used in any place where the Stytch `member_id` is expected.
You can also assign an "organization external ID" to an organization which can then be used any place where the Stytch `organization_id` is expected.
## Assigning an external ID at creation
An External IDs is a string consisting of alphanumeric, `.`, `_`, or `-` characters with a maximum length of 128 characters.
Member external IDs must be unique across members in a given organization, but may be reused across different organizations in the same project.
Organization external IDs must be unique across all organizations in the same project
When calling the [Create Member](/docs/api-reference/b2b/api/members/create-member) you may pass in an optional `external_id` parameter.
When calling the [Create Organization](/docs/api-reference/b2b/api/organizations/create-organization) you may pass in an optional `organization_external_id` parameter.
You may also set an external ID during a call to the [Migrate Password](/docs/api-reference/b2b/api/passwords/migrate) endpoint, but only if the member does not yet exist and is created.
If the member already exists, you must use the [Update Member](/docs/api-reference/b2b/api/members/update-member) endpoint to assign or reassign the `external_id`.
## Assigning an external ID later
If a member has already been created, you may assign it an `external_id` *later* with the [Update Member](/docs/api-reference/b2b/api/members/update-member) endpoint.
Similar to creation, pass in an `external_id` parameter and it will then be assigned to that member.
Again, remember that external IDs must be unique within a given organization, but may be reused across different organizations in the same project.
Organizations can similarly be assigned an `organization_external_id` later with the [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization) endpoint.
## Updating external IDs
If you wish to change a member's external ID you may use the same [Update Member](/docs/api-reference/b2b/api/members/update-member) endpoints listed above.
If you wish to change an organization's external ID you may use the [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization) endpoint.
After this point, you will no longer be able to refer to that entity by its *old* `external_id`.
## Using external IDs
In any endpoint that expects a `member_id`, you may instead choose to pass in your custom-defined `external_id`.
For clarity, all responses *from* Stytch will continue to use the Stytch-defined `member_id`, though `external_id` will exist as a subfield within the Member object.
In any endpoint that expects an `organization_id`, you may instead choose to pass in your custom-defined `organization_external_id`.
Similarly, all responses *from* Stytch will continue to use the Stytch-defined `organization_id`, though `organization_external_id` will exist as a subfield within the Organization object.
```js theme={null}
const stytch = require('stytch');
const client = new stytch.B2BClient({
project_id: '${projectId}',
secret: '${secret}',
});
const params = {
organization_id: "example-organization-external-id",
member_id: "example-member-external-id",
};
client.organizations.members.get(params)
.then(resp => { console.log(resp) })
.catch(err => { console.log(err) });
```
# Migrate from Consumer to B2B
Source: https://stytch.com/docs/resources/migrations/migrating-from-consumer-to-b2b
Migrate from Consumer to B2B auth in Stytch.
At some point, you may find that Stytch's user-first Consumer data model no longer works for your use case and you want to migrate to the organization-first B2B data model.
Migrating Stytch data models requires some work so please be sure to first consider your desired end state, login flows, and other requirements you might have. Feel free to [ask us](https://stytch.com/contact) if you have any questions about this transition.
For more information, please check out [this guide](/docs/multi-tenant-auth/overview) on the differences between the two models.
## Common use cases
* You are moving from a single-tenant to a multi-tenant model, and thus need Organizations to represent tenants and isolate data within each tenant.
* You require native SSO (SAML/OIDC) support and [Trusted Auth Tokens](/docs/consumer-auth/authentication/trusted-auth-tokens) do not fit your use case.
* You require [SCIM](/docs/multi-tenant-auth/enterprise-ready/scim/overview) to enable end users to be managed by their workforce IdP.
## Before you begin
In B2B authentication, the equivalent to a User is a Member.
Users may be created with an email address, phone number, or no auth factor at all. On the other hand, Members *must* have an email address. Email addresses must be unique within an Organization, but can be shared by Members across multiple Organizations.
When updating your authentication flows, keep in mind that there are two different login flows paths in B2B: Discovery flows generally return an Intermediate Session Token and a list of discovered Organizations, and Organization-specific authentication endpoints require a specific `organization_id` to be passed in. For more information, please see [this guide](/docs/multi-tenant-auth/overview#core-flows).
If you support Passwords as an authentication method, please be sure to read through [this guide](/docs/multi-tenant-auth/authentication/passwords/overview) to determine whether you want to use Cross-Organization or Org-Scoped Passwords, as you cannot change this setting once you have active Passwords.
## Prerequisites
Please note that most of these steps can be managed via [Programmatic Workspace Actions](/docs/api-reference/pwa/api/v3/overview) or [the API](/docs/api-reference/b2b/api/overview), either by using your existing infrastructure as code, or by getting the corresponding object and re-creating it with the B2B-specific API endpoint.
1. Create a new Project and select **B2B SaaS Application**.
2. Configure any Project-level settings that existed for your Consumer Project, e.g. [Redirect URLs](/docs/consumer-auth/build-auth/login-or-signup), [Custom Domains](/docs/resources/branding/custom-domains), [Custom Claims](/docs/multi-tenant-auth/manage-sessions/custom-claims), Frontend SDK configuration, [RBAC policies](/docs/multi-tenant-auth/enterprise-ready/rbac/create-rbac-policy#the-rbac-policy), etc.
## Migrating your users
To migrate your Consumer Users to B2B Members we must first create all of the Organizations, i.e. tenants, for those Members to belong to.
There are many strategies you can take for pre-creating Members and Organizations depending upon your application logic today, i.e. whether you already have tenancy in your application, whether you want to do a dual write or just-in-time migration, etc. For this guide we'll keep it very general but definitely [reach out](https://stytch.com/contact) to our team if you have some questions or want specific recommendations.
Create Organizations via our [Create Organization endpoint](/docs/api-reference/b2b/api/organizations/create-organization).
Here you will also want to consider what authentication options you'd like per Organization.
Now, you'll migrate your Users by creating them as Members under their corresponding Organization(s). You can reference the steps in [this guide](/docs/resources/migrations/migrating-user-data) to learn about best practices here.
Make sure you create Members as `active`, not `pending`, as otherwise you'll need to go through an authentication flow to "activate" them.
## Migrate your codebase
Now, you'll want to migrate your codebase to use our B2B endpoints.
Switch out the Consumer endpoints for their B2B equivalents and modify your application logic to account for this new tenancy model.
You can feature flag these changes in conjunction with the previous step in a gradual roll out to minimze impact on your end users.
## Migrating sessions
Finally, you may want to consider migrating existing Consumer sessions for B2B sessions to ensure the migration is seamless for your end users, preventing them from needing to re-authenticate.
Note, this can be somewhat complex and you should carefully weigh the cost benefit ratio of preserving pre-existing sessions with the work required to migrate them.
Often times a switch to our B2B API may bring along new auth requirements, as your user's teams can now define restricted auth methods or MFA, and this migration represents a good time to force a re-authentication for all users.
To migrate your Consumer sessions to B2B, you can utilize [Trusted Auth Tokens](/docs/consumer-auth/authentication/trusted-auth-tokens) and exchange a JWT from a Consumer session for a B2B one using the [Attest Session](/docs/api-reference/b2b/api/sessions/attest-session) endpoint.
In your B2B Project, create a [Trusted Auth Token Profile](https://stytch.com/dashboard/trusted-auth-tokens).
Set **Audience** to the `aud` the Consumer session JWT will have (typically the `project_id` of the Consumer Project). Set **Issuer** to the `iss` the Consumer session JWT will have (typically `stytch.com/`). Set **JWKS** to `https://test.stytch.com/v1/sessions/jwks/`. Replace this with `api.stytch.com` for Live environments. Set `email` to whatever attribute represents the email address, added via Custom Claims (see steps below). Here, we'll use `email`. Set `token_id` to `sub`. (Optional) Set any other attribute mappings.
Add an attribute identifying an end user's email address to the Stytch User's [`trusted_metadata`](/docs/api-reference/consumer/api/resources/metadata) in your Consumer Project. This can be done in bulk with the [Update User](/docs/api-reference/consumer/api/users/update-user) endpoint.
This will expose the user's email to be consumed by the Trusted Auth Token step below.
Example using `email` as the name of the attribute:
Modify the [Custom Claim Template](/docs/consumer-auth/manage-sessions/custom-claims) in your Consumer Project, and ensure the template content contains some sort of attribute representing the email address to use.
Again, we'll use `email` in the example below; note that the value should reference the same attribute name you added to the `trusted_metadata` in the previous step:
```json theme={null}
{
"email": {{ user.trusted_metadata.email }}
}
```
This will ensure that all newly minted session JWTs will include this new Custom Claim, which our Trusted Auth Token Profile will utilize.
Now you should be all set, and all you'll need to do is take Consumer session JWTs for your Users, and use it to attest a B2B session.
Get the Consumer session JWTs you would like to migrate with the [Get Sessions](/docs/api-reference/consumer/api/sessions/get-sessions) endpoint for each of your Users with an active session.
You may additionally want to "refresh" any older/existing sessions for a new one via the [Authenticate Session](/docs/api-reference/consumer/api/sessions/authenticate-session) endpoint, so that the JWT contains an email (or whatever you named your attribute) in the Custom Claims added in the previous step.
Pass these JWTs into the [Attest Session](/docs/api-reference/b2b/api/sessions/attest-session) endpoint, along with the `profile_id` of your Trusted Auth Token Profile and the `organization_id`.
You should get a newly minted B2B session for the Member that matches the `email` in the JWT.
Now that you have B2B sessions, you'll want to hydrate these sessions wherever you use Stytch sessions today.
If you are using our frontend SDKs, you'll use their [Update Session method](/docs/api-reference/b2b/frontend-sdks/vanilla-js/methods/sessions/update-session) to update the client with the new session.
If you're using our Backend SDKs and your application manages the session cookie lifecycle, you'll want to feed these new B2B sessions into that path.
You're done! All of your users with pre-existing sessions in our Consumer API now have up-to-date and still valid sessions in our B2B API as well, no re-authentication required.
## Other resources
* Export Users from your Consumer Project with this [export utility script](https://github.com/stytchauth/stytch-node-export-users)
# Migrating user data
Source: https://stytch.com/docs/resources/migrations/migrating-user-data
Choose a migration strategy for moving user data to Stytch.
If your users have passwords, Stytch recommends the **static migration** approach by creating jobs to synchronize data to Stytch. If your users are passwordless, you can use a static migration or follow the **dynamic (rolling) migration** approach.
## Static migration (recommended for passwords)
If you have over 1M users, skip to **Dual write and backfill** below.
### Export and import
First, export your user data. Helpful resources for exporting:
* **Auth0**: Create a [support request](https://community.auth0.com/t/how-to-export-user-password-hashes/45485).
* **Firebase**: Use the [CLI](https://firebase.google.com/docs/cli/auth#authexport). Note that Firebase password hashes are **not** currently compatible with Stytch, and a static migration of users with passwords from Firebase to Stytch is not possible.
* **AWS Cognito**: Use the [ListUsers API](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API%5FListUsers.html). AWS Cognito does not support exporting password hashes through this API.
Next, write an import job that calls Stytch's API to create each exported user:
* Passwordless users: [Create User](/docs/api-reference/consumer/api/users/create-user)
* Users with passwords: [Migrate Password](/docs/api-reference/consumer/api/passwords/migrate)
* Users with email + phone + password: create the user first, then migrate the password hash
Your script should submit requests to Stytch at a maximum rate of **65 r/s**.
### Dual write and backfill (for large user bases)
Dual write and backfill keeps Stytch in sync with your existing provider while you migrate.
Sync all user changes into Stytch (create, update, delete) and save the returned `user_id` in your DB.
Create any users without a Stytch `user_id` via [Create User](/docs/api-reference/consumer/api/users/create-user), one at a time, capped at 65 r/s.
After backfill, call [Migrate Password](/docs/api-reference/consumer/api/passwords/migrate) for existing Stytch users.
## Dynamic (rolling) migration (recommended for passwordless)
If your users do not have passwords, you can migrate users as they log in:
1. As each user logs in, call the Stytch API to authenticate and create a Session.
2. Look up the user in your internal database.
3. If the user exists, store the Stytch `user_id` on the record.
4. If the user does not exist, create a new user record and store the Stytch `user_id`.
## Adding TOTP or biometrics
For auth factors like TOTP or Biometrics, you cannot use Create User or Migrate Password. Re-enroll users using the Stytch APIs or SDKs after they have been imported and authenticated:
* [TOTP Register](/docs/api-reference/consumer/api/totp/create)
* [Biometrics Register](/docs/api-reference/consumer/api/passkeys-webauthn/register/start)
Note: You cannot add additional factors until the primary factor has been verified. See the [Additional migration considerations](/docs/resources/migrations/additional-migration-considerations#consumer-auth) guide for more details.
In order to migrate your authentication from an existing system to Stytch's B2B SaaS Authentication platform, you will need to import all current organization and user data to Stytch via API or SDK.
Stytch recommends the data migration approaches outlined below. This involves creating a set of jobs to synchronize organization and user data to Stytch.
## Dual-write and backfill
In order to achieve a smooth, zero-downtime migration without any issues around data reconciliation, we recommend setting up dual-writes prior to kicking off a backfill to Stytch. This allows any create, update, or delete requests during the backfill period are reflected in both your current system as well as in Stytch, ensuring that you can cut over to Stytch as the read source of truth without any customer impact or downtime.
However, if you have less than \~500k users and would rather skip the dual write step, you can always run the backfill directly via an **Export and Import** approach described below.
The above is an example diagram of a Dual-write and Backfill process for users and organizations.
For every place you write to your internal member or organization tables, introduce a corresponding write to Stytch. This includes new user signups, user updates, user deletions, organization creations, organization updates and organization deletions.
The returned Stytch `member_id` or `organization_id` should be saved in your tables on creation, so you know the user or organization has been migrated.
**Organization API endpoints:**
* Creations: Create organization via [Create Organization](/docs/api-reference/b2b/api/organizations/create-organization)
* Updates:
* If the organization doesn't already exist in Stytch, create the organization via [Create Organization](/docs/api-reference/b2b/api/organizations/create-organization)
* If you already have a `stytch_organization_id` associated with your record, update the organization via [Update Organization](/docs/api-reference/b2b/api/organizations/update-organization)
* Deletions: If you've already created the Organization in Stytch (`stytch_organization_id` associated with the record) delete the organization via [Delete Organization](/docs/api-reference/b2b/api/organizations/delete-organization)
**Member API endpoints:**
* Create user:
* If this is a new user joining an existing Organization that has already been migrated to Stytch (`stytch_organization_id` on record), create via [Create Member](/docs/api-reference/b2b/api/members/create-member)
* If this is a user signing up for a new Organization or joining an Organization that has not been migrated yet, [Create Organization](/docs/api-reference/b2b/api/organizations/create-organization) first and store the `stytch_organization_id` and then [Create Member](/docs/api-reference/b2b/api/members/create-member)
* Updates:
* If the user doesn't already exist in Stytch, create the user as described above. Use [Migrate Member](/docs/api-reference/b2b/api/passwords/migrate) if the user has a password hash in your DB.
* If the user already exists (`stytch_member_id` associated with your record) use the [Update Member](/docs/api-reference/b2b/api/members/update-member) API for updates to name, email, roles, or metadata.
* If the update is to the MFA Phone Number, Password or Member TOTP Registration, you'll need to first delete the old authentication factor record from the Stytch Member, and then call Update Member to complete.
* Deletions: Delete user via [Delete Member](/docs/api-reference/b2b/api/members/delete-member)
While dual-write is continuously running, you can assume that all users and organizations in your internal tables **without** an associated Stytch `member_id` or `organization_id` must run through a backfill job that adds them to Stytch via the [Create Organization endpoint](/docs/api-reference/b2b/api/organizations/create-organization) or the [Create Member endpoint](/docs/api-reference/b2b/api/members/create-member), one at a time.
Create Organization:
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/b2b/organizations \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"organization_name": "Example Org Inc.",
"organization_slug": "example-org",
"mfa_policy": "REQUIRED_FOR_ALL",
"email_allowed_domains": ["example.com"],
"email_invites": "ALL_ALLOWED",
"email_jit_provisioning": "RESTRICTED",
}'
```
Create Member:
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/b2b/organizations/{ORGANIZATION_ID}/members \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email_address": "adalovelace@stytch.com",
"mfa_phone_number": "+18000000000",
"mfa_enrolled": true
"trusted_metadata": {"internal_id": "407207d7-2a19-44e8-b192-4e2d45428b31"}
}'
```
Your backfill job should start with Organizations, then backfill Members once Organizations are complete. This job should submit requests to Stytch at a rate of **100 r/s maximum**.
If your manage passwords in-house within your own databases, you can integrate the logic for migrating passwords hashes into the core dual-write and backfill steps. However, if you are migrating over from a legacy auth provider, like Auth0, that stores password hashes on your behalf, you will need a full data export. In order to minimize the time for drift on password changes, we recommend migrating password hashes **at the end** of this process by executing a script to call our [Migrate Password endpoint](/docs/api-reference/b2b/api/passwords/migrate) for each user after backfill has been completed.
To migrate passwords you will leverage the [Migrate Password endpoint](/docs/api-reference/b2b/api/passwords/migrate) by passing in the `email_address` associated with the Member, and the password hash from your provider.
Stytch offers the option to either **allow end users to use the same password across *all* Organizations they belong to under that email** or to **scope passwords to the specific Member/Organization**. This setting **cannot be changed** once you have active passwords in your project, so prior to kicking off your migration update your password settings in the [Stytch Dashboard](https://stytch.com/dashboard/password-strength-config) to match the password behavior that you would like.
If you want to allow passwords to be used across Organizations, you only need to call the [Migrate Password endpoint](/docs/api-reference/b2b/api/passwords/migrate) one time for a given email -- even if the email is associated with multiple Members. If you want passwords to be scoped to the Member/Organization, you will call the Migrate Password endpoint for each MemberID you've migrated to Stytch.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/b2b/passwords/migrate \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email_address": "adalovelace@stytch.com",
"hash": "$2a$12$vefoDBbzuMb...",
"hash_type": "bcrypt",
"organization_id": "organization-test-07971..."
}'
```
## Export and import
First, you will need to export your organization and user data from your current auth service. We recommend querying this information from your internal user and organization tables if possible. If you don't have a copy of user and workspace records outside of your auth provider, here are some helpful resources to export from various auth providers:
* To export from **Auth0**, create a [support request](https://community.auth0.com/t/how-to-export-user-password-hashes/45485).
Next, you will write an import job that calls Stytch's API to create each Organization and Member in Stytch. The logic should resemble the following steps:
Pass each application user group's name, slug, and authentication settings into Stytch [create Organization](/docs/api-reference/b2b/api/organizations/create-organization) API endpoint.
* If the Organization has Single Sign-On connections with an identity provider, you will need to create SAML and OIDC connection objects. See [Additional migration considerations](/docs/resources/migrations/additional-migration-considerations#b2b-auth) for more information.
* Configure an Organization's auth settings to control approved authentication methods, allowed email domains, invites, MFA, Just-in-Time (JIT) Provisioning, SSO connections, and more. See the [managing Organization settings](/docs/multi-tenant-auth/enterprise-ready/org-management/org-configuration-rules) guide for more information.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/b2b/organizations \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"organization_name": "Example Org Inc.",
"organization_slug": "example-org",
"email_allowed_domains": ["exampleorg.com"],
"email_invites": "RESTRICTED"
}'
```
For each Stytch Organization a user belongs to, you will need to create a Member object. Pass the user's email address, phone number (optional), and their password hash (optional) into the corresponding Stytch API endpoints:
* If the user is passwordless, use the [Create Member endpoint](/docs/api-reference/b2b/api/members/create-member).
* If the user has a password, use our [Migrate Password endpoint](/docs/api-reference/b2b/api/passwords/migrate).
* If the user has a password and two factors (e.g. email and phone), first use the [Create Member endpoint](/docs/api-reference/b2b/api/members/create-member) to add the `email_address` + `mfa_phone_number`, then use the [Migrate Password endpoint](/docs/api-reference/b2b/api/passwords/migrate) to add the password hash.
Stytch offers the option to either **allow end users to use the same password across *all* Organizations they belong to under that email** or to **scope passwords to the specific Member/Organization**. This setting **cannot be changed** once you have active passwords in your project, so prior to kicking off your migration update your password settings in the [Stytch Dashboard](https://stytch.com/dashboard/password-strength-config) to match the password behavior that you would like.
If you want to allow passwords to be used across Organizations, you only need to call the [Migrate Password endpoint](/docs/api-reference/b2b/api/passwords/migrate) one time for a given email -- even if the email is associated with multiple Members. If you want passwords to be scoped to the Member/Organization, you will call the Migrate Password endpoint for each MemberID you've migrated to Stytch.
You may call [Migrate Password endpoint](/docs/api-reference/b2b/api/passwords/migrate) for existing Stytch Members, using the `email_address` associated with the Member. Stytch will recognize the email address and add the password hash to the existing Member object or allow for use across multiple Member objects depending on your setting.
```sh theme={null}
curl --request POST \
--url https://test.stytch.com/v1/b2b/organizations/{ORGANIZATION_ID}/members \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"name": "Ada Lovelace",
"email_address": "adalovelace@stytch.com"
}'
```
## What's next
Check out the [additional migration considerations guide](/docs/resources/migrations/additional-migration-considerations#b2b-auth) to learn how to move your app logic to Stytch and for more user import guidance.
# Migration overview
Source: https://stytch.com/docs/resources/migrations/overview
Migrate your app's existing authentication system over to Stytch.
Migrating your authentication setup can seem daunting, but this guide will walk you through everything you need in order to run a smooth, zero-downtime migration to Stytch from any authentication setup, whether it's an in-house build or from a legacy provider like Auth0 or Cognito.
This migration guide covers the process of migrating your app's existing authentication system over to Stytch. Here we provide generalized guidance which applies to any authentication setup, including authentication built in-house as well as Auth0, AWS Cognito, and Google Firebase.
## Before you start
This set of migration guides assumes you are using Stytch's Consumer APIs and SDKs. If you are using our B2B APIs, please see the [B2B migration guide](#b2b-auth).
In order to complete any of the migration guides, you'll need the following:
* A [Stytch](https://stytch.com/start-now) account if you don't have one already.
* A Stytch Consumer Auth project. If you don't have one already, in the [Dashboard](https://stytch.com/dashboard), click on your projects in the top nav, click **Create Project**, and then select **Consumer Authentication**.
* The `project_id` and secret for the **Test environment** you would like to use. These values can be accessed from the [Project ID & API keys](https://stytch.com/dashboard) section of the Project Overview. You'll need to pass these values into the Authorization request header for most Stytch API calls.
## Key questions and considerations
Before you start, you should consider the following questions about your migration requirements:
* What are your application surface areas? Do you have a web app, mobile app, or both?
* Does your user base use passwords? Are there passwordless users? A mix of both?
* How many authentication factors do your users have? Do your users have only one primary factor like email? Or multiple factors like email and phone?
* Where is authentication handled in your stack? Will you be integrating the Stytch API and SDKs into your backend or frontend?
## Migration guides
Depending on how you answered the previous questions, our recommended approaches and strategies will differ. We've split them into multiple step-by-step guides organized by migration use cases:
1. [Static data migration strategy](/docs/resources/migrations/migrating-user-data#static-migration-recommended-for-passwords)
2. [Dynamic data migration strategy](/docs/resources/migrations/migrating-user-data#dynamic-rolling-migration-recommended-for-passwordless)
3. [Migrating authentication related logic](/docs/resources/migrations/additional-migration-considerations#consumer-auth)
4. [Deploying your Stytch integration to production](/docs/resources/migrations/zero-downtime-deployment)
5. [Exporting from Stytch](/docs/resources/migrations/exporting-from-stytch#consumer-auth)
To understand if migrating data statically or dynamically is a better strategy for you, see our high level recommendations below:
## What's next
To start, we recommend checking out the migrating user data guides — [statically](/docs/resources/migrations/migrating-user-data#static-migration-recommended-for-passwords) or [dynamically](/docs/resources/migrations/migrating-user-data#dynamic-rolling-migration-recommended-for-passwordless).
Below is a high level overview of the sequenced steps involved in migrating to Stytch. Each step has its own detailed guide that will walk you through the key decisions and considerations based on your current authentication setup.
The first step of planning your migration to Stytch is planning out how you want to connect your current data model to Stytch's core entities: Organization and Member.
If you're unsure how your end user and tenant entities should map to Stytch's Members and Organizations, the [reconciling data models guide](/docs/resources/migrations/reconciling-data-models#b2b-auth) covers the key considerations around multi-tenancy, account deduplication and more.
In order to achieve a smooth, zero-downtime migration without any issues around data reconciliation, we recommend setting up dual-writes prior to kicking off a backfill to Stytch. This allows any create, update, or delete requests during the backfill period are reflected in both your current system as well as in Stytch, ensuring that you can cut over to Stytch as the read source of truth without any customer impact or downtime.
However, if you have a relatively small user base you can always run the backfill without a dual write.
For more details setting up the dual-write, backfill and considerations depending on if you're coming from an in-house build or a legacy provider like Auth0, check out the [migrating user data guide](/docs/resources/migrations/migrating-user-data#b2b-auth).
Depending on your current system and offerings you might need to migrate other data to Stytch, such as:
* Single Sign-On (SSO) Connections
* Roles and permissions
* Ingesting events
* Password policies
Review [additional migration considerations](/docs/resources/migrations/additional-migration-considerations#b2b-auth) to see if any of these apply to your migration.
If you're migrating to Stytch from Auth0 (or other 3rd party providers that use OIDC JWTs) we offer a **Zero-Downtime Session Migration** feature that allows you to mint Stytch Sessions from an external session -- ensuring that your users can stay logged in as your cutover to Stytch.
We discuss the details of how this works, and alternative deployment strategies if you're moving from in-house or a non-OIDC compliant authentication provider in the [zero down-time deployment](/docs/resources/migrations/zero-downtime-deployment#b2b-auth) guide.
You've switched to Stytch! Time to cut a satisfying PR to delete the deprecated code!
# Reconciling data models
Source: https://stytch.com/docs/resources/migrations/reconciling-data-models
Map your existing user model to Stytch's authentication data model.
Before migrating, align your existing data model to Stytch's Consumer User object. This will help you plan which identifiers, auth factors, and metadata to migrate and how to map your internal IDs.
## Key data to map
* **Unique identifiers**: Decide whether email, phone, or both will identify users, and verify uniqueness.
* **Auth factors**: Inventory which factors each user has (passwords, email, phone, passkeys, etc.).
* **Metadata**: Identify which attributes should move into `trusted_metadata` or `untrusted_metadata`.
* **External IDs**: Preserve your internal user ID by mapping it to Stytch's `external_id`.
* **Session data**: Plan how you'll validate sessions post-migration and whether you'll use Session Tokens or JWTs.
* **Redirects and domains**: Ensure redirect URLs and authorized domains are configured in the Dashboard.
## Recommended mapping approach
1. Start with the [Consumer data model](/docs/consumer-auth/data-model) to understand the Stytch User object.
2. Map your internal user identifier to [External IDs](/docs/resources/migrations/external-ids#consumer-auth).
3. Decide which user attributes are authoritative in your system versus Stytch, then align metadata accordingly.
4. Plan for changes in auth factors (for example, if you're adding magic links or passkeys post-migration).
## Questions to answer
* Where is authentication handled today (frontend, backend, or both)?
* Which auth methods do users rely on (passwords, email, phone, or multiple)?
* How many factors does a user typically have?
* Do you need to support a web app, mobile app, or both?
For a broader migration checklist, see [Migration overview](/docs/resources/migrations/overview#consumer-auth).
The first step of planning your migration to Stytch is planning out how you want to connect your current data model to Stytch's core entities. This guide outlines the key considerations and approaches for how to do this for a range of existing data models.
## Overview of Stytch's data model
Stytch's [B2B data model](/docs/multi-tenant-auth/data-model) is centered around two first-class API entities: Organizations and Members.
1. A Stytch [Organization](/docs/api-reference/b2b/api/organizations/overview) is the top-level “tenant", which groups one or more [Members](/docs/api-reference/b2b/api/members/overview) together within your application.
2. A Stytch [Member](/docs/api-reference/b2b/api/members/overview) is an end user who belongs to an Organization. Stytch uses **email address** as the primary unique identifier for Members within an Organization. An end user can belong to multiple Organizations by having multiple Member records, which are linked together by using the same email address.
For more details and technical specifics on Organizations and Members, visit our [B2B Overview](/docs/multi-tenant-auth/overview) or the [API reference](/docs/api-reference/b2b/api/overview).
## Defining the top-level tenant
The top-level tenant of your application usually maps to your business customers, who pay for your service and own all the data and resources within their instance. Key characteristics of a top-level tenant are:
* **Account Ownership:** the tenant holds the subscription or contract for using your services, or has the ability to manage billing and upgrade if you have a free tier
* **Access Management:** the tenant determines which end users can access their instance, and what roles and permissions authorized end users can do within the context of the tenant
* **User Management:** the tenant can manage end users, performing admin actions like updating emails or resetting MFA enrollment
* **Data Segregation:** the data and configurations that belong to the tenant are *typically* isolated from other tenants, ensuring strong governance and security
The top-level tenant you've identified will map to Stytch's **Organization** concept.
If you have a mix of business customers and individual end users, we recommend creating an Organization behind the scenes for each individual user "tenant" in order to allow these single-player accounts to seamlessly upgrade and add collaborators in the future. This is the same approach that Notion takes, creating a WorkspaceID in the background for both single-player and multi-player sign-ups.
To create a clean 1:1 mapping between your internal representation of the top-level tenant and Stytch's Organization, store the stable Stytch `organization_id` as a column on your equivalent entity.
Outside of the UUID, Stytch Organizations can also be uniquely identified by `organization_slug`, which is the unique URL identifier of your application (e.g. `acme` for `acme.your-app.com` or `your-app.com/team/acme`). However, as the organization slug can be updated, we recommend primarily using the `organization_id` in any internal references to the Stytch object.
If you have internal information about the Organization that you want to have accessible in Stytch (for example, to automatically include in the [JWT Custom Claims](https://stytch.com/dashboard/custom-claim-templates) for Member Sessions), you can use the `trusted_metadata` field on the Organization.
Stytch supports a wide range of **authentication and provisioning settings** that you can offload entirely to Stytch, to control approved authentication methods, MFA Policies, Just-in-Time (JIT) Provisioning and more. Refer to the [Organization object](/docs/api-reference/b2b/api/organizations/organization-object) in the API reference to see all available fields and settings. You can set these while migrating Organizations in via the CreateOrganization method.
```js theme={null}
[
{
"organization_id": "organization-test-07971b...",
"organization_name": "Team A",
"organization_slug": "team-a",
"trusted_metadata": {
"billing_tier": "free",
"privacy_setting": "public"
},
"email_invites": "ALL_ALLOWED",
"email_jit_provisioning": "NOT_ALLOWED",
...
},
{
"organization_id": "organization-test-02645e...",
"organization_name": "Team B",
"organization_slug": "team-b",
"trusted_metadata": {
"billing_tier": "premium",
"privacy_setting": "private"
},
"email_invites": "RESTRICTED",
"email_jit_provisioning": "RESTRICTED",
"email_allowed_domains": ["stytch.com"],
...
},
{
"organization_id": "organization-test-09320h...",
"organization_name": "Team C",
"organization_slug": "team-c",
"trusted_metadata": {
"billing_tier": "enterprise",
"privacy_setting": "private"
},
"auth_methods": "RESTRICTED",
"allowed_auth_methods": ["sso"],
...
}
]
```
## Defining & mapping end users
Stytch's user entity is the **Member** object, which is scoped to a *specific* Organization and is uniquely identified *within* that Organization by their email address. If `claude.shannon@stytch.com` belongs to three different Organizations, there will be three entirely distinct Member records associated with `claude.shannon@stytch.com`.
End users can still "switch" between different Organizations without having to log out and log back in, but this data model ensures the **data isolation** expected by enterprise companies, and allows each Organization to have **complete control** over the end users who are accessing their application.
Depending on how your application enforces and stores the relationships between users and groups, you will need to create one or many Stytch Member objects for every application user.
### Migrating from 1:1 data model
If your application already defines an end user as belonging to a single Organization, you will create one Member record for each existing user record in your system. Store the stable Stytch `member_id` on your internal representation of the user for easy queries.
For applications where the 1:1 data model is global, and an end user can *only* belong to one Organization at a time, keep in mind that Stytch does not have this restriction and is more of a "many 1:1 model". If your application relies on the assumption that a single email will only ever be associated with a single Organization, you will need to add in your own enforcement of this constraint before calling Stytch.
While we do recommend removing this restriction, as it can cause unnecessary friction for contractors or companies with multiple instances of your application, it is not necessary to do as part of your migration.
### Migrating from 1:many data model
If your application allows a single User record to be *shared* across multiple Organizations, that all reference the same UserID, you will create multiple Member objects for each User in your system -- one for each Organization that the end user is shared across.
If you already have a membership table storing the relationship between User\<>Organizations, you can add the Stytch `member_ids` to that record. However, keep in mind that in Stytch changing the email or name of one Member object will not impact the other Member records that share the original email and so it is important to keep this table in sync and/or ensure that all AuthN/AuthZ functionality looks to Stytch as the source of truth rather than this table.
A fairly common approach with customer is to migrate to Stytch with their existing data model in place leveraging a mapping table like the one described above, and then later on update their internal data model to be more consistent with an organization-tenancy model, improving the security and data governance controls for their enterprise customers.
Regardless of either scenario, you can leverage the following features to enrich the Stytch Member object:
* **Custom metadata**: Utilize the custom `trusted_metadata` and `untrusted_metadata` fields on the Member object to store any application or business-specific attributes for your end user and their membership.
* **Authentication and authorization settings**: Configure a Member's auth settings to control MFA enrollment, breakglass privileges, and roles and permissions.
Refer to the [Member object](/docs/api-reference/b2b/api/members/member-object) in the API reference to see all available fields and settings.
```js theme={null}
[
// These three Member objects are all the same end user
{
"member_id": "member-test-32fc...",
"organization_id": "organization-test-07971...",
"email_address": "adalovelace@stytch.com",
"email_address_verified": true,
"name": "Ada Lovelace",
"status": "active",
"untrusted_metadata": {
"job_title": "Engineer",
"preferred_locales": ["en", "es"]
}
},
{
"member_id": "member-test-83ei...",
"organization_id": "organization-test-08264...",
"email_address": "adalovelace@stytch.com",
"email_address_verified": true,
"name": "Miss Ada",
"status": "active",
"untrusted_metadata": {
"timezone": "EST"
}
},
{
"member_id": "member-test-65yu...",
"organization_id": "organization-test-02598...",
"email_address": "adalovelace@stytch.com",
"email_address_verified": true,
"name": "Ada",
"status": "active",
"untrusted_metadata": {
"display_theme": "dark"
}
}
]
```
## Other considerations
### Account deduplication
Stytch utilizes the email address as a unique primary key for identifying Members within an Organization. This means two things:
1. Two different Members cannot use the same email address within a single Organization.
2. Stytch will automatically consolidate Members who log into an Organization using the same email address with different auth methods. For example, an end user authenticates into Organization A using Email Magic Links and the email address `example@stytch.com`. Later, they authenticate into Organization A again with the same email address but this time using Google OAuth. Rather than create duplicate Member records, Stytch consolidates both auth instances under one Member object and `member_id`.
For this reason, you may need to merge your users' accounts before migrating to Stytch. If your script attempts to manually create two Members in an Organization in Stytch with the same email address, you will receive a `duplicate_email` error.
### Passwords
Stytch offers two different approaches to password authentication:
1. **Cross-organization passwords:** allows end users to use the same password across all Organizations they belong to under that email. This approach is best if you are migrating from a 1:many user to Organization data model, as it will allow a seamless transition to Stytch's organization-first approach without any impact to UX
2. **Organization-scoped passwords:** scopes the password to each individual Member object within a single Organization. This approach is best if your customers are highly security conscious, and want strong multi-tenant separation so that a password reset in another Organization does not impact their Member's authentication options
You can select which approach you want to use for your project in the Stytch Dashboard [Passwords Policy](https://stytch.com/dashboard/password-strength-config) page.
Please note that **you cannot switch password approaches if there are existing passwords in your project**, so make sure to update the setting to your desired approach prior to kicking off your migration to Stytch.
### Linking Stytch Members and Organizations back to your db
You should continue to maintain your own internal user and groups tables in your application, and link Stytch’s `member_id`s and `organization_id`s in your schema. You can also add your internal identifier references as a `trusted_metadata` field on the Stytch Member and the Stytch Organization record using metadata.
## What's next
With an understanding of how your user and organization data model will map to that of Stytch, see our guide to [migrate users and organizations to Stytch](/docs/resources/migrations/migrating-user-data#b2b-auth).
# Zero-downtime deployment
Source: https://stytch.com/docs/resources/migrations/zero-downtime-deployment
Strategies and best practices to deploy your Stytch auth integration to production.
This section covers strategies and best practices to deploy your Stytch auth integration to production. There are a few approaches when it comes to deployment.
## Option 1: Zero-downtime sessions deployment
We offer a Zero-Downtime Sessions feature which enables developers to mint Stytch Sessions from an external session (such as an Auth0 JWT). This solution is designed to facilitate a smooth **zero-downtime** cutover between Stytch and an external provider, by ensuring your end users stay logged into your application throughout the entire migration process. For this guide, we will use Auth0 as an example.
To enable this feature for your account, go to your Dashboard's [Migrations](https://stytch.com/dashboard/migrations). Enter your external provider's OIDC-compliant UserInfo endpoint in the form.
For Auth0, you can find the UserInfo URL in the application settings:
Grab your access token from your legacy provider. For example, with Auth0, you can use the [Authorization Code Flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow/call-your-api-using-the-authorization-code-flow) to grab the `access_token` (for example, with Auth0, this starts with **eyJ**).
```js theme={null}
{
"access_token": "eyJz39a...hOo5WwA",
"id_token": "eyJhb...Vp8hog",
"token_type": "Bearer"
}
```
After you have [migrated a user to Stytch](/docs/resources/migrations/migrating-user-data), call the [Migrate Session API](/docs/api-reference/consumer/api/sessions/migrate-session) with your external provider's access token / session token. Stytch will respond with a session you can associate with this user.
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/sessions/migrate \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"session_token": "eyJz39a...hOo5WwA"
}'
```
You should receive a response with a User object as well as a User session object usable with Stytch.
```js theme={null}
{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"user_id": "user-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"session": "{...}",
"session_token": "mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q",
"session_jwt": "eyJ...",
"user": "{...}"
}
```
After you have [created a Stytch member](/docs/api-reference/b2b/api/members/overview), call the [Migrate Session API](/docs/api-reference/b2b/api/sessions/migrate-session) with your external provider's access token / session token . Stytch will respond with a session you can associate with this member.
```curl theme={null}
curl --request POST \
--url https://test.stytch.com/v1/b2b/sessions/migrate \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"session_token": "eyJz39a...hOo5WwA",
"organization_id": "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
}'
```
You should receive a response with a [Member object](/docs/api-reference/b2b/api/members/member-object) as well as a [Member Session object](/docs/api-reference/b2b/api/sessions/session-object) usable with Stytch.
```curl theme={null}
{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"session_token": "mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q",
"session_jwt": "eyJ...",
"member": "{...}",
"organization": "{...}"
}
```
## Option 2: All-at-once deployment
An all-at-once deployment adheres to the following sequence:
1. Deploy your Stytch auth implementation to production and to all users at once.
2. Upon deployment, your users will require Stytch Sessions in your middleware / server-side logic. As a result, all users will be logged out and need to log back in.
3. Users log back in with the Stytch auth flow when they revisit your app.
An all-at-once deployment is the most straightforward. It is the best option when you're ready to move quickly as it removes any need to build secondary logic to aid in the migration.
## Option 3: Rolling deployment
A rolling deployment roughly adheres to the following sequence:
1. Deploy auth changes to a portion of your users at a time using a feature flag (IP, geography, other measures such as a cookie).
2. Users under the feature flag will see Stytch powered login flow and will need a Stytch Session to be considered logged in.
3. Users that are not feature flagged into Stytch will continue to see your legacy auth flow. These users will not be logged out and can continue using their existing legacy sessions.
4. In your middleware / server-side logic, you will need to determine if each JWT is from Stytch or your legacy flow.
5. You will also need to maintain two middleware routes to authenticate the session: one for Stytch's auth flow and another for your legacy auth flow.
This strategy requires a few bits of additional logic in your app to ensure a smooth transition:
* A feature flag within your authentication flow that bifurcates your user base between Stytch and your legacy authentication at a controllable percentage.
* If desired, you can set a cookie (or similar) on each user's device that tracks which authentication flow to present. You may also use factors such as IP or geography if you have access to that information.
* For new users that go through your sign up flow via Stytch, you will need to create a record in your legacy flow to handle the edge case where this user returns logged out on a different device and is presented with the legacy authentication UI.
A rolling deployment strategy allows you to quickly and easily monitor your integration with minimal user impact, and adjust your transition if necessary. It's also typically used when your application stack includes **mobile clients**, since you need to manage the user base on older versions of your application.
A rolling deployment roughly adheres to the following sequence:
1. Deploy auth changes incrementally across your user base by using feature flags to segment your users into discrete groups. You could divide your user base by IP, geography, and other measures like cookies. If your application only utilizes organization-specific login flows, you could create feature flags users by Stytch Organizations.
2. Users under the feature flag will see Stytch powered login flow and will need a Stytch Session to be considered logged in.
3. Users that are not feature flagged into Stytch will continue to see your legacy auth flow. These users will not be logged out and can continue using their existing legacy sessions.
4. In your middleware / server-side logic, you will need to determine if each JWT is from Stytch or your legacy flow.
5. You will also need to maintain two middleware routes to authenticate the session: one for Stytch's auth flow and another for your legacy auth flow.
This strategy requires a few bits of additional logic in your app to ensure a smooth transition:
* A feature flag within your authentication flow that bifurcates your user base between Stytch and your legacy authentication at a controllable percentage.
* If desired, you can set a cookie (or similar) on each user’s device that tracks which authentication flow to present. You may also use factors such as IP or geography if you have access to that information.
* For new users that go through your sign up flow via Stytch, you will need to create a record in your legacy flow to handle the edge case where this user returns logged out on a different device and is presented with the legacy authentication UI.
A rolling deployment strategy allows you to quickly and easily monitor your integration with minimal user impact, and adjust your transition if necessary. It's also typically used when your application stack includes **mobile clients**, since you need to manage the user base on older versions of your application.
## General deployment best practices
Stytch recommends to use environment variables (such as .env-live and .env-test) in your application for test and live Stytch keys. When using env files, the Stytch public token, project ID and secret map to your environment dynamically, preventing the common mistake of forgetting to change your API keys to your live set upon deployment to production.
# Resources
Source: https://stytch.com/docs/resources/overview
Connect with the Stytch community and stay plugged in to what our team is shipping.
Join our community channel and speak directly with a Stytch auth expert.
Check the system status and uptime of our services.
Stay up to date with our latest releases and product improvements from the Stytch team.
Read about software engineering and what goes on behind the tech scenes here at Stytch.
Check out our public SDKs and example apps.
Retrieve Stytch's compliance documents.
# Email deliverability
Source: https://stytch.com/docs/resources/policies/messaging/deliverability/email-deliverability
Learn about how Stytch handles and optimizes for email deliverability during our authentication flows.
While we optimize for deliverability, and the vast majority of Stytch emails are delivered successfully, there are still some exceptions.
Below are some common causes of email deliverability issues:
* The email address provided is inactive or outdated.
* The recipient's email inbox is full, or the email server is otherwise rejecting incoming messages.
* The user has email security software in place that delays or blocks emails sent from email addresses or domains that aren't explicitly allowed. This is most common in enterprise settings.
## Observability
You can check platform-wide email time-to-inbox on our status page at [status.stytch.com](http://status.stytch.com)! The data shown is the average time-to-inbox in milliseconds across the platform.
For per-request observability, Stytch emits logs with an `action` of `EmailDelivery` for email delivery events, which you can view in [Event Logs](https://stytch.com/dashboard/activity) in your Stytch Dashboard. Each log has a `message_status` field (displayed in the drop-down view of the log) which indicates the status of the email.
There may be more than one `EmailDelivery` event per email sent (grouped by identifiers like `request_id` and `user_id`) - for instance, the logs below show a `MagicLinksEmailSend` request with two corresponding `EmailDelivery` log events (one `delivered` and one `open`).
Possible `message_status` values are:
* `delivered` - The recipient's email server has acknowledged delivery of the email.
* `bounce` - The recipient's email server has "bounced" the email, or refused delivery and sent the email back. This can happen for a number of reasons, including the following:
* The recipient's inbox is full.
* The recipient's email address no longer exists or could not be found.
* The recipient's email server is offline.
* Automatic responses, including out-of-office replies can sometimes cause a`bounce`status.
* `spam_complaint` - The recipient manually marked or reported the email as spam.
* `open` - The recipient has opened the email after delivery.
* This may appear more than once if the recipient opens the email multiple times, or if certain email client features are enabled (for example, iOS [Mail Privacy Protection](https://www.apple.com/newsroom/2021/06/apple-advances-its-privacy-leadership-with-ios-15-ipados-15-macos-monterey-and-watchos-8/)).
* The absence of an `open` status does not necessarily mean the email was never opened - for example, if an email client blocks images from loading, opening emails will not trigger `open` events.
## Troubleshooting steps
#### 2XX (success) responses
**Delays in email arrival**:
1. Determine whether delays are occurring for all users and email domains, or just a subset.
* If delays are occurring for all users:
* If you're using a custom email domain or template, you may need to optimize the content of emails and the email sender domain reputation. More on this [here](#troubleshooting-tips).
* If you're not using custom templates and domains, or if you are but need some additional guidance, feel free to reach out to us at [support@stytch.com](mailto:support@stytch.com?subject=Email%20deliverability%20help).
* If this is just for one user, or a subset of users, the issue is likely related to the specific email client and security settings. Continue to step 2:
2. Check the Event Logs for a delayed email arrival.
* You can search Event Logs by `request_id` or `user_id` to see `EmailDelivery` actions pertaining to one of our email-based products.
3. In the Event Logs, determine when the email was marked as delivered by the recipient's email server.
* When viewing Event Logs for a particular request, click into `EmailDelivery` events to see the `message_status` and timestamps:
* If the email was marked as `delivered` shortly after the Stytch API request, but the email was delayed in its arrival to the inbox, this generally means that the email was successfully delivered to the mail server but was then delayed by the mail client.
* This can happen when the email gets "stuck" in the spam filter. If you're using a custom email domain or template, you may need to optimize the content of emails and the email domain reputation. We have a blog post around this subject [here](https://stytch.com/blog/landing-email-magic-links/), and Postmark has some great documentation on domain reputation [here](https://postmarkapp.com/guides/how-to-improve-domain-reputation-for-better-email-deliverability).
**Successful Stytch requests where the email doesn't appear in the user's inbox:**
1. Check Stytch logs for the user via [Event logs](https://stytch.com/dashboard/activity) in the Stytch Dashboard.
* If there's an issue preventing emails from being successfully triggered, you may see **Error** logs there.
* If you don't see any requests for that user (neither **Success** nor **Error** logs), there may be something preventing Stytch endpoints from being called at all for the user. In that case, we recommend taking a look at your own internal application logs to pinpoint the issue. If all requests appear to be successful, there may be something preventing your user's email software from displaying Stytch emails in the user's inbox.
2. Ask your user to check their spam folder.
* Stytch emails aren't often marked as spam, but it can happen in rare cases. If your user does find a Stytch email in their spam folder, ask them to mark it as 'safe' or 'not spam' to ensure that future emails reach their inbox.
3. Ask your user to add Stytch emails to their email sender allowlist.
* The specific process varies by email provider, but in some cases (particularly for enterprise email addresses), the user's email administrator may need to take this action for them. One example of this is [Microsoft's Tenant Allowlist](https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/create-safe-sender-lists-in-office-365?view=o365-worldwide#use-allow-entries-in-the-tenant-allowblock-list).
#### 4XX errors
* User hitting `invalid_email` errors:
* The user's email address is improperly formatted. Ask your user to make sure they're entering their email address correctly.
* User hitting `inactive_email` errors:
* If our email provider receives a hard bounce or a user-initiated spam complaint when attempting to send an email to a given email address, that email address will be marked as inactive, and future requests will result in `inactive_email` errors.
* If our email provider previously hit a hard bounce but the email address can now successfully receive emails, you can reactivate it via the [User management](https://stytch.com/dashboard/user-management) or [Members](https://stytch.com/dashboard/members) tab in the Stytch Dashboard.
* If the user previously marked a Stytch email as spam but is now interested in receiving Stytch emails again, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Email%20reactivation) so that we can reactivate their email address.
If you've completed the above troubleshooting steps and are still having trouble pinpointing the issue, we can check our email provider's logs and share the results with you (please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Email%20deliverability%20help) if interested). Note that once an email successfully reaches your user's email server, we no longer have visibility into why it didn't show up in their inbox.
## Using custom email domains
Email deliverability can be impacted when you configure a [custom email domain](/docs/resources/branding/custom-domains) instead of using the default Stytch sender address (e.g. `login@stytch.com`).
When considering sending emails from your own domain, be sure to weigh the tradeoff between having consistent branding and the risk of impacting your application's email deliverability.
### Guidelines
We generally recommend the following when considering using a custom email domain:
* Ensure you send a high enough email volume, so that email providers do not flag your domain as potential spam.
* Use a dedicated, separate email domain or subdomain for authentication emails to avoid issues with poor domain reputation.
### Troubleshooting tips
If your users are experiencing difficulties receiving emails for your application and Stytch's messaging provider is [reporting them as delivered](#troubleshooting-steps), there are several steps you can try in order to troubleshoot the issue and narrow down potential causes:
* Try using our default sender (e.g. `login@stytch.com`) instead with your existing email template, and see if the behavior improves.
* Try using our default email templates instead with your existing custom email domain, then tweak your custom email template as needed.
* Fall back to using both our default sender and email templates.
* Check common email domain tools like MXToolbox, Google Postmaster, etc. (More on this [here](https://postmarkapp.com/guides/how-to-improve-domain-reputation-for-better-email-deliverability).) You can also check the reputation of your sender IP with tools such as [this](https://servicecentral.trendmicro.com/en-US/ers/).
* If this happens with all of the users from a specific domain or organization, ask them to allowlist your custom domain in their email provider.
For more on domain reputation and customization, see our [blog post](https://stytch.com/blog/landing-email-magic-links/) around this subject, and [Postmark's documentation](https://postmarkapp.com/guides/how-to-improve-domain-reputation-for-better-email-deliverability) on domain reputation.
# Message deliverability overview
Source: https://stytch.com/docs/resources/policies/messaging/deliverability/overview
Learn about how Stytch handles and optimizes for message deliverability across all of our messaging channels.
Many of our products leverage messaging channels like email, SMS, and WhatsApp. Successful delivery of messages sent by Stytch is critical, and we persistently monitor deliverability metrics in order to ensure a best-in-class user experience.
This resource will discuss how we optimize for deliverability across different channels, what to expect when using each channel, and common troubleshooting steps for deliverability issues.
At Stytch, we think of deliverability as the percentage of attempted messages or emails that are successfully delivered to the user's email server or cell carrier network.
We calculate deliverability by leveraging status webhooks from our messaging providers. We also track latency from the time a message or email is sent to when it is delivered to ensure that we're always using the highest-performing provider at any point in time.
We take several measures across all messaging channels to optimize for deliverability:
* **Fallback messaging providers:** For each channel (SMS, WhatsApp, and email), we have a primary and secondary messaging provider. If deliverability drops below a certain threshold for a given provider, we automatically fall back to the secondary provider to ensure consistent deliverability.
* **Robust alerting and monitoring:** To support automated failover, we have robust monitoring in place for each distinct messaging provider and channel pair to ensure granular visibility into deliverability success. Status metrics differ in format amongst our different providers, but we've standardized our reporting into one cohesive channel.
* **Optimized templates and sender domains for SMS and email:** We've tailored our SMS and email templates for each of our products to ensure optimal deliverability. Our default sender emails (`login@stytch.com` and `login@stytchtest.com`) and SMS/WhatsApp numbers send large volumes of healthy messages and have accumulated a positive domain reputation over time.
# SMS and WhatsApp deliverability
Source: https://stytch.com/docs/resources/policies/messaging/deliverability/sms-whatsapp-deliverability
Learn about how Stytch handles and optimizes for SMS and WhatsApp message deliverability during our authentication flows.
Similar to email, even though the vast majority of Stytch SMS and WhatsApp messages are delivered successfully, exceptions can occur.
Below are some common causes of SMS and WhatsApp deliverability issues:
* The receiving number is invalid or inactive.
* The receiving device is currently turned off or is out of cell coverage.
* The phone number that Stytch messages are sent from was marked as blocked on the receiving device.
* The user's phone carrier receives the SMS but otherwise drops or delays the message.
## Troubleshooting steps
* User hitting `invalid_phone_number` errors:
* The phone number does not match the E.164 format: `[+][country code][phone number including area code]`. For example, `+15551234567`.
* The number matches the E.164 format but is still not a valid phone number (for example, the specified country code does not exist).
* Successful Stytch requests where the user does not receive the SMS or WhatsApp message:
* Check Stytch logs for the user via [Event logs](https://stytch.com/dashboard/activity) in the Stytch Dashboard. If there's an issue preventing SMS or WhatsApp messages from being successfully triggered, you may see **Error** logs there. If you don't see any requests for that user (neither **Success** nor **Error** logs), there may be something preventing Stytch endpoints from being called at all for the user. In that case, we recommend taking a look at your own internal application logs to pinpoint the issue. If all requests appear to be successful, there may be something preventing messages from reaching your user's device.
* Confirm that the user's mobile device is powered on and within cell coverage.
* Confirm that the user can receive SMS or WhatsApp messages from other numbers.
* Confirm that the user is on their home carrier network. Our messaging providers cannot always guarantee message delivery to devices roaming internationally or off-network.
* Confirm that the phone numbers used to send Stytch messages are not marked as blocked on the user's device. If you're not sure which phone numbers Stytch messages are sent from, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=SMS%2F%20WhatsApp%20deliverability%20help) for assistance.
If you've completed the above troubleshooting steps and are still having trouble pinpointing the issue, we can check our messaging provider's logs and share the result with you (please reach out to [support@stytch.com](mailto:support@stytch.com?subject=SMS%2F%20WhatsApp%20deliverability%20help) if interested). Note that once a message is successfully delivered to your user's phone carrier, we no longer have visibility into why it didn't reach their device.
# Messaging overview
Source: https://stytch.com/docs/resources/policies/messaging/overview
Stytch provides reliable and timely email, SMS, and WhatsApp messaging through our providers out-of-the-box, allowing you to spend less time setting up and managing deliverability in your authentication services.
When designing your applications and login flows which utilize messaging APIs, be sure to consider Stytch's [rate limits](/docs/resources/policies/platform/rate-limits).
## Email
By default, Stytch's built-in email messaging comes from the following senders:
* `login@stytch.com` for Live environments
* `login@stytchtest.com` for Test environments
The Test environment sender does not support production use and should be used for testing purposes only.
You can also configure emails to be sent from your own sender and domain, and [customize email templates](/docs/resources/branding/custom-email-templates).
## SMS and WhatsApp
SMS messages include your Project name in Live environments only.
SMS/WhatsApp messaging is only enabled for United States and Canada numbers by default. To enable additional countries, enable them in the [Country Code Allowlist](https://stytch.com/dashboard/country-code-allowlists).
We **strongly recommend** reading through our [toll fraud guide](/docs/resources/policies/messaging/toll-fraud) before enabling international SMS/WhatsApp messaging.
# SMS toll fraud
Source: https://stytch.com/docs/resources/policies/messaging/toll-fraud
Learn about SMS toll fraud (SMS pumping), how it works, and how to protect your app from it.
SMS toll fraud, sometimes known as SMS pumping, is a form of fraud where bad actors partner with complicit telecom providers to send large amounts of traffic to unprotected SMS endpoints.
While the fraud mechanism itself is complex, the implications are fairly simple: if you choose to use SMS One-Time Passcodes (OTPs) as an authentication method for your app, **you should take precautions to ensure that your SMS endpoints are protected from abuse**. While Stytch has some built-in precautions in place to help prevent this, you are ultimately responsible for the SMS or WhatsApp costs that your Stytch Project uses which can in cases of large attacks cost thousands of dollars.
## How Toll Fraud Works
The end goal of toll fraud is to generate revenue for the telecom operators that SMS vendors, like Twilio and MessageBird, pay to deliver SMS to end users. In SMS pumping attacks, fraudsters collude with telecom Mobile Network Operators (MNOs) in exchange for a share of the profits that MNOs receive from charging SMS vendors to deliver SMS messages to the MNO's users.
For fraudsters, the attack playbook for SMS pumping is as follows:
1. Find apps that expose a way to send SMS messages.
2. Use bots to send SMS messages to tens of thousands of phone numbers, often spoofing simple characteristics like IP address and User-Agent to avoid detection.
3. The fraudsters running the bots take a percentage of the inflated revenue received by MNOs who deliver the messages locally for third parties like Twilio.
This is a classic example of the game theory of application security in action: anytime an internet resource (such as SMS endpoints) that provides enough monetization potential is exposed, fraudsters will find a way to exploit it. It's the same reason compute platforms are commonly abused by crypto miners to receive free computation resources.
## Early warning indicators of SMS toll fraud
If you're experiencing a toll fraud / SMS pumping attack, you may notice one or many of the following factors:
### A sudden increase in SMS message quantity or velocity
Since the fraud is usually performed with bots, you'll notice a large number of messages being sent over a short period of time.
### Messages sent to consecutive phone numbers or a single geography
Often during toll fraud attacks, attackers will use 'blocks' of numbers that are consecutive or have similar prefixes, e.g. the first seven digits match. This also means that toll fraud tends to be concentrated in a single geography.
These similar numbers are likely all managed by the same mobile network operator, which is a telltale sign of SMS toll fraud.
### A low SMS send to authentication ratio
Since the intent of the attacker is not to takeover a user's account or create new accounts, only to send SMS messages, there will be no attempt to authenticate any of the OTP codes that are sent.
As a result, if a low percentage of SMS OTP users are actually authenticating, this may be an indicator of toll fraud.
This could also indicate true messaging downtime, i.e. messages aren't making it to end users, check out our guide on how to troubleshoot [SMS and WhatsApp messages](/docs/resources/policies/messaging/deliverability/sms-whatsapp-deliverability).
## How Stytch helps mitigate toll fraud across our platform
We take several steps to help prevent toll fraud on our platform above and beyond what our messaging providers offer.
### Rate limits
Since attacks tend to have common patterns like those mentioned above, we have several layers of rate limiting in place to mitigate the size and scope of toll fraud attacks across our platform.
Because toll fraud and real user traffic can sometimes look similar, e.g. a big launch to a new geo-locale, we balance their sensitivity to ensure that we won't ever block real user traffic.
### Custom country code allowlists
By default, new projects can only send SMS and WhatsApp OTPs to phone numbers in the US and Canada. To customize the countries to which your app can send SMS or WhatsApp OTPs, you can configure allowed country codes through the [Dashboard](https://stytch.com/dashboard/country-code-allowlists) or using [Programmatic Workspace Actions](/docs/api-reference/pwa/api/v3/country-code-allowlist).
### Alerting and monitoring
Our on-call team has robust alerting and monitoring in place across several factors to ensure that we're aware of and able to help mitigate manually if attackers have compromised your app.
***
While these protections are able to lower the impact of a toll fraud attack, they typically will not fully prevent them. We want to ensure that the balance tips in the favor of protecting your uptime and not preventing real users from logging into your app.
We usually see these built-in protections lower the impact of the attack by 75-90%. However, we still strongly recommend taking additional precautions to limit your risk as large attacks may generate thousands of dollars in SMS costs. **Note**, the SMS send attempts still occur from your app but Stytch will prevent them from being sent and thus you incurring the cost of sending an SMS.
# Unsupported countries for SMS and WhatsApp OTPs
Source: https://stytch.com/docs/resources/policies/messaging/unsupported-countries
A list of countries where Stytch does not support sending SMS or WhatsApp passcodes.
Stytch does not support sending SMS passcodes to the following countries.
If you attempt to add a country listed below to your country code allowlist, the API will return a `country_code_allowlist_invalid_country_codes` error.
We do support international SMS to countries not listed below, though international SMS is disabled by default for customers who did not use SMS prior to October 2023. To enable them, please add those countries to your Project's allowlist via the [Dashboard](https://stytch.com/dashboard/country-code-allowlists) or [Programmatic Workspace Actions](/docs/api-reference/pwa/api/v3/country-code-allowlist). Changing your project's allowlist will require [adding credit card details to your account](https://stytch.com/dashboard/settings/billing) to your account.
| Country | Alpha-2 | Prefix |
| - | - | - |
| Algeria | DZ | +213 |
| Afghanistan | AF | +93 |
| Albania | AL | +355 |
| Andorra | AD | +376 |
| Angola | AO | +244 |
| Antarctica | AQ | +672 |
| Armenia | AM | +374 |
| Aruba | AW | +297 |
| Azerbaijan | AZ | +994 |
| Bahamas | BS | +1242 |
| Bahrain | BH | +973 |
| Bangladesh | BD | +880 |
| Barbados | BB | +1246 |
| Belarus | BY | +375 |
| Belize | BZ | +501 |
| Bermuda | BM | +1441 |
| Bhutan | BT | +975 |
| Bosnia and Herzegovina | BA | +387 |
| Botswana | BW | +267 |
| Bouvet Island | BV | +47 |
| British Virgin Islands | VG | +1284 |
| Burma (Myanmar) | MM | +95 |
| Burundi | BI | +257 |
| Cambodia | KH | +855 |
| Cape Verde | CV | +238 |
| Cayman Islands | KY | +1345 |
| Chad | TD | +235 |
| China – including Hong Kong | CN | +86 |
| Comoros | KM | +269 |
| Congo, Dem Rep | CD | +243 |
| Cote D'Ivoire (Ivory Coast) | CI | +225 |
| Cuba | CU | +53 |
| Curaçao and Caribbean Netherlands | CW | +599 |
| Cyprus | CY | +357 |
| Democratic Republic of Congo | CD | +243 |
| Djibouti | DJ | +253 |
| Dominica | DM | +1767 |
| East Timor | TL | +670 |
| Egypt | EG | +20 |
| Equatorial Guinea | GQ | +240 |
| Ethiopia | ET | +251 |
| Eritrea | ER | +291 |
| Estonia | EE | +372 |
| Faroe Islands | FO | +298 |
| Fiji | FJ | +679 |
| French Polynesia | PF | +689 |
| French Southern Territories (the) | TF | +262 |
| Gabon | GA | +241 |
| Gambia | GM | +220 |
| Georgia | GE | +995 |
| Gibraltar | GI | +350 |
| Greenland | GL | +299 |
| Guadeloupe | GP | +590 |
| Guam | GU | +1671 |
| Guinea | GN | +224 |
| Haiti | HT | +509 |
| Heard Island and McDonald Islands | HM | +672 |
| Honduras | HN | +504 |
| Indonesia | ID | +62 |
| Israel | IL | +972 |
| Iran | IR | +98 |
| Iraq | IQ | +964 |
| Ivory Coast | CI | +225 |
| Jersey | JE | +44 |
| Jordan | JO | +962 |
| Kazakhstan | KZ | +7 |
| Korea Dem People's Rep | KP | +850 |
| Kosovo | XK | +383 |
| Kuwait | KW | +965 |
| Kyrgyzstan | KG | +996 |
| Laos PDR | LA | +856 |
| Lebanon | LB | +961 |
| Lesotho | LS | +266 |
| Liberia | LR | +231 |
| Libya | LY | +218 |
| Macau | MO | +853 |
| Macedonia | MK | +389 |
| Madagascar | MG | +261 |
| Malawi | MW | +265 |
| Malaysia | MY | +60 |
| Maldives | MV | +960 |
| Mali | ML | +223 |
| Martinque | MQ | +596 |
| Micronesia | FM | +691 |
| Moldova | MD | +373 |
| Mongolia | MN | +976 |
| Montserrat | MS | +1664 |
| Morocco | MA | +212 |
| Mozambique | MZ | +258 |
| Namibia | NA | +264 |
| Niue | NU | +683 |
| Nepal | NP | +977 |
| New Caledonia | NC | +687 |
| Niger | NE | +227 |
| Nigeria | NG | +234 |
| North Korea | KP | +850 |
| Northern Mariana Islands | MP | +1670 |
| Oman | OM | +968 |
| Pakistan | PK | +92 |
| Palestine | PS | +970 |
| Papua New Guinea | PG | +675 |
| Philippines | PH | +63 |
| Pitcairn | PN | +870 |
| Qatar | QA | +974 |
| Republic of North Macedonia | MK | +389 |
| Reunion/Mayotte | RE | +262 |
| Russia | RU | +7 |
| Rwanda | RW | +250 |
| Samoa | WS | +685 |
| Saudi Arabia | SA | +966 |
| Senegal | SN | +221 |
| Serbia | RS | +381 |
| Sierra Leone | SL | +232 |
| Singapore | SG | +65 |
| Solomon Islands | SB | +677 |
| Somalia | SO | +252 |
| South Georgia and the South Sandwich Islands | GS | +500 |
| Sri Lanka | LK | +94 |
| St Vincent Grenadines | VC | +1784 |
| Sudan | SD | +249 |
| Syria | SY | +963 |
| Tajikistan | TJ | +992 |
| Thailand | TH | +66 |
| Togo | TG | +228 |
| Tonga | TO | +676 |
| Tunisia | TN | +216 |
| Tuvalu | TV | +688 |
| Turkmenistan | TM | +993 |
| Turks and Caicos Islands | TC | +1649 |
| U.S. Virgin Islands | VI | +1340 |
| United Arab Emirates | AE | +971 |
| Uganda | UG | +256 |
| Uzbekistan | UZ | +998 |
| Vanuatu | VU | +678 |
| Venezuela | VE | +58 |
| Vietnam | VN | +84 |
| Wallis and Futuna | WF | +681 |
| Yemen | YE | +967 |
| Zambia | ZM | +260 |
| Zimbabwe | ZW | +263 |
| United States Minor Outlying Islands (the) | UM | |
Stytch does not support sending WhatsApp passcodes to the following countries.
If you attempt to add a country listed below to your country code allowlist, the API will return a `country_code_allowlist_invalid_country_codes` error.
| Country | Alpha-2 | Prefix |
| - | - | - |
| Antarctica | AQ | +672 |
| Bangladesh | BD | +880 |
| Bouvet Island | BV | +47 |
| Crimea | UA | +7978 |
| Cuba | CU | +53 |
| Egypt | EG | +20 |
| French Southern Territories (the) | YF | +262 |
| Heard Island and McDonald Islands | HM | +672 |
| India | IN | +91 |
| Iran | IR | +98 |
| Iraq | IQ | +964 |
| Indonesia | ID | +62 |
| Kazakhstan | KZ | +7 |
| Kuwait | KW | +965 |
| Malaysia | MY | +60 |
| Morocco | MA | +212 |
| Nepal | NP | +977 |
| Nigeria | NG | +234 |
| North Korea | KP | +850 |
| Oman | OM | +968 |
| Pakistan | PK | +92 |
| Pitcairn | PN | +64 |
| Saudi Arabia | SA | +966 |
| South Africa | ZA | +27 |
| South Georgia and the South Sandwich Islands | GS | +500 |
| Sri Lanka | LK | +94 |
| Syria | SY | +963 |
| Ukraine | UA | +380 |
| United Arab Emirates | AE | +971 |
| United States Minor Outlying Islands (the) | UM | |
# Account enumeration
Source: https://stytch.com/docs/resources/policies/platform/account-enumeration
Learn how to prevent account enumeration attacks and enhance the security of your platform with Stytch's comprehensive guide.
An account enumeration attack occurs when a bad actor tries to identify valid users, emails, etc. within an app's authentication flow. Account enumeration is primarily used to collect information about a system that can be used in further attacks, and does not directly result in compromised accounts.
## Example scenario
In order for account enumeration to occur, your app must expose some information about a user's state. For example, the [Email Magic Links Send endpoint](/docs/api-reference/consumer/api/magic-links/via-email/send-magic-link) can only send emails to existing Users. That means if you leverage this endpoint on a signup form and a bad actor enters an arbitrary email address, the Send endpoint will return a `200` success response if that email address exists as a user in your app, and a `404` error response if that email address does not exist in your app.
To the bad actor, this exposes that an email address belongs to a user of your app and the bad actor may leverage this information in a subsequent attack to phish or compromise this user.
On the other hand, using the [Email Magic Links Login or Create endpoint](/docs/api-reference/consumer/api/magic-links/via-email/log-in-or-create-user) will always send an email and will always return a consistent response, which does not reveal whether the email address already existed in the application or not.
## Recommendations
Generally, account enumeration does not represent a major risk to your application, non-sensitive consumer applications carry very little risk from account enumeration. In fact, account enumeration via useful and actionable error messages is often helpful for consumer login flows. For example, telling a user that their login failed because they do not have an account is a better user experience than presenting a generic failure message.
However, if your domain is particularly sensitive to security (e.g. government, fintech, healthcare etc.) or your application is at a large enough scale, we do recommend you take a few steps to protect your against account enumeration.
* Don't use error messages that give away key information about why a particular action failed. For example, if an actor initiates a password reset for an account that does not exist, don't show an error message. Instead, show a message to the effect of: "If an account is associated with that email, a reset link has been sent."
* Don't expose unmasked Personally Identifiable Information (PII); e.g. only display the last four digits of a phone number in your UI as opposed to the full value.
* Limit how much user state you share with your frontend (user-facing client).
* If you're using our frontend SDKs, the Send methods will error if the user hasn't been pre-created. If this is critical, consider enabling **Opaque error messages** for your Project [in the Dashboard](https://stytch.com/dashboard/sdk-configuration), which prevents the Send methods from returning an error if the user hasn't been created. Alternatively, consider disabling the Send methods and exclusively using the LoginOrCreate methods, or using our API directly from your backend.
# App store reviews
Source: https://stytch.com/docs/resources/policies/platform/app-store-reviews
Learn more about the app store review process when using Stytch's authentication and fraud products.
If you're planning to release an app in the Apple App Store or Google Play Store, you'll need to complete Apple or Google's app review process.
If you offer Passwords as an authentication method, the authentication-related step of the app review process should be fairly straightforward – you'll simply need to provide the app store reviewers with an email address and password combination that will log them into a test account. If you're using our B2B product suite, the test account should belong to a test Organization that you create for this purpose. You'll be able to share the test email address and password when filling out your app details during the review process.
## Passwordless authentication
It's also possible to pass app store reviews while offering exclusively passwordless authentication methods.
### Email-based authentication
For email-based authentication methods, this can generally be accomplished by leaving a note for the app store reviewer describing how they can use their own email address to log in. The exact wording will depend on the specific authentication products that you offer in your application.
Note: if you offer any OAuth login methods (like Google OAuth or Facebook OAuth, for example) in your iOS application, in most cases, you must also offer a login method that allows users to keep their email address private (like Apple OAuth) in order to comply with [Apple's guidelines](https://developer.apple.com/app-store/review/guidelines/#login-services) (with certain exceptions).
### Phone number-based authentication
For phone number-based authentication methods, reviewers are sometimes willing to use their own phone numbers in order to authenticate as well. However, if your application is rejected because the reviewer was not willing to use their own phone number, we generally recommend the following strategy as a backup:
1. Set up a programmable SMS inbox via a service like [Twilio](https://www.twilio.com/) or [Mailosaur](https://mailosaur.com/).
2. Forward OTP codes sent to the programmable inbox to a Google Sheet (or other similar shareable resource). [Twilio provides a helpful resource](https://www.twilio.com/code-exchange/log-incoming-text-messages-in-google-sheets) demonstrating how to set this up.
3. Include a link to the Google Sheet in your app store review notes, along with the phone number that corresponds to the programmable inbox. That way, app store reviewers can enter the phone number in your app and retrieve the OTP code through the Google Sheet.
If any of the above strategies are not accepted by your reviewer, please reach out to us at [support@stytch.com](mailto:support@stytch.com?subject=Help%20with%20app%20store%20review), and we'll be happy to recommend some alternative strategies for your unique auth setup.
## Device fingerprinting
We've designed our [Device Fingerprinting (DFP)](/docs/fraud-risk/device-fingerprinting/overview) product to comply with Apple and Google's guidelines. However, if you're using our DFP product, you should ensure that your privacy policy accurately reflects the data collected from the end user and corresponding device(s) before completing your app store reviews.
We have seen instances where our customers' privacy policies submitted as part of these review processes do not account for information collected and reported as necessary for our DFP solution to function, which has resulted in delayed approvals. As such, with consultation from your legal and engineering teams, we strongly suggest reviewing and updating your privacy policy to clearly articulate to your end users (and app store reviewers) the types of information collected, methods for doing so, and how that information is used.
We have found success with app store submissions with privacy policies that include the below sample disclosure. However, this should not be taken as strict advice that you must follow with regards to updating your privacy policy, but rather as a suggestion for content to include.
We and our partners may automatically process information related to you, your computer or device, your interaction with our Services, our communications and other online services.
Data collected may include but not limited to: device data, such as your device's type, operating system type and version, settings, manufacturer and model, browser type, screen resolution, RAM and disk size, CPU usage, IP address, language settings, mobile device carrier, general location information such as city/state/geographic area, and other unique identifiers.
We may use the following tools for automatic data collection:
* Cookies, which are text files that websites store on a visitor's device to uniquely identify the visitor's browser or to store information or settings in the browser for the purpose of helping you navigate between pages efficiently, remembering your preferences, enabling functionality, helping us understand user activity and patterns, and facilitating online advertising.
* Local storage technologies, like HTML5, that provide cookie-equivalent functionality but can store larger amounts of data, including on your device outside of your browser in connection with specific applications.
* Web beacons, also known as pixel tags or clear GIFs, which are used to demonstrate that a webpage or email was accessed or opened, or that certain content was viewed or clicked.
If you need any additional assistance preparing for app store reviews while using our DFP product, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Help%20with%20app%20store%20review).
# IP validation
Source: https://stytch.com/docs/resources/policies/platform/ip-validation
Learn how to specify a list of IP addresses to ensure only your servers are able to access the Stytch API.
You can specify up to 10 IPs that your requests must come from to ensure that only your servers are able to access the Stytch API. By default we allow all IPs.
If you'd like to add IP validation, please send an email to [support@stytch.com](mailto:support@stytch.com) with the subject "IP Validation" with your live project ID and the IPs you'd like to allow (max 10 IPs).
# Rate limits
Source: https://stytch.com/docs/resources/policies/platform/rate-limits
Learn about how Stytch treats rate limits.
Stytch enforces rate limits across our API endpoints for a few reasons:
1. **To protect our infrastructure and offer the best reliability possible**
* As an example, an unintentional bug in a customer's code that calls our [Create User endpoint](/docs/api-reference/consumer/api/users/create-user) many times within a short period could affect our own infrastructure as well as result in unintended user states.
2. **To limit the surface area for common attack vectors from bad actors**
* For example, we have baseline rate limiting in place for our endpoints that trigger an SMS OTP code to be sent to offer a first line of defense against [toll fraud attacks](/docs/resources/policies/messaging/toll-fraud).
3. **To satisfy security requirements**
* For instance, without any rate limiting on our [Authenticate OTP endpoint](/docs/api-reference/consumer/api/otp/authenticate), OTP codes could be brute forced.
Rate limit enforcement depends on a number of factors - this resource will describe the general axes used to determine and enforce rate limits and what to expect when building with Stytch.
**User locks:** Stytch also offers built-in [User locks](/docs/resources/policies/platform/user-locks), which are distinct from rate limits. You can use User locks to customize how many consecutive failed authentication attempts should result in a user's account being locked, how long the account should remain locked, and whether or not users should be able to unlock their account themselves.
## How rate limits work at Stytch
Rate limits at Stytch have three key components that determine rate limit enforcement:
1. **Field:** The unique field or identifier that is used to determine rate limits - for instance, a specific email address or a Stytch `user_id`. These range in scope - for example, Stytch rate limits the number of SMS OTP Send calls on phone number and phone number prefix (e.g. country code), but the number of Create User requests on the Stytch `project_id`.
2. **Interval:** The period of time over which this rate limit is assessed, e.g. second, minute, hour, day. Once a rate limit is hit, the relevant time interval will need to pass before the user is allowed to try again. Note that intervals are not rolling, but are top of the hour.
3. **Endpoint:** The Stytch API endpoint(s) that the rate limit is assessed on. Some of our rate limits aggregate on more than one endpoint - for instance, we restrict Email Magic Link's [Send](/docs/api-reference/consumer/api/magic-links/via-email/send-magic-link), [Login or Create](/docs/api-reference/consumer/api/magic-links/via-email/log-in-or-create-user), and [Invite](/docs/api-reference/consumer/api/magic-links/via-email/invite) endpoints to one request per second per email across all three endpoints to avoid collisions, as they all initiate an email sent to the end user.
### Example scenario
Stytch rate limits the number of SMS OTP codes that can be sent from Test environments in order to prevent bad actors from signing up and initiating SMS spam attacks. Our current rate limit for our endpoints that send an SMS OTP code is **60/hour/Project** in Test environments only.
If 60 SMS OTP requests are made in an hour from a single Stytch project, the 61st will result in a [429 error](/docs/api-reference/consumer/api/errors/429#too%5Fmany%5Frequests). At the top of the next hour, the rate limit will be reset and new SMS OTP requests will no longer result in an error.
## Building for rate limits
We designed our rate limits at Stytch such that legitimate user traffic shouldn't result in rate limiting, but it's important to keep them in mind when building out user flows to avoid patterns that could result in rate limiting.
Once a rate limit threshold has been reached, it generally cannot be reset or changed for the particular field or user that was rate limited, so it's important to keep a few things in mind when building with Stytch to avoid unwanted rate limit breaches.
### UI considerations
Whenever possible, we recommend building rate limit protections into your application's UI.
For example, if you utilize our Email OTP authentication flow, we generally recommend baking in a reset timer to prevent users from initiating many Email OTP codes in short succession.
Below is an example code snippet showing an exponential cooldown timer for a button that sends an OTP code:
```javascript theme={null}
const handleOTPClick = () => {
// Disable the button when it's clicked
setOTPButtonDisabled(true);
// Calculate the exponential backoff time based on the retry count
const backoffTime = Math.pow(2, retryCount) * 30000; // In milliseconds
// Increment the retry count
setRetryCount(retryCount + 1);
// Enable the button after the calculated backoff time
setTimeout(() => {
setButtonDisabled(false);
}, backoffTime);
};
```
### Testing and debugging flows
Our rate limits at Stytch are designed such that normal user traffic should generally not run into rate limits in our **Live** environment. Your various **Test** [environments](/docs/api-reference/consumer/api/overview#environments) are built to support iteration and testing, and have different rate limits than our **Live** environment.
Building out testing flows in one of your **Test** environment rather than our **Live** environment can help to avoid running into rate limits. For instance, if you're writing end to end tests which will involve authenticating many times in a row to test various application flows, we recommend doing so in one of your **Test** environments rather than **Live**.
### Authentication flow design
Utilizing more than one primary auth factor in your authentication flow can offer a fallback authentication option in case one primary factor is unavailable, either due to rate limits (e.g. repeated password authentication attempts) or any other reason.
Fallback authentication methods should generally offer a mix of different products entirely (e.g. SMS OTP and Email Magic Links) and/or different delivery methods (e.g. SMS and Email OTP). Doing so is protective against not just rate limits, but also the loss of a device or temporary inaccessibility.
For example, if an SMS OTP is [failing to deliver](/docs/resources/policies/messaging/deliverability/overview) because the phone is currently out of its service coverage area, offering an alternative method of delivery, such as Email or WhatsApp OTP, or an alternative authentication factor, such as Passwords, can help avoid a situation where a user makes many successive calls to send an SMS OTP because it is the only authentication method available.
## Handling unexpected 429 errors
If you notice or receive reports of 429 errors, a good place to start is in the [Event logs](https://stytch.com/dashboard/activity) of your Stytch Dashboard to investigate and gather context.
Event logs can show the series of events that led up to a 429 error, which can be helpful in determining mitigation strategies to prevent future occurrences.
We don't return detailed information about the specific rate limit hit in our 429 `too_many_requests` error response, but if you receive this error unexpectedly and during a legitimate user flow, feel free to reach out to [support@stytch.com](mailto:support@stytch.com) for more information on why the rate limit was enforced.
# User locks
Source: https://stytch.com/docs/resources/policies/platform/user-locks
Learn about how Stytch handles user account locking mechanisms.
User Locks is a security feature that automatically locks user accounts after a specified number of failed authentication attempts. This helps protect against brute force attacks and unauthorized access attempts, providing a cooldown period before further attempts can be made.
## Overview
When a user fails to authenticate multiple times in a row, their account will be temporarily locked and future authentication attempts will be denied. While a lock is not active, the counter is reset upon a successful authentication attempt. Users can be unlocked in one of three ways:
* The lock expires
* The user contacts support, who then manually removes the lock via the Dashboard
* A project may optionally have Stytch send an email allowing users to unlock their account themselves
The lock duration and number of allowed failed attempts are configurable at the project level via the Dashboard in the [Password and User Policies](https://stytch.com/dashboard/password-strength-config) page.
## User lock TTL
The duration that the user is locked out once a lock is created. User locks can have a duration between 5 minutes through 7 days. The default lock duration is 1 hour. This can be configured with the Project-level setting **User Lock TTL**.
## User lock threshold
The specified number of consecutive failed authentication attempts before a user lock becomes active. This value can range between 1 through 100. The default value for this is 10. This can be configured with the Project-level setting **User Lock Threshold**.
## Self-service unlocking
This indicates whether you want users to be able to manually unlock themselves via an Email Magic Link or not. When enabled, Stytch will send an email to the User containing a link that will allow the user to unlock themselves. This setting is **off** by default. This can be configured in the Dashboard with the Project-level setting **Allow self serve unlock**.
To use self-serve unlocking, projects will need to have a default redirect URL set up. For Consumer applications, this needs to be of the Login type. For B2B applications, this needs to be either a Login or Discovery type.
Unlock emails can be customized with [custom email templates](/docs/resources/branding/custom-email-templates).
## Best practices
1. Set appropriate thresholds for failed attempts based on your security requirements
2. Understand how long you want locks to last
3. Monitor lock events for potential security threats
4. Consider whether you want to support self-service unlocking or not
# Data compliance
Source: https://stytch.com/docs/resources/policies/security-and-trust/compliance/data-compliance
Learn about Stytch's compliance with GDPR and Data Privacy Framework requirements.
The information below pertains to how Stytch by Twilio handles data in accordance with the GDPR.
Consult your legal and compliance team to determine the exact impact on your business.
The information on this page does not, and is not intended to, constitute legal advice; instead, all information is for general informational purposes only.
## What is GDPR?
The General Data Protection Regulation (GDPR) is a comprehensive European Union law that dictates how organizations collect, process, and protect personal data. It grants individuals greater control over their personal information and enforces strict privacy and security standards globally for any business that handles the data of EU residents.
## How Stytch complies with GDPR
Stytch complies with GDPR directives and is committed to helping you comply with GDPR. Our key measures include:
* **Data Processing Agreement (DPA)**: We offer [a DPA](https://www.twilio.com/en-us/legal/data-protection-addendum) that incorporates Standard Contractual Clauses (SCCs) for lawful cross-border data transfers.
* **Technical and Organizational Measures (TOMs)**: We implement robust security measures to protect customer data.
* **End-user Data Management**: We support deleting user records (right to be forgotten); data deletion requests can be made to [support@stytch.com](mailto:support@stytch.com).
### Data residency
For most B2B SaaS companies, a **Data Residency** approach - where core customer data is stored regionally but processed globally - is standard and typically sufficient. In this model, authentication data, i.e. what is stored within Stytch, is almost always explicitly excluded from regional requirements.
This means when using Stytch for your auth flow, you generally do not need a regionally isolated auth deployment. A global auth configuration paired with regional storage for your proprietary app data will satisfy typical compliance demands, while preserving a unified developer experience and enabling seamless user features like centralized login and cross-region organization switching.
However, if you operate in highly regulated industries or process sensitive, unstructured data, you may face demands for strict **Data Localization**, where *all* data, including authentication data, must be stored, processed, and served entirely within a specific region.
Because Stytch operates exclusively out of U.S. servers, achieving this level of rigid localization with their service is not possible. Therefore, your strategy hinges on deeply validating your pipeline's specific compliance requirements: If your enterprise prospects accept the standard carve-out for auth data, Stytch remains a great fit, but if they mandate absolute regional isolation, Stytch's centralized infrastructure will be a hard blocker.
## Data Privacy Framework (DPF)
Stytch self-certifies compliance with the Data Privacy Framework requirements for data transfers from the EU, United Kingdom, and Switzerland to the United States.
Key measures Stytch has in place include:
* **Public Self-Certification**: Stytch has publicly committed to the DPF Principles and is listed on the official [DPF participant list](https://www.dataprivacyframework.gov/list).
* **Privacy Policy**: Our [DPA](https://www.twilio.com/en-us/legal/data-protection-addendum) explicitly outlines our DPF commitments, data processing practices, and provides information for individuals to contact us regarding privacy concerns.
* **Third-Party Management**: We require all third-party vendors handling EU, UK, or Swiss personal data on our behalf to provide equivalent data protections.
* **User Rights**: We provide mechanisms for individuals to access, correct, or request deletion of their personal information.
* **Dispute Resolution**: We offer independent recourse mechanisms for addressing unresolved privacy complaints, as required by the DPF.
* **Security Measures**: Robust technical and organizational security measures are implemented to protect all customer and end-user data.
# Overview
Source: https://stytch.com/docs/resources/policies/security-and-trust/compliance/overview
Retrieve Stytch's compliance documents.
You can find Stytch's compliance documents, including our SOC 2 report, ISO 27001 certification, CAIQ, PCI-DSS, and more in the [Twilio Trust Center](https://security.twilio.com/).
Be sure to look for the Stytch-specific documents, whose names start with "Stytch".
# Privacy
Source: https://stytch.com/docs/resources/policies/security-and-trust/privacy
# Security
Source: https://stytch.com/docs/resources/policies/security-and-trust/security
# System Status
Source: https://stytch.com/docs/resources/system-status
# API Keys
Source: https://stytch.com/docs/resources/workspace-management/api-keys
To use the Stytch API and SDKs, you need a Stytch account; get your API keys by [signing up](https://stytch.com/start-now).
The Stytch API uses [basic authentication](https://en.wikipedia.org/wiki/Basic%5Faccess%5Fauthentication) with your `project_id` and `secret`. You'll need to pass these values into the Authorization request header for every API call.
```bash theme={null}
curl --request POST \
--url https://test.stytch.com/v1/magic_links/email/login_or_create \
-u 'PROJECT_ID:SECRET' \
-H 'Content-Type: application/json' \
-d '{
"email": "sandbox@stytch.com",
}'
```
You can find your keys in the **Project ID & API keys** section in your [Stytch Dashboard](https://stytch.com/dashboard).
## Frontend SDKs
For our frontend SDKs, you'll use your `public_token` rather than your `project_id` and `secret` when communicating with the Stytch API.
## Environments
Each Stytch Project has 1 **Live** environment and 1 **Test** environment by default. You can create additional Test environments in the Stytch Dashboard, or via the [Programmatic Workspace Actions API](/docs/api-reference/pwa/api/v3/environments/environment-object).\
Each environment has a distinct Project ID, Secret, and Public token.
* Test environments access the `https://test.stytch.com` API
* Live environments access the `https://api.stytch.com` API
The Stytch API keys and data resources are all scoped per-environment and are *not* shared between environments. For example, a User created in one environment will not be available in another environment.
# Dashboard Roles and Permissions
Source: https://stytch.com/docs/resources/workspace-management/dashboard-roles-and-permissions
Stytch's dashboard can be used for managing your integration configuration, debugging via event logs and DFP analytics, or performing customer support actions like resetting passwords.
To support this range of use cases, Stytch offers the following user roles in our dashboard:
* **Admin:** Able to perform all actions across the Dashboard, including managing Live API Keys.
* **Developer:** Able to manage project configurations (excluding User Impersonation configuration), as well as write access to all core entities. Can view and create API Keys in Test, but not in Live.
* **Support Manager:** Read access to project configurations and event logs, and write access to project Users, Members, Organizations (including managing SSO and SCIM connections), and Device Fingerprinting rules.
* **Support Agent:** Read access to project Users, Members, Organizations and event logs. Able to take non-destructive write actions on Users/Members, such as resetting passwords and revoking sessions.
* **No Access:** The "invited" state, no functional access within the Dashboard. Can be used to provision users without yet granting them a higher-privilege role.
If you enable Just-in-Time (JIT) Provisioning to allow users from a specific email domain (i.e. `stytch.com`) to discover and join your workspace without an explicit invite, those users will be assigned the **Support Agent** role by default. You can override this default by configuring an **automatic role assignment** for the email domain, which will apply to all workspace members with verified emails from that domain.
## Detailed Permissions
| **Permission** | **Admin** | **Developer** | **Support Manager** | **Support Agent** | **No Access** |
| - | - | - | - | - | - |
| Manage Workspace Members | ✅ | | | | |
| Manage Workspace Settings | ✅ | | | | |
| Manage Workspace Billing | ✅ | | | | |
| Manage Live API Keys | ✅ | | | | |
| Manage Test API Keys | ✅ | ✅ | | | |
| Create Projects | ✅ | ✅ | | | |
| Delete Projects | ✅ | | | | |
| Create Test Environments | ✅ | ✅ | | | |
| Delete Test Environments | ✅ | | | | |
| Manage Project Configurations | ✅ | ✅ | | | |
| Manage User Impersonation Configuration | ✅ | | | | |
| View Project Configurations | ✅ | ✅ | ✅ | | |
| Manage M2M Clients | ✅ | ✅ | | | |
| View M2M Clients | ✅ | ✅ | ✅ | | |
| Manage Users, Members and Organizations | ✅ | ✅ | ✅ | | |
| View Users, Members and Organizations | ✅ | ✅ | ✅ | ✅ | |
| Impersonate Users and Members | ✅ | ✅ | ✅ | | |
| Manage DFP Rules | ✅ | ✅ | ✅ | | |
| View DFP Rules | ✅ | ✅ | ✅ | ✅ | |
| View DFP Analytics | ✅ | ✅ | ✅ | ✅ | |
| View Event Logs | ✅ | ✅ | ✅ | ✅ | |
| Trigger Password Resets | ✅ | ✅ | ✅ | ✅ | |
| Revoke Sessions | ✅ | ✅ | ✅ | ✅ | |
| Reactivate Emails | ✅ | ✅ | ✅ | ✅ | |
# Event Log Streaming
Source: https://stytch.com/docs/resources/workspace-management/event-log-streaming
Stream your Stytch event logs to your preferred observability platform.
Event log streaming allows you to send the same activity logs already available in the Dashboard in real-time to your destination of choice. This enables easier debugging and monitoring of your authentication flows. This feature is particularly useful if:
* You are already using other tools or vendors for observability and monitoring purposes.
* You have different retention requirements than the default 30-day retention window Stytch provides.
* You want to set up monitoring and alerting side-by-side to your existing monitoring tooling.
We currently support:
* Streaming to a single destination per project
* Supported destinations are **Datadog** and **Grafana Loki**
If you'd like us to support more destination types, please [contact us](https://stytch.com/contact).
## Configuring streaming
You can configure event log streaming through the Dashboard or our management API. Furthermore, you can enable and disable streaming without having to delete your destination configuration. Note that re-enabling after disabling streaming will not backfill for the length of time it was disabled. Only new event logs will be streamed.
### Dashboard
Under the [Activity](https://stytch.com/dashboard/activity?tab=stream-settings) tab in the Stytch dashboard, navigate to Stream Settings:
1. Select which supported destination type you want to stream logs to
2. Fill in the required information. See below for specifics for each destination type.
3. Save the changes
Once a configuration is stored, you **must** enable streaming and save that change. Logs should start flowing shortly afterwards.
Navigate to one of the guides below for an overview of what inputs you need to configure event log streaming.
### Management API (Programmatic Workspace Actions)
This is available via a direct HTTP API, our Go SDK, or our [Terraform provider](https://registry.terraform.io/providers/stytchauth/stytch/latest/docs/resources/event%5Flog%5Fstreaming). See the docs on [PWA](/docs/api-reference/pwa/api/v3/overview) for more information.
## Destinations
### Datadog
For Datadog, you will need the following information:
1. **Datadog Site (Region):** This determines the Datadog endpoint Stytch will send logs to. You can find the available options in [this list](https://docs.datadoghq.com/getting%5Fstarted/site/), as well as information to see which one you are using.
2. **A Datadog API Key:** This key allows Stytch to authenticate with Datadog and send event data to your account. The key is typically a 32-character string using the characters 0-9 and a-f only.
#### Getting a Datadog API Key
You can create a Datadog API Key so that Stytch can send event logs to your Datadog instance. You can use an existing one if you'd like to, but we recommend creating a new one.
1. Sign in to your Datadog account
2. Navigate to the [API Keys](https://app.datadoghq.com/organization-settings/api-keys) page within your Organization Settings
3. Click on "New Key". Give it a name and create the key. Store the key in a safe place
*Note: You will also get a key ID. This is only an identifier for the key. This string is not equivalent to the API Key, and you do not need to provide this to us.*
If you are re-using a key or want to check that your API and site are correct, you can call Datadog's validate endpoint ([docs](https://docs.datadoghq.com/api/latest/authentication/#validate-api-key)). Just make sure to edit the site URL to the one that is relevant to you.
```bash theme={null}
# For the US site, the site URL is app.datadoghq.com
curl -X GET "https://app.datadoghq.com/api/v1/validate" \
-H "Accept: application/json" \
-H "DD-API-KEY: ${DD_API_KEY}"
```
#### Logs in Datadog
The logs will have the following Datadog-specific metadata:
* `source: stytch`
* `service: stytch.auth`
## Grafana Loki
Streaming to a Grafana Loki backend is supported. Keep in mind that this **must** be a Loki backend. We do not support forwarding to Prometheus metrics or any other Grafana-owned service.
1. **Loki HTTP API hostname:** This should be the hostname of a Loki log ingestion API endpoint that is accessible to the public internet. Specifically, we'll be hitting the Loki HTTP API documented on the [Grafana Labs Loki HTTP API](https://grafana.com/docs/loki/latest/reference/loki-http-api/).
* Your URL for ingestion should look something like: `https://{hostname}/loki/api/v1/push`
* **Note**: The https protocol and the endpoint path are static and added by Stytch. These cannot be modified.
2. **Authentication credentials:** We require Basic Auth for authentication. Therefore we need the following information:
* `username`
* `password`
**Note:** The user must have permissions to write logs to Loki.
#### Getting Grafana Loki Credentials (Grafana Cloud)
You'll need to set up a new access policy and token for your account.
**Getting a hostname and username**
1. Go to your Grafana Administration Cloud Portal
2. Click on your stack. This will show you the "Manage your stack" page
3. Click on Loki -> Send Logs
4. You will see a box with your auth settings. This will have Name, URL, Basic Auth (checked), User, and Password
5. Your hostname is the URL **without** the protocol. For example, logs-prod-021.grafana.net
6. The username is the string in the User field
**Getting a password (token)**
1. In the same Grafana Administration Cloud Portal, click on "Access Policies" in the Security section
2. Create a new access policy or reuse an existing one. Set up the realm and scope that you need for your setup. To forward logs, you need logs:write
3. Once created, click on "Add token" and give it a name
4. You will get a token. This is the password used in authentication. Copy this and store it in a safe place.
#### Logs in Grafana Loki
Logs will be sent in gzipped compressed JSON format, which is supported by Grafana Loki's [HTTP push endpoint](https://grafana.com/docs/loki/latest/reference/loki-http-api/#ingest-logs).
The logs will have 2 additional labels. These can be used to filter for only these logs when querying Loki.
* source: stytch
* service\_name: stytch.auth
### Loki to S3 bridge
Even though Stytch does not yet support direct S3 forwarding, you can achieve this by creating a service compatible with the Loki ingestion endpoint and setting up relevant infrastructure on your end. The open source repository [stytch-loki-ingestion-example](https://github.com/Essential-Personnel/stytch-loki-ingestion-example) has an example of how you can do this with AWS infrastructure.
***Note: This repository is not owned, maintained, or audited by Stytch. This is entirely owned by a third-party, who agreed with Stytch sharing it for others to use.***
# Event Logs
Source: https://stytch.com/docs/resources/workspace-management/event-logs
Understand and debug your authentication flows using Stytch Event Logs.
## Dashboard
You can find Event Logs in the [Stytch Dashboard](https://stytch.com/dashboard/activity). Event logs should be available within a minute upon request completion and are currently retained for 30 days. For longer retention or external analysis, we recommend enabling event log streaming.
## Event log streaming
Event logs can be streamed to different observability platforms. Currently Datadog Logs and Grafana Loki are the only supported destinations. For more information, see the [Event Log Streaming](./event-log-streaming).
## Schema
### Basic metadata
All events have the following fields:
* `event_id`: A unique identifier of the individual event
* `project_id`: The project ID related to this event
* `action`: A string that corresponds to a Stytch API request or an admin action. See below for more details
* `status`: The status for a given action tied to this particular event. See below for more details
* `request_id`: A unique identifier for the API request this event belongs to. Multiple events can share a request\_id
### Other metadata
Most events will have additional metadata specific to that action. For example:
* Most successful project-specific API calls will have `project_name` (start events do not have a project name)
* Events related to session authentication have `session_jwt_hash` or `session_token_hash`.
* Events with a status error or success will typically have `http_status_code` with the relevant information
* Events in projects with DFPPA enabled will have more metadata related to the request, including the verdict action
### Actions
Each event log maps to a specific action, which corresponds to either a Stytch API request (e.g. `MagicLinksEmailLoginOrCreate`) or a meaningful dashboard request (e.g. `CreatePublicToken`). Actions help you understand what types of requests are being made.
The list of actions below is not exhaustive, but gives you a sense of the types of actions you can expect to see in your event logs:
* `SessionsAuthenticate` or `MultiTenantSessionsAuthenticate`: This action will appear whenever a user's session is authenticated. Our frontend SDKs will automatically call this method to refresh and authenticate a user's session. SessionsAuthenticate relates to a session in our Consumer API, while MultiTenantSessionsAuthenticate relates to a session in our B2B API.
* `OAuthGoogleOneTapStart`: This action will appear whenever the Google One Tap prompt is rendered (i.e. anytime a user visits a page where you display the Google One Tap prompt).
* `GetUser`: This action will appear whenever a user is fetched from Stytch via GetUsers.
* `SessionsJwks`: This action will appear whenever you fetch your Project's JWKS. This can be done manually, but is typically done automatically by our SDKs whenever a Stytch client is instantiated.
* `SearchUsersExternal`: This action will appear whenever you call SearchUsers. This is marked external to differentiate it from the search event (SearchUsers) called by our Dashboard user search.
* `EmailDelivery`: This action gives you visibility into email delivery and email opens for your users. This action will appear for successful deliveries, bounces or errors, and opens. Check out the message\_status field to determine the type of event.
* `SMSDelivery`: This action provides visibility into SMS delivery status. Use the message\_status field to identify the type of event. An event can be marked as 'sent' or 'undelivered' by the SMS provider and as 'delivered' when the user’s carrier confirms it has reached the recipient.
* `PasswordsAuthenticate`: This action appears whenever a user authenticates with a password.
* `MagicLinksCreate`: This action relates to our [Embeddable Magic Links](/docs/consumer-auth/authentication/magic-links/embeddable-magic-links) product. This action will appear whenever a magic link is created.
* `CryptoWalletsAuthenticateStart`: This action will appear whenever a user starts the authentication flow for a crypto wallet.
* `OAuthAuthenticate`: This action will appear whenever a user successfully authenticates with an OAuth provider.
* `OAuthGoogleStart`: This action will appear whenever a user starts the non-One Tap authentication flow for Google.
* `SessionsRevoke`: This action will appear whenever a user's session is revoked. This can happen when a user logs out or when a user's session is revoked via the API.
* `CryptoWalletsAuthenticate`: This action will appear whenever a user successfully authenticates with a crypto wallet.
* `OTPsAuthenticate`: This action will appear whenever a user successfully authenticates with an OTP.
* `MagicLinksAuthenticate`: This action will appear whenever a user successfully authenticates with a magic link.
* `UpdateUser`: This action will appear whenever a user is updated via UpdateUser.
### Status types
You can filter for event logs by status type. The following status types are available:
* `start` marks the start of a request. It is paired with a subsequent success or error event log.
* `success` marks the completion of a successful request.
* `error` marks the failure of a request.
`start` and `success`/`error` event logs are tied together by request ID.
# Programmatic Workspace Actions API
Source: https://stytch.com/docs/resources/workspace-management/pwa-api
# Redirect URLs
Source: https://stytch.com/docs/resources/workspace-management/redirect-urls
Redirect URLs are used by several Stytch products including Email Magic Links, OAuth, and Passwords (as part of the reset flow). There are four types of redirect URLs: login, sign-up, invite, and reset password. These values are set in your code during the associated API call, and tell Stytch where to redirect users upon successful completion of the authentication flow.
By default, all redirect URLs are set to `http://localhost:3000/authenticate` for Test environments.
## Structure
Redirect URLs for traditional web applications contain seven parts. Values 1-3 and 7 are set by you and values 4, 5, and 6 are set by Stytch. If you are using mobile or desktop deep links, values 1 and 2 will be different (e.g. `slack://auth/callback`).
1. **Protocol**: e.g. `https://` or `http://`.
2. **Domain**: e.g. `example.com`, `www.example.com` , and `example.com` are considered equivalent.
3. **Path**: This route is where your backend will accept the redirect and authenticate the user (e.g. `/authenticate`).
4. **Public token**: For our Passwords product, your `public_token` is included in the redirect URL to allow our JavaScript SDK, if you use it, to complete the authentication flow.
5. **Stytch token type**: The `stytch_token_type` query parameter indicates which product and authentication flow the user is currently completing. You'll use this value to call the appropriate Authenticate endpoint. For example, a value of `stytch_token_type=magic_links` indicates that you'll need to call the [Magic Links Authenticate endpoint](/docs/api-reference/consumer/api/magic-links/authenticate) with the token to complete the authentication flow. \
Possible values are in our Consumer API are:
* `magic_links`: Used by our [Email Magic Links](/docs/api-reference/consumer/api/magic-links/authenticate) product.
* `oauth`: Used by our [OAuth](/docs/api-reference/consumer/api/oauth/authenticate) product.
* `reset_password`: Used by our [Passwords](/docs/api-reference/consumer/api/passwords/authenticate) product during the reset password flow.
* `login`: Used by our [Passwords](/docs/api-reference/consumer/api/passwords/authenticate) product during the reset flow as the alternative one click login.
* `impersonation`: Used by our [User Impersonation](/docs/api-reference/consumer/api/impersonation/authenticate-token) feature.
Possible values in our B2B API are:
* `multi_tenant_magic_links`: Used by our [Email Magic Links](/docs/api-reference/b2b/api/email-magic-links/organization/authenticate-magic-link) product.
* `multi_tenant_passwords`: Used by our [Passwords](/docs/api-reference/b2b/api/passwords/authenticate) product.
* `sso`: Used by our [SSO](/docs/api-reference/b2b/api/sso/shared/complete-sso-authenticate) product.
* `discovery`: Used for Magic Link [Discovery](/docs/api-reference/b2b/api/email-magic-links/discovery/authenticate-discovery-magic-link) flows.
* `discovery_oauth`: Used for OAuth [Discovery](/docs/api-reference/b2b/api/oauth/discovery/authenticate) flows.
* `oauth`: Used by our [OAuth](/docs/api-reference/b2b/api/oauth/organization/authenticate) product.
* `multi_tenant_impersonation`: Used by our [Impersonation](/docs/api-reference/b2b/api/impersonation/authenticate-token) feature.
6. **Token**: The token value that you'll pass to the appropriate Authenticate endpoint, e.g. `token=rM_kw42CWBhsHLF62V75jELMbvJ87njMe3tFVj7Qupu7`.
7. **Organization slug**: For redirect URLs in our B2B API, the slug parameter indicates which Organization the redirect URL pertains to. This query parameter will only be included in Organization-based authentication flows, and not Discovery flows (where the Organization is not yet known).
8. **Query parameters**: You may set any arbitrary query parameters and include state data within the redirect URL. See the **Query parameters** section below, e.g. `?next_route=profile`.
### Example redirect URLs
```txt theme={null}
// Magic Links redirect URL example
"https://example.com/authenticate?stytch_token_type=magic_links&token=rM_kw42CWBhsHLF62V75jELMbvJ87njMe3tFVj7Qupu7"
// Passwords redirect URL examples
// This URL powers the "Reset Password" button in password reset emails:
"http://example.com/authenticate?stytch_token_type=reset_password&token=H3qsCWufbGf4u2hOPKgIaScT_ao2yC4yPAD24zug1KjW"
// This URL powers the "Log in without a password" button in password reset emails:
"https://example.com/v1/magic_links/redirect?public_token=public-token-test-03abfdeb-5c39-48db-898b-9a2c42a09e0f&stytch_token_type=login&token=RGLT-fxUSgOdWLmPxQX5ScTxg5buOYba2YsgdJL0E5Tg"
// OAuth redirect URL example
"http://example.com/login?stytch_token_type=oauth&token=pYIfdlODeuQOjf1CULw8LcF3roDeSy0J81AQznWOMK8o"
// B2B Organization Magic Links redirect URL example
"https://example.com/authenticate?slug=my-org&stytch_token_type=multi_tenant_magic_links&token=rM_kw42CWBhsHLF62V75jELMbvJ87njMe3tFVj7Qupu7"
```
## Security
For security, a redirect URL must be added in the [Dashboard](https://stytch.com/dashboard/redirect-urls) before being used. This prevents bad actors from creating a fake login portal, phishing your users, and redirecting them to a site they control. In the Dashboard, you may set a default redirect URL for each type which allows you to omit setting it as a parameter when making the associated API call. Redirect URLs are not shared across project environments, and must be added to each individually.
## Testing
To make testing and development easier, you may use local, non-HTTPS, addresses (e.g. `http://localhost:3000`) in Test environments. All Live redirect URLs must use HTTPS.
In Test environments, you may also use `*` to specify a wildcard for the domain. For example, `https://*.stytch.com.` Query parameters cannot contain wildcards and must be an exact match.
This can be helpful in generating preview environments (e.g. `https://myapp-*.vercel.app`).
`https://*.vercel.app` is not a valid redirect URL, as `vercel.app` is listed as a public suffix. Allowing wildcards with public suffixes introduces a potential security concern. A bad actor could spoof your subdomain if the environment is hosted on a shared third-party service (e.g. `https://badactor.vercel.com`). This domain would resolve as valid if you are using the wildcard subdomain example in the Dashboard.
## Query parameters
Redirect URLs can optionally include query parameters. Query parameters can be useful as an alternative way to persist state outside of browser cookies and local storage during authentication.
URLs must be an exact match. If you have an optional query parameter `optional_param`, create two redirect URLs: `https://www.example.com` and `https://www.example.com?optional_param={}`.
### User app state example
An e-commerce app may want to persist a user's shopping cart and other app state in their URL so they can serve the user a tailored checkout experience post-login, e.g. `https://www.example.com/signup?cart_id=123&promo_banner=true`.
Query parameters can be defined as part of the redirect URL in the Dashboard using the pattern `param_name={}`. In the example above, the developer would need to set the following redirect URL in the Dashboard, `https://www.example.com/signup?cart_id={}&promo_banner={}`.
### Returning to current page example
Another common use case for query parameters is storing the current route location of a user before they authenticate. That way, after a user logs in, your application can parse this value from the query parameters, and redirect the user to the same page that they were on before authenticating.
1. Add a redirect URL like `https://www.example.com/authenticate?next\_route={}` in the Dashboard.
2. When starting an authentication flow that features a redirect, grab the current page path, and pass the full redirect URL as a parameter in your call to Stytch (e.g. `https://www.example.com/authenticate?next\_route=/profile/details/`)
3. After redirecting to `https://www.example.com/authenticate` and successfully authenticating, finally redirect your user to the path stored in `next_route` (e.g. `https://www.example.com/profile/details/`).
Be sure to validate the `next_route` contents before doing the final redirect in order to prevent [open redirect](https://cwe.mitre.org/data/definitions/601.html) vulnerabilities.
## Native redirect URLs
Native redirect URLs for desktop and mobile applications are also supported, for example `slack://auth/callback`.
If you are using Email Magic Links or Passwords along with native redirect URLs you may need to make additional configuration changes.
# Stytch MCP Server
Source: https://stytch.com/docs/resources/workspace-management/stytch-mcp-server
One of the most compelling recent developments in the AI ecosystem is the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). Briefly, MCP allows large language models (LLMs) like ChatGPT or Claude to perform actions in an app (called tools in the MCP protocol) on behalf of the user chatting with the LLM. MCP allows LLMs to increase their capabilities by connecting the AI to third party services in a seamless fashion.
Stytch can be used to [provide the authentication layer](/docs/connected-apps/guides/mcp-auth-overview) for implementing MCP servers, but, in addition, Stytch *itself* has been implemented as an MCP server. This allows configuring Stytch's products using natural language through an MCP-capable AI — no need to visit the Stytch dashboard.
## Configuring Stytch's MCP server
Stytch is commonly configured during software development, so for this example we'll be configuring an IDE with Stytch's MCP server information. To one-click install Stytch's MCP server with [Cursor](https://cursor.com/en) or [VSCode](https://code.visualstudio.com/), you can visit [mcp.stytch.dev](https://mcp.stytch.dev/) and click the button for installation. Alternatively, for other tools or to set up Stytch MCP with Cursor manually, proceed through the following walkthrough. Although the configuration process here is specific to Cursor, this example should give enough information to configure Stytch's MCP server for other tools.
In the app menu bar, activate
```bash theme={null}
Cursor => Settings => Cursor Settings
```
Select the Tools & Integrations tab, and then Add Custom MCP. This will open a file called mcp.json that contains the server's configuration. [Follow the documentation here](https://docs.cursor.com/context/model-context-protocol) to understand how to configure the JSON file. For our purposes, the file should look something like:
```json theme={null}
{
"mcpServers": {
"stytch": {
"url": "https://mcp.stytch.dev/mcp"
}
}
}
```
It may be necessary to restart Cursor to pick up the changes.
Note: when configuring other tools, although the configuration steps may differ, the same URL used in the above configuration should be used to specify where Stytch's MCP server is located.
Now, when going to
```bash theme={null}
Cursor => Settings => Cursor Settings => Tools & Integrations
```
there should be an entry for stytch and a small message underneath which says "Needs authentication".
## Authenticating with the Stytch MCP server
From time to time it may be necessary to reauthenticate with the Stytch MCP server. In the Tools & Integrations view, you can click the message that says "Needs Authentication" to go through the auth flow to grant access to your Stytch workspace to Cursor. Once the flow is done, Cursor should enumerate the tools available and you should see a message like "21 tools enabled". Clicking this message will expand to show you the tools that Stytch's MCP server has implemented.
The Stytch MCP server is now configured to be used with the Cursor agent. You should be able to ask it things like "What are my Stytch project IDs" and it will return a response. Using natural language—you don't have to specifically match the name of the tool—you can perform any action listed in the available tools from Stytch's MCP server.
When running requests against the Stytch MCP server, note that, by default, Cursor is careful not to run commands automatically, as that may change state - you have to grant it permission for each tool call. If you get stuck or Cursor's agent seems to hang, be sure to check if it's waiting for permission to make the call. [Cursor has documentation available](https://docs.cursor.com/context/model-context-protocol#auto-run) on how to automatically run MCP tools if you choose to not be prompted for each call.
## What's possible using Stytch's MCP server
Having access to Stytch via its MCP server is very handy for chatting with AI agents or quickly getting information without having to context switch out of the IDE. In addition, it's convenient for keeping more of your systems in your repository; for instance, you can keep your email templates in git / version control and just ask the AI agent to upload a new version as it changes rather than having to log in and do the upload manually.
One of the most exciting possibilities made available by using our MCP server is in putting configuration instructions directly in your repository's README file. For an example of what this might look like you can check out our [MCP example hosted on Vercel](https://github.com/stytchauth/vercel-mcp-example), which shows at the end of its README an example of how to script the configuration of Stytch via the MCP server and LLM. In this way you can create documentation that is both human-readable and which can be used by AI to bootstrap your projects.
# Webhooks
Source: https://stytch.com/docs/resources/workspace-management/webhooks
Stytch's webhooks support allows you to keep your internal database records in sync with any changes that occur in Stytch and are out-of-band of your system.
You can enroll in webhooks through the Stytch Dashboard by specifying a URL(s) at which you wish to receive the webhook POST requests and the type of events you wish to receive.
Stytch's webhooks are powered through [Svix](https://www.svix.com/), and you can read the documentation from Svix on receiving and verifying webhooks manually or through their SDKs [here](https://docs.svix.com/receiving/introduction).
## Overview
Stytch's webhook event types are segmented by:
* `source`: the origin of the trigger.
* `object_type`: the type of entity being acted upon.
* `action`: type of action taken.
These segments combine to form the event type identifier, following the structure `source.object_type.action`.
For example, the `direct.organization.create` event type will be triggered when an Organization is created via API or SDK call. Similarly, the `dashboard.update.member` event type will be triggered when a Member is updated via the Stytch Dashboard.
## Payload structure
Each Stytch event will have the following fields:
* `project_id`: the Stytch ProjectID that the event is for.
* `event_id`: a unique UUID of the event.
* `action`: type of action taken; CREATE, UPDATE or DELETE.
* `object_type`: the type of entity being acted upon.
* `source`: the origin of the trigger; DIRECT, DASHBOARD, or SCIM.
* `id`: the UUID of the entity the action was taken upon.
* `timestamp`: the ISO 8601 timestamp the event was sent in Stytch (please see Webhooks Disclaimers below).
The payload for CREATE and UPDATE events will also include the full payload object for the entity event type, with the key as the value indicated in the `object_type` field:
* `organization`
* `member`
* `saml_connection`
* `oidc_connection`
* `scim_connection`
* `user` (for Consumer projects)
```json theme={null}
// Example organization create via dashboard (dashboard.organization.create)
{
"project_id": "project-live-123-...",
"event_id": "event-live-123-...",
"action": "CREATE",
"object_type": "organization",
"source": "DASHBOARD",
"id": "organization-live-123-...",
"timestamp": "2024-03-07T18:49:32.760777783Z",
"organization": { ... }
}
// Example update member event via SCIM (scim.member.update)
{
"project_id": "project-live-123-...",
"event_id": "event-live-456-...",
"action": "UPDATE",
"object_type": "member",
"source": "SCIM",
"id": "member-live-123-...",
"timestamp": "2024-03-07T18:49:32.760777783Z",
"member": { ... }
}
// Example delete SAML connection event via JS SDK or API (direct.saml_connection.delete)
{
"project_id": "project-live-123-...",
"event_id": "event-live-789-...",
"action": "DELETE",
"object_type": "saml_connection",
"source": "DIRECT",
"timestamp": "2024-03-07T18:49:32.760777783Z",
"id": "saml-connection-live-123-..."
}
```
## Supported Events
Our event types and complete schemas can be found [here for B2B projects](https://www.svix.com/event-types/us/org%5F2XtyUqbN4SKO3pqtd1Yp5eNLYas/) and [here for Consumer projects](https://www.svix.com/event-types/us/org%5F2olWIePjxAFYube1xW8DoQUEnyY/), or in the [Webhooks](https://stytch.com/dashboard/webhooks) page of the Dashboard.
For each webhook URL you configure in the Stytch Dashboard, you can choose to either receive all events or filter down to just the ones you care about.
## Webhooks Disclaimers
Webhooks do not inherently offer ordering guarantees, due to a number of factors including the possibility of intermittent failures and retries, network variability, and optimizing for reliability over strict ordering. You can read more about this topic [here](https://www.svix.com/blog/guaranteeing-webhook-ordering/).
The recommended approach for protecting against writing stale data due to out-of-order events is to use the webhook as a **signal to pull Stytch**. Upon receiving a direct.member.update event, you would then trigger a `GET /v1/b2b/organizations/{organization_id}/member` API call for the MemberID specified in the event and use the response to update your internal system. This ensures that even if the webhooks are processed out of order, you are always working with the most up-to-date data.
However, we recognize that every system has different tradeoffs and have included the full entity payload, to provide maximum flexibility in how you wish to process events or determine when to pull Stytch (e.g. only if the event indicates an update to a field you store in your system). We have also included the ISO 8601 timestamp in nanosecond precision that the event was sent - but please note that this timestamp is not necessarily unique, and is recorded immediately after the DB transaction completes.
Please note that Svix automatically disables webhooks if all attempts to a specific endpoint fail for a period of 5 days. To re-enable a disabled endpoint, go to the webhook dashboard, find the endpoint from the list and select "Enable Endpoint".
## Setting up Webhooks in the Dashboard
### Step 1: Create a webhooks endpoint URL
To begin configuring your webhooks, navigate to the [Webhooks](https://stytch.com/dashboard/webhooks) page of the Dashboard.
Click **+ Add Endpoint**. This is where you'll define and enter an endpoint URL to which Stytch will send webhook events. The endpoint you specify should be set up on your server to receive POST requests, as this is how Stytch transmits the event data.
You can create multiple webhook endpoint URLs for different events or environments.
### Step 2: Select and filter events
Once you've set up an endpoint URL, the next step is to specify which events you want to receive. You can opt to receive all events or filter for specific event types. Refer to the Stytch [events catalog](https://www.svix.com/event-types/us/org%5F2XtyUqbN4SKO3pqtd1Yp5eNLYas/) for a complete list.
Click **Create** at the bottom once you've finished.
### Step 3: Sending and receiving events
With your webhook URLs configured, the final step involves validating that the webhooks are being sent and received. For testing, trigger the events you selected in **Step 2** (e.g. Create an Organization in the Dashboard or Update a Member record with an API call). Your server's endpoint should receive the events, and you should see the events appear in the **Message Attempts** table, the **Logs** tab, and the **Activity** tab.