/
Contact usSee pricingStart building

    About B2B Saas Authentication

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

    Authentication

    Single Sign On
    • Resources

      • Overview
        External SSO Connections
    • Integration Guides

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

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

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

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

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

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

      • Overview
        Email Security Scanner Protections
    • Integration Guides

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

      • Overview
    • Integration Guides

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

    Authorization & Provisioning

    RBAC
    • Resources

      • Overview
        Stytch Resources & Roles
        Role assignment
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
    SCIM
    • Resources

      • Overview
        Supported actions
    • Integration Guides

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

    Testing

    E2E testing
    Sandbox values
Get support on SlackVisit our developer forum

Contact us

B2B Saas Authentication

/

Guides

/

About B2B Saas Authentication

/

Next.js

/

Authentication

Authentication

Authentication flows can be handled on the frontend (NextJS SDK) or the backend (Node SDK). We typically recommend using our frontend NextJS SDK, as the pre-built UI components will automatically handle complex multi-step authentication flows and session management out of the box. However, if you want the ability to have complete control over your login flow you can use the Node SDK in your Next app server-side.

Stytch offers two different authentication flows: Discovery and Organization-Specific. This example will walk through the Discovery login flow, which is designed for applications with a centralized login page.

Frontend Implementation (pre-built UI)

Stytch's Next.js SDK includes pre-built UI components with customizable login and signup forms, alongside a collection of headless methods for integrating with your own custom UI. This example walks through adding authentication with our pre-built UI components.

Want to skip straight to the source code? Check out an example app here.

1
Install Stytch SDKs and configure your API keys

If you haven't already, create a Stytch B2B Project in your Stytch Dashboard.

Add our frontend NextJS SDK package to your Next.js application:

npm install @stytch/nextjs @stytch/vanilla-js --save

Add your Stytch project's public_token to your application as an environment variable in a .env file:

# .env
# The below values may be found in your Stytch Dashboard: https://stytch.com/dashboard/api-keys
NEXT_PUBLIC_STYTCH_PROJECT_ENV=test
NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN="YOUR_STYTCH_PUBLIC_TOKEN"

2
Configure Stytch SDK settings

To allow the Stytch SDK to run on your frontend, you'll also need to:

  1. Enable frontend SDKs in Test in your Stytch Dashboard here.
  2. Add the domain your application will run on (e.g. http://localhost:3000) to the list of Domains under Authorized applications.
  3. Enable the Create organizations toggle under Enabled methods. This isn't required, but this setting will allow users to create new Organizations directly from our SDK.

3
Create the UI client and wrap your application in <StytchB2BProvider>

First, initialize the Stytch UI client by invoking the createStytchB2BUIClient constructor function, passing in your Project's public token.

Next, pass the Stytch UI client to the StytchB2BProvider component at the root of your application, making it accessible to all child components.

// pages/_app.jsx
import { StytchB2BProvider } from '@stytch/nextjs/b2b';
import { createStytchB2BUIClient } from '@stytch/nextjs/b2b/ui';

// optional object for configuring SDK cookie behavior, currently showing defaults
const stytchOptions = {
  cookieOptions: {
    opaqueTokenCookieName: "stytch_session",
    jwtCookieName: "stytch_session_jwt",
    path: "",
    availableToSubdomains: false,
    domain: "",
  }
}

const stytchClient = createStytchB2BUIClient(
  process.env.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN,
  stytchOptions
);

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
         // Truncated
      </Head>

      <StytchB2BProvider stytch={stytchClient}>
        <main>
          <div className="container">
            <Component {...pageProps} />
          </div>
        </main>
      </StytchB2BProvider>
    </>
  );
}

4
Add the <StytchB2B> UI component

Create a StytchB2B component. You can configure which authentication flow and methods you'd like to offer by modifying the authFlowType and products fields respectively in your config object. Here's an example that uses our Discovery flow and utilizes our Email Magic Links product:

// src/components/LoginOrSignupDiscoveryForm.jsx
import { StytchB2B } from '@stytch/nextjs/b2b';
import { AuthFlowType, B2BProducts } from '@stytch/vanilla-js/b2b';

export const LoginOrSignupDiscoveryForm = () => {
   const config = {
      products: [B2BProducts.emailMagicLinks],
      sessionOptions: { sessionDurationMinutes: 60 },
      authFlowType: AuthFlowType.Discovery,
    };

   return <StytchB2B config={config} />;
};

For Email Magic Links, you must specify a redirect URL in your Project's Dashboard to authenticate the token. By default, the redirect URL is set to http://localhost:3000/authenticate.

If the <StytchB2B> component is rendered on the redirect URL used for this flow, the Email Magic Link token will automatically be authenticated. You can specify additional Redirect URLs in your Project's Dashboard, and override the default by passing in an explicit emailMagicLinksOptions.discoveryRedirectURL to your UI config.

You can read more about redirect URLs in this guide.

5
Add the <LoginOrSignupDiscoveryForm> component to your login page

Finally, add the LoginOrSignupDiscoveryForm component we just created to the /authenticate page. You'll notice that we check for a logged-in member before displaying the LoginOrSignupDiscoveryForm component.

// pages/authenticate.jsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useStytchMember } from '@stytch/nextjs/b2b';
import { LoginOrSignupDiscoveryForm } from "src/components/LoginOrSignupDiscoveryForm";

export default function Authenticate() {
  const { member, isInitialized } = useStytchMember();
  const router = useRouter();

  useEffect(() => {
    if (isInitialized && member) {
      // Redirect the user to an authenticated page if they are already logged in
      router.replace("/dashboard");
    }
  }, [member, isInitialized, router]);

  return <LoginOrSignupDiscoveryForm />;
}

Backend Implementation

