# 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. Stytch offers APIs and SDKs for backend, frontend headless, and frontend pre-built UI integrations. ## 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 Diagram for headless implementation 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 JWT validation flow 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 Multi-tenant vs.
single-tenant ## 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. Auth0 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. Import client to Stytch 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`. Edit M2M client 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. Auth0 permissions 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. Import scopes 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
Loading
; }; ``` ```jsx Next.js icon=triangle theme={null} import { useEffect } from 'react'; import { useStytchB2BClient } from '@stytch/nextjs/b2b'; export const DiscoveryAuthenticate = () => { const stytch = useStytchB2BClient(); const authenticate = async () => { const result = await stytch.authenticateByUrl({ session_duration_minutes: 60, }); if (result?.handled) { const { intermediate_session_token, email_address, discovered_organizations } = result.data; console.log({ intermediate_session_token, email_address, discovered_organizations }); } }; useEffect(() => { authenticate(); }); return
Loading
; }; ``` ```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
Log In
; }; ``` ```jsx Next.js icon=triangle theme={null} import { useEffect } from 'react'; import { useStytchB2BClient } from '@stytch/nextjs/b2b'; export const ExchangeIntermediateSession = () => { const stytch = useStytchB2BClient(); useEffect(() => { stytch.discovery.intermediateSessions.exchange({ organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931', session_duration_minutes: 60, }); }); 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
; }; ``` ```jsx Next.js icon=triangle theme={null} import { useEffect } from 'react'; import { useStytchB2BClient } from '@stytch/nextjs/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. 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
Loading
; } ``` ```jsx Next.js icon=triangle theme={null} import { useEffect } from 'react'; import { useStytchB2BClient, useStytchMemberSession } from '@stytch/nextjs/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
Loading
; }; ``` ```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**. Create Credentials 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. Authorized redirect URI 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**. 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. Create Credentials 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. Authorized redirect URIs 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**. Authorized JavaScript Origins # 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: Google One Tap prompt ## 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**. New Microsoft Registration Enter a name for your OAuth client. Select **Accounts in any organizational directory (Any Azure AD directory - Multitenant)**. Select Account Type 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. Redirect URI 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**. Add a platform Copy the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) and paste it into the URI field. Configure redirect URIs 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. Stytch project domain Navigate to the [OAuth section](https://stytch.com/dashboard/oauth), select the OAuth identity provider of your choice and click **Configure**. Stytch OAuth providers Once in the provider configuration, we recommend clicking the **Setting up OAuth with \[provider]** button to streamline the configuration of your OAuth client. Stytch GitHub OAuth configuration 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" } ``` Dashboard page for configuring JIT Provisioning by email domain 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: View SAML setup instructions button in Okta 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: Okta SAML Configuration 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. Copy SAML Metadata URL in Okta 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**: Assign users button in Okta ## 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: Create App Integration button in Okta Select **OIDC - OpenID Connect** and **Web Application**: App type selection screen in Okta Enter the name of your application and (optionally) your application's logo. Under **Grant type**, select **Authorization Code**: Okta grant type selection 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. Okta access type selection 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: Client ID and secret in Okta 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**: Web and mobile apps tab in Google Workspace Select **Add custom SAML app** from the **Add app** dropdown: Add custom SAML app button in Google Workspace 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": Google Workspace SAML IdP config information * **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: Stytch SSO Connection configured for Google 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 Google SAML service provider details 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: User access page in Google Workspace 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: Service status toggle in Google Workspace ## Microsoft Entra SAML Log into Microsoft Entra Admin Center, navigate to Enterprise applications and select to create a new application. Create new Entra application Select create your own application at the top. Create custom Entra application 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. Setup SSO for Entra application 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 Entra Basic SAML Configuration 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** Entra edit NameID claim to use primary authoritative email 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**. Entra final Attributes & Claims 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. Entra SAML Metadata URL 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". Entra add users to SAML app ## Microsoft Entra OIDC Log into Microsoft Entra Admin Center, navigate to **App registrations** and select to create a New registration Entra OIDC create new registration Input a name and select **Accounts in this organizational directory only** for Supported account types and click Register Entra OIDC register your application Navigate to the Authentication section and select "Add a platform" under Platform configurations Entra OIDC add platform config in authentication setup Select web and input the Redirect URI from the Stytch SSO Connection you created earlier. Leave the rest blank and click "Configure" Entra OIDC configure Redirect URL 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. Entra OIDC create new client secret In the Stytch Dashboard, click "configure" on your SSO Connection and input the secret value as the Client Secret in Stytch Entra OIDC get secret value 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 Entra OIDC get ClientID and Issuer 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. Enabling user impersonation Enabling user impersonation ## 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**. Impersonating a Member Impersonating a 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. Impersonation event log Impersonation event log 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 Headless Overview 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 Backend Overview 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 Admin Portal Member Management UI Admin Portal Organization Settings UI Admin Portal SSO Management UI Admin Portal SCIM Management UI ## 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 ( {members.map((member) => ( ))}
Name Email Roles
{member.name} {member.email_address} {member.roles.map((role) => role.role_id).join(', ')}
); }; ```
Use [`Update Member`](/docs/api-reference/b2b/frontend-sdks/react/methods/members/update-member) to update a Member from the table. ```jsx theme={null} import { useStytchB2BClient } from '@stytch/react/b2b'; export const UpdateMember = ({ memberId }) => { const stytch = useStytchB2BClient(); const [name, setName] = useState(''); const handleUpdate = async () => { await stytch.members.update({ member_id: memberId, name }); }; return (
setName(e.target.value)} placeholder="Enter member name" />
); }; ```
Add an [`Invite Member`](/docs/api-reference/b2b/frontend-sdks/react/methods/email-magic-links/invite) button to allow inviting new Members to the Organization. ```jsx theme={null} import { useStytchB2BClient } from '@stytch/react/b2b'; const stytch = useStytchB2BClient(); export const InviteMember = () => { const stytch = useStytchB2BClient(); const [email, setEmail] = useState(''); const handleInvite = async () => { await stytch.members.invite({ email_address: email }); }; return (
setEmail(e.target.value)} placeholder="Enter email address" />
); }; ```
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 (

Organization Name: {organization.organization_name}

Allowed Authentication Methods:

{organization.auth_methods === "RESTRICTED" ? (
    {organization.allowed_auth_methods.map((method) => (
  • {method}
  • ))}
) : (

All authentication methods are allowed

)} {.... display other organization settings ...}
); }; ```
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} EPD Engineering ``` 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”. Go to Entra App Catalog to create SCIM app In the app creation flow, input a name for your application and select “Integrate any other application you didn't find in the gallery". Create your SCIM application in Microsoft Entra 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. In Entra app navigate to Provisioning On the next page, click “Get started” and then switch the provisioning mode from “Manual” to “Automatic”. Select automatic provisioning mode in Entra SCIM app 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. Create Microsoft Entra SCIM Connection In Stytch Once you click save, you'll be provided with the base url and bearer token you'll need for the next step. Entra Connection Credentials from Stytch 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) Input admin credentials for SCIM with Microsoft Entra 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”. Enable provisioning for Entra SCIM 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. Provision on demand with Entra for 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. Admin Portal SCIM Management UI 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". Enable SCIM for an existing Okta SAML App 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. Create SCIM Connection in Stytch Dashboard Once you click save, you'll be provided with the base url and bearer token you'll need for the next step. Stytch Okta SCIM Connection Credentials 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: Expected SCIM Configuration for existing SAML App 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)" Search App Catalog for SCIM Name your application: Name your standalone SCIM app On the "Sign-on Options" tab scroll to the bottom, and under "Credential Details" change the "Application username format" to "Email". Use email for username in SCIM app 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. Create SCIM Connection in Stytch Dashboard Stytch Okta SCIM Connection Credentials Back in the Okta admin dashboard, input the returned BaseURL and set the API Token to `Bearer ${returned_bearer_token}`. Stand alone SCIM app credentials You will then be able to select which actions to send via SCIM. Enable SCIM actions to send from Okta 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. Admin Portal SCIM Management UI 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 JWT validation flow 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]}) ``` MetaMask popup 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. Auth0 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. Import client to Stytch 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`. Edit M2M client 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. Auth0 permissions 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. Import scopes 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. Stytch API auth flow ## 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. Login view example 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). Stytch SDK auth flow 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. Embeddable magic links flow diagram 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. Stytch SDK auth flow 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. Apple Select App Type 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. Apple Configure 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. Apple Configure Service ID 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. Apple Return URL 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'. Download Key File 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: Configure Apple OAuth Stytch Dashboard * **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**. Add Facebook Login 7. Ignore the quickstart menu. Instead, on the left pane under **Facebook Login**, click on **Settings**. Facebook Login 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. Request advanced access *** *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**. Add Facebook Login 4. Ignore the quickstart menu. Instead, on the left pane under **Facebook Login**, click on **Settings**. Facebook Login 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. Request advanced access # 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**. Google Create Credentials 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. Google Authorized Redirect URIs 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). Google Authorized Javascript Origins *** *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. Google Create Credentials 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. Google Authorized Redirect URIs 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 Authorized Javascript Origins # 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**. Google Create Credentials 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. Google Authorized Redirect URIs 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). Google Authorized Javascript Origins *** *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. Google Create Credentials 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. Google Authorized Redirect URIs 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 Authorized Javascript Origins # 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**. New Microsoft 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)". Select Account Type 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. Redirect URI 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. Supported Account Types Sign in audience 3. In your OAuth client configuration, select the link next to the **Redirect URIs** section. Then select **Add a platform** and finally select **Web**. Add a redirect URI Add a platform 4. Copy-paste the Stytch redirect URI from the [Dashboard](https://stytch.com/dashboard/oauth) into the URI field. Configure redirect URIs 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: OAuth consent start screen 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**. OAuth consent authorized domains 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. OAuth consent start screen 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. Enter phone number Enter 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). Passkeys diagram Passkeys diagram
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: Passkeys diagram
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. Passkeys diagram 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: Passkeys diagram Passkeys diagram 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: Passkeys diagram 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. Passkeys diagram 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. Password signup UI Weak password UI 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. Password strength check 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. Password signup UI Weak password UI 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). Password reset sebd UI Password reset email UI Password reset UX 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). Password flow # 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. Password reset email 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. Enabling user impersonation Enabling user impersonation ## 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**. Impersonating a User Impersonating a 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. Impersonation event log Impersonation event log 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. RBAC Resources in Stytch Dashboard For example, a `documents` Resource might support `create`, `update`, `delete`, and `download`. RBAC Resources in Stytch Dashboard 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. RBAC Grant Subset of Permissions To grant all actions for a Resource, use the wildcard action `*`. RBAC Wildcard Actions Grant 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 (
{canWriteDocument && }
); }; ``` ```jsx Next.js icon="triangle" theme={null} import { useEffect, useState } from 'react'; import { useStytch } from '@stytch/nextjs'; 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 (
{canWriteDocument && }
); }; ``` ```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. Stytch and Feathery # 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. Stytch and Planetscale 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. Stytch and Supabase # Unit Source: https://stytch.com/docs/consumer-auth/integrations/unit Use Stytch JWTs to satisfy Unit customer token requirements. Stytch and Unit 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). 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**. SetRule Dashboard Event Logs 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. Delete DFP 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). 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. Stytch fraud prevention framework 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 Stytch Device Fingerprinting mapped to the Stytch fraud prevention framework 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: Protected Auth Enforcement mode ### 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). Dashboard showing multiple country-based 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. Flowchart showing webhooks 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. Enable Protected Auth section in the Stytch Dashboard 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. Webhook configuration in the Dashboard 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. Device Fingerprinting Dashboard 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!

)}
) : ( <> Lock

{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.' }

{deviceResponse.hasRegisteredPhone && deviceResponse.phoneNumber ? ( ) : ( )} )}
```
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): Device Fingerprinting Dashboard 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: Turning on Protected Auth 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. Device Fingerprinting 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: Enforcement mode ## 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 Getting Started Pn 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"""

