Skip to content

Repository files navigation

@passflow/passflow-react-sdk

This is a SDK for react application.

Table of Contents

to install just type:

pnpm install
pnpm build

Local Development

Component development with Storybook

The visual source of truth lives in src/components/ui and is catalogued in Storybook.

pnpm storybook

Before reusing a new visual state in a flow, add it to the owning UI component and its co-located *.stories.tsx file. Verify the static catalog with:

pnpm build-storybook

Flows and form controllers should map domain state and callbacks into library components rather than creating or styling controls in place. See CLAUDE.md for the repository boundary.

Using Local Passflow JS SDK

For local development and testing with a local version of the Passflow JS SDK, you need to:

  1. Clone the Passflow JS SDK repository in a sibling directory to this project.
  2. remove current dependecy pnpm remove @passflow/core
  3. Link folder with:
pnpm link ../passflow-js-sdk
pnpm install

Now you can run watch mode in libraray mode and change it. It will compile every changes incrementally.

pnpm watch

After all done, we need to unlink and return all to the original state

pnpm remove @passflow/core
pnpm unlink @passflow/core
pnpm install @passflow/core

Test writing Environment Setup

For local development and UI testing, you need to set up the Passflow environment:

  1. Set the PASSFLOW_URL environment variable to point to your Passflow instance.
  2. Set the PASSFLOW_APP_ID environment variable
  3. Run pnpm dev anmd all should works

Refer .env.example for more details.

we are using pnpm. Please ansure you have it in the system.

UI Testing

All UI behavior and Storybook state tests use Playwright. Install Chromium once:

pnpm exec playwright install chromium

Run the complete UI suite headlessly:

pnpm test

Or launch Playwright's interactive test UI:

pnpm test:ui

Both scripts automatically start the demo app and Storybook. pnpm test uses the economical headless configuration, while pnpm test:ui retains traces and a final full-page screenshot for every successful test. Select a runnable leaf test in Playwright UI to inspect its action timeline, DOM snapshots, and screenshot attachment. Tests live in playwright/tests; component-state tests render the real Storybook stories rather than recreating components in a test-only harness. Vitest remains for non-visual hooks, controllers, and utility logic.

Installation

pnpm add @passflow/passflow-react-sdk

Requirements

  • React 18+
  • React Router DOM v6/v7 or Wouter or TanStack Router

Integration

Passflow Cloud

For a quick start with Passflow Cloud:

const passflowConfig: PassflowConfig = {
  url: process.env.PASSFLOW_URL ?? 'http://localhost:5432',
  appId: process.env.PASSFLOW_APP_ID ?? 'test_app_id',
  createTenantForNewUser: true,
  scopes: ['openid', 'email', 'profile', 'offline'],
};

export const PassflowProviderWrapper: FC<PropsWithChildren> = ({
  children,
}) => {
  const navigate = useNavigate(); // from react-router-dom

  return (
    <PassflowProvider
      url={passflowConfig.url}
      appId={passflowConfig.appId}
      createTenantForNewUser={passflowConfig.createTenantForNewUser}
      scopes={passflowConfig.scopes}
      navigate={(options) =>
        navigate(
          {
            pathname: options.to,
            search: options.search,
          },
          { replace: options.replace }
        )
      }
      router="react-router"
    >
      {children}
    </PassflowProvider>
  );
};

export const App = () => (
  <BrowserRouter>
    <PassflowProviderWrapper>
      <PassflowFlow
        federatedDisplayMode='redirect'
        successAuthRedirect='https://jwt.io'
        pathPrefix='/web'
      />
    </PassflowProviderWrapper>
  </BrowserRouter>
);

Built-in OIDC RP integration

