> ## Documentation Index
> Fetch the complete documentation index at: https://stytch.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding magic links to a custom auth flow

> Integrate with Stytch's headless or backend SDKs for complete control over your authentication flow.

export const jit_provisioning = "Just-in-time provisioning: allow new Members to be added to an Organization as soon as they authenticate, based on email domain or SSO connection.";

export const ist = "A session token designed to preserve state when the user has completed an initial authentication step, but has not fully authenticated into an Organization.";

export const organization = "Represents an instance or tenant in your application, typically mapping to each of your top-level customers.";

export const discovery = "Centralized login flow that allows users to view all Organizations they have access to, including pending invites and Organizations they are allowed to automatically join based on their verified email domain.";

## Overview

<Tabs>
  <Tab title="Headless frontend SDK">
    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 <Tooltip tip={discovery}>Discovery</Tooltip> and Organization-specific authentication flows are supported.

    <Tabs>
      <Tab title="Discovery authentication">
        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 <Tooltip tip={organization}>Organization</Tooltip> 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

        <Steps>
          <Step title="Initiate magic link email">
            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.

            <CodeGroup>
              ```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 <button onClick={sendDiscoveryEmail}>Send email</button>;
              };
              ```

              ```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 <button onClick={sendDiscoveryEmail}>Send email</button>;
              };
              ```

              ```javascript Vanilla JS icon=square-js theme={null}
              <button onclick="sendDiscoveryEmail()">Send email</button>

              <script>
              import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
              const stytch = new StytchB2BClient('STYTCH_PUBLIC_TOKEN');
              const sendDiscoveryEmail = () => {
                  stytch.magicLinks.email.discovery.send({
                  email_address: 'sandbox@stytch.com',
                  });
              };
              </script>
              ```
            </CodeGroup>
          </Step>

          <Step title="Handle callback">
            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.

            <CodeGroup>
              ```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 <div>Loading</div>;
              };
              ```

              ```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 <div>Loading</div>;
              };
              ```

              ```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 });
              }
              ```
            </CodeGroup>

            <Info>
              An <Tooltip tip={ist}>intermediate\_session\_token</Tooltip> will be returned. The headless SDK will automatically set this in cookies and include it in follow-up calls.
            </Info>
          </Step>

          <Step title="Handle organization selection">
            When the end user selects an Organization to log into, call the exchange intermediate session method with the selected Organization ID.

            <CodeGroup>
              ```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 <div>Log In</div>;
              };
              ```

              ```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 <div>Log In</div>;
              };
              ```

              ```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'
                });
              };
              ```
            </CodeGroup>
          </Step>

          <Step title="(Optional) Support organization creation">
            Allow users to create a new Organization instead of logging into an existing one.

            <CodeGroup>
              ```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 <div>Create Organization</div>;
              };
              ```

              ```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 <div>Create Organization</div>;
              };
              ```

              ```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();
              };
              ```
            </CodeGroup>

            <Tip>
              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.
            </Tip>
          </Step>
        </Steps>
      </Tab>

      <Tab title="Organization-specific authentication">
        If end users login via a page that indicates which Organization they're trying to access (e.g., `<org-slug>.your-app.com` or `your-app.com/team/<org-slug>`), you can offer Organization-specific authentication.

        ### Prerequisites

        1. Enable the Frontend SDKs in [your Stytch Dashboard](https://stytch.com/dashboard/sdk-configuration)

        ### Implementation

        <Steps>
          <Step title="Initiate magic link email">
            On your login page for the organization, take in a user's email address and call the `magicLinks.email.loginOrSignup()` method.

            <CodeGroup>
              ```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 <button onClick={sendEmailMagicLink}>Send email</button>;
              };
              ```

              ```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 <button onClick={sendEmailMagicLink}>Send email</button>;
              };
              ```

              ```html Vanilla JS icon=square-js theme={null}
              <button onclick="sendEmailMagicLink()">Login with email</button>

              <script>
              import { StytchB2BClient } from '@stytch/vanilla-js/b2b';
              const stytch = new StytchB2BClient('STYTCH_PUBLIC_TOKEN');
              const sendEmailMagicLink = () => {
                  stytch.magicLinks.email.loginOrSignup({
                  email_address: 'sandbox@stytch.com',
                  organization_id:'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
                  });
              };
              </script>
              ```
            </CodeGroup>
          </Step>

          <Step title="Handle callback">
            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.

            <CodeGroup>
              ```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 <div>Loading</div>;
              }
              ```

              ```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 <div>Loading</div>;
              };
              ```

              ```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,
              });
              ```
            </CodeGroup>
          </Step>
        </Steps>
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Backend SDK">
    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 <Tooltip tip={discovery}>Discovery</Tooltip> and Organization-specific authentication flows are supported.

    <Tabs>
      <Tab title="Discovery authentication">
        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 <Tooltip tip={organization}>Organization</Tooltip> they are trying to access.

        ### Implementation

        <Steps>
          <Step title="Initiate magic link email">
            Prompt the user to input their email and then call the [SendDiscoveryEmail API](/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
            ```
          </Step>

          <Step title="Handle callback">
            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
            ```
          </Step>

          <Step title="Handle organization selection">
            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/<string:organization_id>", 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
            ```
          </Step>

          <Step title="(Optional) Support organization creation">
            Allow users to create a new Organization instead of logging into an existing one.

            <Tip>
              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.
            </Tip>

            ```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
            ```
          </Step>
        </Steps>
      </Tab>

      <Tab title="Organization-specific authentication">
        If end users login via a page that indicates which Organization they're trying to access (e.g., `<org-slug>.your-app.com` or `your-app.com/team/<org-slug>`), you can offer Organization-specific authentication.

        ### Implementation

        <Steps>
          <Step title="Initiate magic link email">
            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"
            ```
          </Step>

          <Step title="Handle callback">
            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 <Tooltip tip={jit_provisioning}>JIT Provisioning</Tooltip>. 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
            ```
          </Step>
        </Steps>
      </Tab>
    </Tabs>
  </Tab>
</Tabs>