Consent required

This app is requesting access to:

    {''.join([f'
  • {s}
  • ' for s in scopes])}
{''.join([f'' for k, v in hidden.items()])}
""" ``` ```javascript B2B Node expandable theme={null} function renderConsentPage(scopes, hidden) { const hiddenInputs = Object.entries(hidden) .map(([k, v]) => ``) .join(''); return `

Consent required

This app is requesting access to:

    ${scopes.map((s) => `
  • ${s}
  • `).join('')}
${hiddenInputs}
`; } ``` ```python Consumer Python expandable theme={null} def render_consent_html(scopes, hidden): return f"""

Consent required

This app is requesting access to:

    {''.join([f'
  • {s}
  • ' for s in scopes])}
{''.join([f'' for k, v in hidden.items()])}
""" ``` ```javascript Consumer Node expandable theme={null} function renderConsentPage(scopes, hidden) { const hiddenInputs = Object.entries(hidden) .map(([k, v]) => ``) .join(''); return `

Consent required

This app is requesting access to:

    ${scopes.map((s) => `
  • ${s}
  • `).join('')}
${hiddenInputs}
`; } ```
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). Authorization Url Pn 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. Create New App Pn Select Client Type Pn 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. App Details Pn 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. Integrate With Ai Agents Redirect Urls Pn
# 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). Authorization Url Pn 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. Create New App Pn Select Client Type Pn 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. App Details Pn 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. Integrate With Ai Agents Redirect Urls Pn 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. Create New App Configure the app as First Party Public. Select Client Type 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. Cli App 1 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. Cli App 2 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`. Authorization Url Pn 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 New App Pn 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**. Select Client Type Pn 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` Integrate Ai Agents Custom Gpt Pn 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. Integrate With Ai Agents Redirect Urls Pn 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).