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).

Implementation differs between Discovery and Organization login flows; the examples below will use the Organization login flow.

Frontend Implementation (pre-built UI)

The following is an example implementation of an Organization login flow using our Email Magic Links product with our NextJS SDK.

// To launch our prebuilt UI components
import React, { useEffect, useState } from "react";
import { StytchB2B } from "@stytch/nextjs/b2b";
import {
  AuthFlowType,
  B2BProducts,
  StytchB2BUIConfig,
} from "@stytch/vanilla-js";

const TenantedLoginForm = () => {
  const [config, setConfig] = useState<StytchB2BUIConfig | null>();

  useEffect(() => {
    setConfig({
      products: [B2BProducts.emailMagicLinks],
      sessionOptions: { sessionDurationMinutes: 60 },
      emailMagicLinksOptions: {
        loginRedirectURL: `${window.location.origin}/authenticate`,
        signupRedirectURL: `${window.location.origin}/authenticate`,
      },
      authFlowType: AuthFlowType.Organization,
    });
  }, []);

  return config ? <StytchB2B config={config} /> : null;
};

export default TenantedLoginForm;

Since Email Magic Links is a redirect-based authentication flow, you’ll also need to re-launch the UI component at the loginRedirectURL and signupRedirectURL defined above to ultimately authenticate the Magic token:

const Authenticate = () => {
  const [config, setConfig] = useState<StytchB2BUIConfig | null>();
  const router = useRouter();

  useEffect(() => {
    setConfig({
      products: [B2BProducts.emailMagicLinks],
      sessionOptions: { sessionDurationMinutes: 60 },
      authFlowType: AuthFlowType.Organization,
    });
  }, []);

  return config ? (
    <StytchB2B
      config={config}
      callbacks={{
        onEvent: async ({ type, data }) => {
          if (type === StytchEventType.B2BDiscoveryIntermediateSessionExchange) {
            router.push(`/${data.organization.organization_slug}/dashboard`);
          } else if (type === StytchEventType.B2BDiscoveryOrganizationsCreate) {
            router.push(`/${data.organization.organization_slug}/dashboard`);
          } else if (type === StytchEventType.B2BMagicLinkAuthenticate) {
            router.push(`/${data.organization.organization_slug}/dashboard`);
          } else if (type === StytchEventType.B2BSSOAuthenticate) {
            router.push(`/${data.organization.organization_slug}/dashboard`);
          }
        },
      }}
    />
  ) : null;
};

export default Authenticate;

Read more about how our pre-built UI components handles Organization login and Discovery login flows.

Backend Implementation

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

This example was pulled from our stytch-b2b-node-example example application:

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


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

export async function handler(req: NextApiRequest, res: NextApiResponse) {
  const stytchClient = loadStytch();
  const { email, organization_id } = JSON.parse(req.body);
  const domain = getDomainFromRequest(req);
  try {
    await stytchClient.magicLinks.email.loginOrSignup({
      email_address: email,
      organization_id: organization_id,
      login_redirect_url: `${domain}/api/callback`,
      signup_redirect_url: `${domain}/api/callback`,
    });
    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;

// Handler function to be called at the redirect URLs defined above
async function handleMagicLinkCallback(
  req: NextApiRequest
): Promise<ExchangeResult> {
  const authRes = await stytchClient.magicLinks.authenticate({
    magic_links_token: req.query.token as string,
    session_duration_minutes: SESSION_DURATION_MINUTES,
  });

  return {
    kind: "login",
    token: authRes.session_jwt as string,
  };
}