<PassflowProvider> is also a full OIDC relying-party SDK out of the box. When a parent_challenge_id query parameter is present on the host page's URL (typical of a Passflow OP redirect to the login UI), the provider:

  1. Installs an interceptor on window.fetch and XMLHttpRequest.prototype.{open,send} that injects parent_challenge_id on every outbound Passflow auth POST body and follows server-returned {redirect_url} JSON responses with window.location.assign. Covers /auth/login, /auth/passwordless/*, /auth/passkey/*, /auth/2fa/*, /auth/federated/start, and /auth/session/exchange.

  2. Runs an exchange-on-mount probe. If the SDK has existing tokens AND the parent challenge allows it (prompt=login not set, max_age not exceeded), the provider POSTs /auth/session/exchange with the current access token and navigates the browser when the server returns a redirect URL. This satisfies OIDC Core §3.1.2.1 / §3.1.2.4 (prompt=none, max_age, id_token_hint) without re-prompting an already-authenticated user.

  3. Renders a placeholder during exchange (theme via the continuingToAppPlaceholder prop on PassflowFlow — TODO once the prop is wired through; today the default placeholder appears).

  4. Falls through to the login form when exchange is impossible or returns 403 upgrade_required. The login form gets an explanatory banner (also themeable).

Consumer code remains a single line of plumbing — mount <PassflowProvider>, render <PassflowFlow> inside, done. No manual interceptor wiring required.

See the project's docs/adr/0002-session-exchange-endpoint.md for the server contract and docs/adr/0003-amr-acr-claims.md for the authentication-context claims those tokens carry.

PassflowFlow

Prop Type Description
successAuthRedirect string URL to redirect after successful authentication
federatedDisplayMode "popup" | "redirect" Federated authentication display mode
pathPrefix string Prefix for all routes (optional)

React Router DOM

Example of integration with React Router DOM: PS: The example uses the Declarative approach.

import {
  PassflowProvider,
  SignIn,
  SignUp,
  ForgotPassword,
  ForgotPasswordSuccess,
} from "@passflow/passflow-react-sdk";
import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom";

const passflowConfig = {
  url: import.meta.env.VITE_PASSFLOW_URL,
  appId: import.meta.env.VITE_PASSFLOW_APP_ID,
  createTenantForNewUser: true,
  scopes: ["id", "offline", "email", "profile", "openid", "management"],
};

const PassflowProviderWrapper = ({ children }) => {
  const navigate = useNavigate();

  return (
    <PassflowProvider
      url={passflowConfig.url}
      appId={passflowConfig.appId}
      createTenantForNewUser={passflowConfig.createTenantForNewUser}
      scopes={passflowConfig.scopes}
      navigate={(options) =>
        navigate(
          {
            pathname: options.to,
            search: options.search,
          },
          { replace: options.replace }
        )
      }
      router="react-router"
    >
      {children}
    </PassflowProvider>
  );
};

export const App = () => (
  <BrowserRouter>
    <PassflowProviderWrapper>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/signin" element={<SignIn successAuthRedirect="/" signUpPath="/signup" />} />
        <Route path="/signup" element={<SignUp successAuthRedirect="/" signInPath="/signin" />} />
        <Route 
          path="/forgot-password" 
          element={
            <ForgotPassword
              successResetRedirect="/"
              signInPath="/signin"
              forgotPasswordSuccessPath="/forgot-password/success"
            />
          } 
        />
        <Route path="/forgot-password/success" element={<ForgotPasswordSuccess />} />
        {/* Add other routes here */}
      </Routes>
    </PassflowProviderWrapper>
  </BrowserRouter>
);

Wouter

Example of integration with Wouter:

import {
  PassflowProvider,
  SignIn,
  SignUp,
  ForgotPassword,
  ForgotPasswordSuccess,
} from "@passflow/passflow-react-sdk";
import { Switch, Route, useLocation } from "wouter";

const passflowConfig = {
  url: import.meta.env.VITE_PASSFLOW_URL,
  appId: import.meta.env.VITE_PASSFLOW_APP_ID,
  createTenantForNewUser: true,
  scopes: ["id", "offline", "email", "profile", "openid", "management"],
};

const PassflowProviderWrapper = ({ children }) => {
  const [, navigate] = useLocation();

  return (
    <PassflowProvider
      url={passflowConfig.url}
      appId={passflowConfig.appId}
      createTenantForNewUser={passflowConfig.createTenantForNewUser}
      scopes={passflowConfig.scopes}
      navigate={(options) => {
        const searchParamWouter = options.search
          ? options.search.startsWith("?")
            ? options.search
            : `?${options.search}`
          : "";
        navigate(`${options.to}${searchParamWouter}`, {
          replace: options.replace,
        });
      }}
      router="wouter"
    >
      {children}
    </PassflowProvider>
  );
};

export const App = () => (
  <Switch>
    <PassflowProviderWrapper>
      <Route path="/" component={Home} />
      <Route path="/signin" component={SignInWrapper} />
      <Route path="/signup" component={SignUpWrapper} />
      <Route path="/forgot-password" component={ForgotPasswordWrapper} />
      <Route path="/forgot-password/success" component={ForgotPasswordSuccessWrapper} />
      {/* Add other routes here */}
    </PassflowProviderWrapper>
  </Switch>
);