The following is an example implementation of an Discovery login flow using our Email Magic Links product with our backend Node SDK, implemented with a NextJS Route Handler.

For a working example app with our Node SDK, check out the example app here. While this example uses Express, our Node SDK is framework agnostic and the same methods and flows apply.

1
Install Stytch SDK and configure your API Keys

Create a Stytch B2B Project in your Stytch Dashboard if you haven't already.

Install our Node SDK in your Express environment:

npm install stytch

Configure your Stytch Project's API keys as environment variables:

STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
# Use your Project's 'test' or 'live' credentials

2
Set up your login route

First define an API that will take in an email address, and call Stytch to initiate the Email Magic Link flow.

// API route, defined in /lib/api.ts (non-Stytch code)
export const discovery_login = async (email: string) =>
  fetch("/api/discovery_login", {
    method: "POST",
    body: JSON.stringify({
      email,
    }),
  });


// Route handler with Node SDK, defined in /pages/api/discovery_login.ts
// This API route sends a magic link to the specified email address.
import type { NextApiRequest, NextApiResponse } from "next";
import loadStytch from "@/lib/loadStytch";

export async function handler(req: NextApiRequest, res: NextApiResponse) {
  const stytchClient = loadStytch();
  const { email } = JSON.parse(req.body);

  try {
    await stytchClient.magicLinks.email.discovery.send({
      email_address: email,
    });
    return res.status(200).end();
  } catch (error) {
    console.log("error sending magic link", error);
    const errorString = JSON.stringify(error);
    return res.status(400).json({ errorString });
  }
}

export default handler;

3
Add a route to handle redirect callback from Stytch

When a user completes an authentication flow in Stytch, we will call the Redirect URL specified in your Stytch Dashboard with a token used to securely complete the auth flow. By default the redirect URL is set tohttp://localhost:3000/authenticate.

You can read more about redirect URLs and possible token types in this guide.

// API route, defined in /lib/api.ts (non-Stytch code)
export const authenticate = async (email: string) =>
  fetch("/api/authenticate", {
    method: "GET",
    body: JSON.stringify({
      token_type,
      token,
    }),
  });

// Route handler with Node SDK, defined in /pages/api/authenticate.ts
export async function handler(req: NextApiRequest, res: NextApiResponse) {
  const stytchClient = loadStytch();
  const { token_type, token } = JSON.parse(req.body);

  if (token_type !== 'discovery') {
    console.error(`Unrecognized token type: '${tokenType}'`);
    res.status(400).send();
    return;
  }

  try {
    const authResp = await stytchClient.magicLinks.discovery.authenticate({
      discovery_magic_links_token: token,
    });
    return res.status(200).end();
  } catch (error) {
    console.log("error authenticating magic link token", error);
    const errorString = JSON.stringify(error);
    return res.status(400).json({ errorString });
  }
}

4
Create new Organization or login to existing Organization

At this point in the flow, the end user has authenticated but has not specified whether they want to create a new Organization or log into another Organization they belong to or can join through their verified email domain and JIT Provisioning.

stytchClient.magicLinks.discovery.authenticate() will return an Intermediate Session Token (IST) which allows you to preserve the authentication state while you present the user with options on how they wish to proceed. For the purposes of this quickstart, we will automatically create a new Organization if the end user does not have any Organizations they can log into and otherwise log into their first Organization.

export async function handler(req: NextApiRequest, res: NextApiResponse) {
  const stytchClient = loadStytch();
  const { token_type, token } = JSON.parse(req.body);

  if (token_type !== 'discovery') {
    console.error(`Unrecognized token type: '${tokenType}'`);
    res.status(400).send();
    return;
  }
	
  try {
    const authResp = await stytchClient.magicLinks.discovery.authenticate({
      discovery_magic_links_token: token,
    });
    return res.status(200).end();
  } catch (error) {
    console.log("error authenticating magic link token", error);
    const errorString = JSON.stringify(error);
    return res.status(400).json({ errorString });
  }

  // Sign into existing Organization if already Member
  const ist = authResp.intermediate_session_token
  if (authResp.discovered_organizations.length > 0 ) {
    try {
      const exchangeResp = await stytchClient.discovery.intermediateSessions.exchange({
        intermediate_session_token: ist,
        organization_id: authResp.discovered_organizations[0].organization.organization_id,
      });
    } catch (error) {
      console.log("error exchanging IST into Organization", error);
      const errorString = JSON.stringify(error);
      return res.status(400).json({ errorString });
    }
    
    // Store the returned session and return session member information
    res.setHeader('Set-Cookie', serialize('stytch_session_jwt', authResp.session_jwt, {
        path: '/',
        maxAge: 60 * 60 * 24,
    }));
    return res.status(200).end();
  }

  // If not eligible to log into an existing org, create new one
  try {
    const createResp = await stytchClient.discovery.organizations.create({
      intermediate_session_token: ist,
    });
  } catch (error) {
    console.log("error creating Organization", error);
    const errorString = JSON.stringify(error);
    return res.status(400).json({ errorString });
  }
  
  
  // Store the returned session and return session member information
  res.setHeader('Set-Cookie', serialize('stytch_session_jwt', authResp.session_jwt, {
      path: '/',
      maxAge: 60 * 60 * 24,
  }));
  return res.status(200).end();
});

Frontend Implementation (pre-built UI)

1.

Install Stytch SDKs and configure your API keys

2.

Configure Stytch SDK settings

3.

Create the UI client and wrap your application in <StytchB2BProvider>

4.

Add the <StytchB2B> UI component

5.

Add the <LoginOrSignupDiscoveryForm> component to your login page

Backend Implementation

1.

Install Stytch SDK and configure your API Keys

2.

Set up your login route

3.

Add a route to handle redirect callback from Stytch

4.

Create new Organization or login to existing Organization