B2B Saas Authentication

/

Guides

/

About B2B Saas Authentication

/

Next.js

/

Sessions

Session management

Once a Member successfully authenticates, a Stytch Session will be minted and returned in the API response.

Most applications will use a combination of frontend and backend session management; both are discussed below.

Frontend

Storing session cookies

Whether you’re using our frontend or backend SDK for the authentication flow, you’ll want to store the Stytch Session on your frontend.

If you’re using our Next.js SDK for authentication flows on the frontend, session cookies will be automatically stored in the browser, and sent along to your backend (as long as they share a domain). More information and example code snippets can be found in our frontend SDK documentation here.

Managing application state with hooks

Our Next.js SDK offers Member and Session hooks that represent the current state of a Stytch Member or Stytch Session.

These hooks return envelope objects:

// Get the state of the Member object
const {member, fromCache, isInitialized} = useStytchMember();
// Get the state of the Session object
const {session, fromCache, isInitialized} = useStytchMemberSession();

These envelope objects represent the state of these objects as they relate to SWR (Stale-While-Revalidating). In particular:

  • In SSR/SSG contexts, as well as the first client side render, member and session will be null and isInitialized will be false.
  • The SDK will read member and session out of persistent storage, and rerender with fromCache: true - at this point you’re reading the stale-while-revalidating value.
  • The SDK will make network requests to pull the most up-to-date user and session objects, and serve them with fromCache: false- at this point, the SDK has updated the cached data.

A common pattern is to use these hook values to determine application behavior - for instance:

import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useStytchUser } from '@stytch/nextjs/b2b';

export default function Profile() {
  const router = useRouter();
  const { session, isInitialized } = useStytchMemberSession();

  useEffect(() => {
    // Redirect to the login page if the Session is null (e.g. no active Session)
    if (isInitialized && session === null) {
      router.push('/login');
    }
  }, [session, isInitialized]);

  return (
    <Layout>
      <h1>Your Profile</h1>
      <pre>{JSON.stringify(member, null, 2)}</pre>
    </Layout>
  );
}

Backend

Storing session cookies

If you’re not using our Next.js SDK, and are using a backend-only integration you’ll need to store session cookies on your frontend yourself. Next.js has documentation on how to do this here.

Protecting backend resources

Even with a frontend integration, you’ll want to add session authentication to any backend routes you’d like to gate behind authentication.

There are a few different strategies for this that Next.js covers in their documentation.

The examples below utilize our Node SDK since these are strategies for authenticating a Stytch Member on the server side.

For information on supported runtime contexts, see this section of our Node SDK README.

Next.js has documentation on route handler authentication strategies here.

The below is an example of enforcing authentication on a specific route via a route handler and Stytch Session Token authentication.

app/api/my_protected_route.ts

import type { NextApiRequest, NextApiResponse } from 'next';
import loadStytch from '../../lib/loadStytch';
import Cookies from 'cookies';

type ErrorData = {
  errorString: string;
};

export async function handler(req: NextApiRequest, res: NextApiResponse<ErrorData>) {
  // Get session from cookie
  const cookies = new Cookies(req, res);
  const session = cookies.get('stytch_session');
  // If session does not exist display an error
  if (!storedSession) {
    return res.status(400).json({ errorString: 'No session provided' });
  }
  try {
    const stytchClient = new stytch.Client({
      project_id: process.env.STYTCH_PROJECT_ID || '',
      secret: process.env.STYTCH_SECRET || '',
    });
    // Validate Stytch session
    await stytchClient.sessions.authenticate({ session_token: storedSession });
    // Request is authenticated - safe to fetch/return data for authorized users
    console.log("This request is authenticated!")
    return res.status(200).end();
  } catch (error) {
    const errorString = JSON.stringify(error);
    return res.status(400).json({ errorString });
  }
}

export default handler;

Next.js has documentation on middleware authentication strategies here.

The below snippet is an example of a middleware.ts file that utilizes Stytch JWTs for authentication.

middleware.ts

import { NextRequest, NextResponse } from 'next/server';
import * as stytch from 'stytch';
import { cookies } from 'next/headers';

export default async function middleware(req: NextRequest) {
  const path = req.nextUrl.pathname;
  // If this is an authentication request, continue.
  if (path === '/authenticate') return NextResponse.next();

  const sessionJwt = cookies().get('stytch_session_jwt')?.value;
  // If no session is present, redirect to authentication flow.
  if (!sessionJwt) return NextResponse.redirect(new URL('/login', req.nextUrl));

  const stytchClient = new stytch.Client({
      project_id: process.env.STYTCH_PROJECT_ID || '',
      secret: process.env.STYTCH_SECRET || '',
    });

  // authenticateJwt() will attempt to authenticate the Stytch JWT locally first, then, if that fails for any reason, will hit the Stytch API as a fallback.
  try {
    await stytchClient.sessions.authenticateJwt({
      session_jwt: sessionJwt
    });
  }
  catch (e) {   // If the session is not valid, redirect to authentication flow.
    return NextResponse.redirect(new URL('/login', req.nextUrl));
  }
}

// Routes that do not require authentication checks. 
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}

Next.js has documentation on middleware authentication strategies here.

The snippets below are pulled from one of our demo applications - source code here.

In src/app/shared-actions.ts, getStytchUser() authenticates the current session stored in cookies, redirecting to / if there is no session. This is the shared server action that can be used to gate an action behind authentication.

In src/app/get-code/actions.tsx, getStytchUser() is used to gate the action (saving a photo) behind Stytch User authentication.

src/app/shared-actions.ts

export async function getStytchUser() {
  const cookieStore = cookies();
  const sessionCookie = cookieStore.get("stytch_session");

  if (!sessionCookie) {
    redirect("/");
  }

  let session;
  try {
    session = await stytch.sessions.authenticate({
      session_token: sessionCookie?.value,
    });
  } catch (error) {
    // log error
  }

  return session.user;
}

Example usage in src/app/get-code/actions.tsx:

export async function saveUserToPhotoAction(
  previousState: FormState,
  formData: FormData,
) {
  const user = await getStytchUser();

  const code = formData.get("code") as string;

  // ...

  redirect("/profile");
}