TanStack Router

Example of integration with TanStack Router: PS: The example uses Code-Based Routing.

// App.tsx
import { PassflowProviderWrapper, RouterProvider } from './providers';

export const App = () => (
  <PassflowProviderWrapper>
    <RouterProvider />
  </PassflowProviderWrapper>
);

// PassflowProviderWrapper.tsx
import { PassflowProvider } from '@passflow/passflow-react-sdk';

const passflowConfig = {
  url: import.meta.env.VITE_PASSFLOW_URL,
  appId: import.meta.env.VITE_PASSFLOW_APP_ID,
  createTenantForNewUser: true,
  scopes: ['id', 'offline', 'email', 'profile', 'openid', 'management'],
};

export const PassflowProviderWrapper = ({ children }) => (
  <PassflowProvider
    url={passflowConfig.url}
    appId={passflowConfig.appId}
    createTenantForNewUser={passflowConfig.createTenantForNewUser}
    scopes={passflowConfig.scopes}
    router="tanstack-router"
  >
    {children}
  </PassflowProvider>
);

// router/root.tsx
import { useNavigation } from '@passflow/passflow-react-sdk';
import { Outlet, useNavigate } from '@tanstack/react-router';

export const Root = () => {
  const navigate = useNavigate();
  const { setNavigate } = useNavigation();

  useEffect(() => {
    setNavigate((options) => navigate(options));
  }, [navigate, setNavigate]);

  return <Outlet />;
};

// router.tsx
import { QueryClient } from '@tanstack/react-query';
import { createRouter } from '@tanstack/react-router';
import { queryClient } from '../query';
import { routerTree } from './routes';
import { Passflow } from '@passflow/passflow-react-sdk';

export interface RouterContext {
  queryClient: QueryClient;
  passflow?: Passflow;
}

export const router = createRouter({
  routeTree: routerTree,
  context: {
    queryClient,
    passflow: undefined,
  },
  defaultPreload: 'intent',
});

declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router;
  }
}


// routes.tsx
import { Outlet, createRootRouteWithContext, createRoute, redirect } from '@tanstack/react-router';
import { RouterContext } from './router';
import { Root } from './root';
import { About, Home } from '@/pages';
import { ForgotPassword, ForgotPasswordSuccess, SignIn, SignUp } from '@passflow/passflow-react-sdk';
import { RootLayout } from '@/layouts';

const redirectToSignin = () => {
  throw redirect({
    to: '/signin',
  });
};

const rootRoute = createRootRouteWithContext<RouterContext>()({
  component: Root,
  notFoundComponent: () => <div>404 Not Found</div>,
});

// PUBLIC ROUTES
const publicRoute = createRoute({
  getParentRoute: () => rootRoute,
  id: 'public',
  component: () => <Outlet />,
});

const signInRoute = createRoute({
  getParentRoute: () => publicRoute,
  path: '/signin',
  component: () => <SignIn successAuthRedirect='/' signUpPath='/signup' />,
});

const signUpRoute = createRoute({
  getParentRoute: () => publicRoute,
  path: '/signup',
  component: () => <SignUp successAuthRedirect='/' signInPath='/signin' />,
});

const forgotPasswordRoute = createRoute({
  getParentRoute: () => publicRoute,
  path: '/forgot-password',
  component: () => (
    <ForgotPassword successResetRedirect='/' signInPath='/signin' forgotPasswordSuccessPath='/forgot-password/success' />
  ),
});

const forgotPasswordSuccessRoute = createRoute({
  getParentRoute: () => publicRoute,
  path: '/forgot-password/success',
  component: () => <ForgotPasswordSuccess />,
});

{/* Add other PASSFLOW COMPONENTS routes here */}

// PROTECTED ROUTES
const protectedRoute = createRoute({
  getParentRoute: () => rootRoute,
  id: 'protected',
  beforeLoad: async ({ context }) => {
    const { passflow } = context;

    await passflow?.session({
      createSession: async (tokens) => {
        console.log(tokens); // if session is created, this function will be called with the tokens
      },
      expiredSession: async () => {
        console.log('expiredSession');
        redirectToSignin(); // if session is expired and refresh token is not valid, redirect to signin
      },
      doRefresh: true,
    });
  },
  component: () => <RootLayout />,
});

const dashboardRoute = createRoute({
  getParentRoute: () => protectedRoute,
  path: '/',
  component: () => <Home />,
});

const aboutRoute = createRoute({
  getParentRoute: () => protectedRoute,
  path: '/about',
  component: () => <About />,
});

{/* Add other protected routes here */}

export const routerTree = rootRoute.addChildren([
  publicRoute.addChildren([signInRoute, signUpRoute, forgotPasswordRoute, forgotPasswordSuccessRoute]),
  protectedRoute.addChildren([dashboardRoute, aboutRoute]),
]);

Props

PassflowProvider

Prop Type Description
url string Passflow server URL
appId string Application ID
createTenantForNewUser boolean Whether to create a tenant for new users
scopes string[] Array of required scopes
router "default" | "react-router" | "wouter" | "tanstack-router" Router being used (optional) (default is native window navigation)
navigate (options: NavigateOptions) => void Navigation function (optional) (default is native window navigation)

Form Components

SignIn

Component for user authentication.

Prop Type Description Default
successAuthRedirect string URL to redirect after successful sign in Required
signUpPath string Path to sign up page (optional) /signup
forgotPasswordPath string Path to forgot password page (optional) /forgot-password
verifyMagicLinkPath string Path to verify magic link page (optional) /verify-challenge-magic-link
verifyOTPPath string Path to verify OTP page (optional) /verify-challenge-otp
federatedDisplayMode "popup" | "redirect" Display mode for federated authentication (optional) "popup"

SignUp

Component for user registration.

Prop Type Description Default
successAuthRedirect string URL to redirect after successful sign up Required
signInPath string Path to sign in page (optional) /signin
verifyMagicLinkPath string Path to verify magic link page (optional) /verify-challenge-magic-link
verifyOTPPath string Path to verify OTP page (optional) /verify-challenge-otp
federatedDisplayMode "popup" | "redirect" Display mode for federated authentication (optional) "popup"

ForgotPassword

Component for password recovery initiation.

Prop Type Description Default
successResetRedirect string URL to redirect after successful password reset Required
signInPath string Path to sign in page (optional) /signin
forgotPasswordSuccessPath string Path to success page after initiating password reset (optional) /forgot-password/success

ForgotPasswordSuccess

Component for password recovery success.

No props required.

ResetPassword

Component for setting a new password.

Prop Type Description Default
successAuthRedirect string URL to redirect after successful password reset Required

VerifyChallengeMagicLink

Component for verifying magic link authentication.

No props required.

VerifyChallengeOTP

Component for OTP verification.

Prop Type Description Default
successAuthRedirect string URL to redirect after successful verification Required
numInputs number Number of OTP input fields (optional) 6
shouldAutoFocus boolean Whether to autofocus the first input (optional) true
signUpPath string Path to sign up page (optional) /signup

InvitationJoin

Component for accepting invitations and joining organizations.

Prop Type Description Default
successAuthRedirect string URL to redirect after successful join Required
signInPath string Path to sign in page (optional) /signin

Hooks

useAuth

Hook for authentication management. Provides methods for checking authentication status, obtaining tokens, and logging out.

const { isAuthenticated, getTokens, logout, isLoading } = useAuth();
Parameter Type Description
initialRefresh boolean (optional) Whether to refresh tokens on mount

Returns:

Property Type Description
isAuthenticated () => boolean Current authentication status
getTokens (doRefresh: boolean) => Promise<{ tokens: Tokens | undefined; parsedTokens: ParsedTokens | undefined; }> Function to get authentication tokens
logout () => void Function to log out user
isLoading boolean Loading state indicator

usePassflow

Hook for accessing the Passflow SDK instance. Must be used within PassflowProvider.

const passflow = usePassflow();

Returns:

Type Description
Passflow Passflow SDK instance

usePassflowStore

Hook for synchronizing state with Passflow SDK. Allows subscribing to token changes.

const tokens = usePassflowStore([PassflowEvent.SignIn, ...]);
Parameter Type Description
events PassflowEvent[] (optional) Events to subscribe to

Returns:

Type Description
Tokens | undefined Current tokens state

useSignIn

Hook for implementing sign-in functionality. Supports password, passkey, and passwordless authentication.

const { fetch, isLoading, isError, error } = useSignIn();

Returns:

Property Type Description
fetch (payload: PassflowPasskeyAuthenticateStartPayload | PassflowSignInPayload | PassflowPasswordlessSignInPayload, type: 'passkey' | 'password' | 'passwordless') => Promise<boolean | string | PassflowPasswordlessResponse> Sign in function
isLoading boolean Loading state
isError boolean Error state
error string Error message

useSignUp

Hook for implementing registration functionality. Supports password, passkey, and passwordless registration.

const { fetch, isLoading, isError, error } = useSignUp();

Returns:

Property Type Description
fetch (payload: PassflowPasskeyRegisterStartPayload | PassflowSignUpPayload | PassflowPasswordlessSignInPayload, type: 'passkey' | 'password' | 'passwordless') => Promise<boolean | PassflowPasswordlessResponse> Sign up function
isLoading boolean Loading state
isError boolean Error state
error string Error message

useNavigation

Hook for navigation between pages. Supports various routers (react-router, wouter, tanstack-router).

const { navigate, setNavigate } = useNavigation();

Returns:

Property Type Description
navigate NavigateFunction Navigation function
setNavigate (newNavigate: NavigateFunction | null) => void Function to update navigation handler

useProvider

Hook for working with federated authentication providers (OAuth).

const { federatedWithPopup, federatedWithRedirect } = useProvider(redirectUrl);
Parameter Type Description
redirectUrl string URL to redirect after authentication

Returns:

Property Type Description
federatedWithPopup (provider: Providers) => void Popup authentication function
federatedWithRedirect (provider: Providers) => void Redirect authentication function

useResetPassword

Hook for resetting user password.

const { fetch, isLoading, isError, error } = useResetPassword();

Returns:

Property Type Description
fetch (newPassword: string) => Promise<boolean> Password reset function
isLoading boolean Loading state
isError boolean Error state
error string Error message

useUserPasskeys

Hook for managing user passkeys (create, edit, delete).

const { data, createUserPasskey, editUserPasskey, deleteUserPasskey } = useUserPasskeys();

Returns:

Property Type Description
data PassflowUserPasskey[] List of user passkeys
createUserPasskey (relyingPartyId: string) => Promise<void> Create passkey function
editUserPasskey (newName: string, passkeyId: string) => Promise<void> Edit passkey function
deleteUserPasskey (passkeyId: string) => Promise<void> Delete passkey function
isLoading boolean Loading state
isError boolean Error state
errorMessage string Error message

useAppSettings

Hook for retrieving application settings and password policies.

const { appSettings, passwordPolicy } = useAppSettings();

Returns:

Property Type Description
appSettings AppSettings | null Application settings
passwordPolicy PassflowPasswordPolicySettings | null Password policy settings
isLoading boolean Loading state
isError boolean Error state
error string Error message

useAuthCloudRedirect

Hook for redirecting to Passflow Cloud.

const redirect = useAuthCloudRedirect(cloudPassflowUrl);
Parameter Type Description
cloudPassflowUrl string Passflow Cloud URL

Returns:

Type Description
() => void Redirect function

useForgotPassword

Hook for initiating the password recovery process.

const { fetch, isLoading, isError, error } = useForgotPassword();

Returns:

Property Type Description
fetch (payload: PassflowSendPasswordResetEmailPayload) => Promise<boolean> Password recovery function
isLoading boolean Loading state
isError boolean Error state
error string Error message

useJoinInvite

Hook for accepting organization invitations.

const { fetch, isLoading, isError, error } = useJoinInvite();

Returns:

Property Type Description
fetch (token: string) => Promise<boolean> Join invitation function
isLoading boolean Loading state
isError boolean Error state
error string Error message

useLogout

Hook for logging out of the system.

const { fetch, isLoading, isError, error } = useLogout();

Returns:

Property Type Description
fetch () => Promise<boolean> Logout function
isLoading boolean Loading state
isError boolean Error state
error string Error message

usePasswordlessComplete

Hook for completing passwordless authentication.

const { fetch, isLoading, isError, error } = usePasswordlessComplete();

Returns:

Property Type Description
fetch (payload: PassflowPasswordlessSignInCompletePayload) => Promise<PassflowValidationResponse | null> Complete passwordless auth function
isLoading boolean Loading state
isError boolean Error state
error string Error message

About

Passflow ReactJS SDK for client side

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages