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

# Via backend SDK or API

> Examples and considerations when doing a server-side integration with Stytch.

<Tip>
  Our [frontend SDK](./via-sdk) provide automatic session management, prebuilt login or signup, multi-step flow handling, and more. They can also be used alongside our backend API or SDKs.
</Tip>

## Quickstart

This quickstart guides you through building a Discovery login flow. Select a framework:

<Tabs>
  <Tab title="Node.js" icon="node-js">
    <Columns cols={4}>
      <Card title="Example apps" icon="github" href="https://github.com/stytchauth/stytch-all-examples/tree/main/backend/node/b2b">
        Node.js code examples
      </Card>

      <Card title="Web demo" icon="compass" href="https://www.stytchb2bdemo.app/">
        Hosted Node.js demo
      </Card>

      <Card title="Node.js SDK" icon="node-js" href="https://github.com/stytchauth/stytch-node">
        Stytch Node.js library
      </Card>

      <Card title="API reference" icon="book-text" href="/api-reference/b2b/api/overview">
        API & SDK references
      </Card>
    </Columns>

    <Steps>
      <Step title="Before you start">
        * Have your `public_token` and `secret_key` from your Stytch project & environment.
        * While the guide uses Express, the Node.js SDK is framework agnostic.
      </Step>

      <Step title="Install the Node.js SDK and configure API keys">
        1. Install the SDK in your Express environment:

        ```bash npm icon="terminal" theme={null}
        npm install stytch
        ```

        2. Then add your Stytch API keys to your project's `.env` file:

        ```dotEnv .env icon=file-code theme={null}
        STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
        STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
        # Use your Project's 'live' or any available 'test' credentials
        ```
      </Step>

      <Step title="Set up your app and login routes">
        1. Initialize the Stytch client with your environment variables.
        2. Create a `/login` route that takes in a user's email address and initiates the login or signup flow.

        ```javascript title="POST '/login'" lines theme={null}
        const express = require('express');
        const stytch = require('stytch');
        const session = require('express-session');
        const bodyParser = require('body-parser');

        const app = express();
        app.use(express.json());

        /**
         * Express session management
        */
        app.use(session({
          resave: true,
          saveUninitialized: false,
          secret: 'session-signing-secret',
          cookie: {maxAge: 60000}
        }));

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

        // Key to retrieve the session token from express-session
        // Referenced as req.session[StytchSessionToken]
        const StytchSessionToken = 'stytch_session_token'

        app.post('/login', (req, res) => {
          const email = req.body.email;
          stytchClient.magicLinks.email.discovery.send({
            email_address: email
          })
          .then(response => {
            res.json(response)
          })
          .catch(err => {
            res.status(500).send(err.toString())
          });
        });

        app.listen(3000, () => console.log('Server running on http://localhost:3000'));
        ```
      </Step>

      <Step title="Add a route to handle Stytch's redirect callback">
        1. Stytch calls the [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) with a token to complete a user's authentication flow.
        2. Add an `/authenticate` route to handle the redirect callback in your app.

        ```javascript title="GET '/authenticate'" lines theme={null}
        app.get('/authenticate', async (req, res) => {
          const token = req.query.token;
          const tokenType = req.query.stytch_token_type;

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

          const authResp = await stytchClient.magicLinks.discovery.authenticate({
            discovery_magic_links_token: token,
          });
          if (authResp.status_code !== 200) {
            console.error('Authentication error')
            res.status(500).send();
            return;
          }
        });
        ```
      </Step>

      <Step title="Log in to an organization">
        1. At this point, a user has been authenticated but still needs to specify an [Organization](/api-reference/b2b/api/organizations/organization-object) to log in to and begin a session.
        2. Stytch provides an `intermediate_session_token` that you can use to preserve the user's authentication state while you provide them with options to proceed (e.g. selecting from a list of organizations).
        3. For this example, we'll add logic to automatically sign in to an existing organization or create one if none already exist:

        ```javascript title="GET '/authenticate'" lines theme={null}
        app.get('/authenticate', async (req, res) => {
          const token = req.query.token;
          const tokenType = req.query.stytch_token_type;

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

          const authResp = await stytchClient.magicLinks.discovery.authenticate({
            discovery_magic_links_token: token,
          });
          if (authResp.status_code !== 200) {
            console.error('Authentication error')
            res.status(500).send();
            return;
          }

          // Sign into existing Organization if already Member // [!code ++:32]
          const ist = authResp.intermediate_session_token
          if (authResp.discovered_organizations.length > 0 ) {
            const exchangeResp = await stytchClient.discovery.intermediateSessions.exchange({
              intermediate_session_token: ist,
              organization_id: authResp.discovered_organizations[0].organization.organization_id,
            });
            if (exchangeResp.status_code !== 200) {
              console.error(`Error exchanging IST into Organization: ${JSON.stringify(exchangeResp, null, 2)}`);
              res.status(500).send();
              return;
            }
            // Store the returned session and return session member information
            // Using express sessions with the const key of StytchSessionToken
            req.session[StytchSessionToken] = exchangeResp.session_token;
            res.send(`Hello, ${authResp.member.email_address}! You're logged into the ${authResp.organization.organization_name} organization`);
            return;
          }

          // If not eligible to log into an existing org, create new one
          const createResp = await stytchClient.discovery.organizations.create({
            intermediate_session_token: ist,
          });
          if (createResp.status_code !== 200) {
            console.error(`Error creating Organization: '${JSON.stringify(createResp, null, 2)}'`);
            res.status(500).send();
            return;
          }
          // Store the returned session and return session member information
          req.session[StytchSessionToken] = createResp.session_token;
          res.send(`Hello, ${createResp.member.email_address}! You're logged into the ${createResp.organization.organization_name} organization`);
          return;
        });
        ```
      </Step>

      <Step title="Add a session-protected route">
        1. Create a helper method that returns the user's [Session](/api-reference/b2b/api/sessions/session-object) information.
        2. Use the method to gate any protected route that should only be accessed by an authenticated user.

        ```javascript title="GET '/dashboard'" lines theme={null}
        app.get('/dashboard', async (req, res) => {
          const sessionInfo = await getAuthenticatedMemberAndOrg(req);
          if (sessionInfo && sessionInfo.member && sessionInfo.organization) {
            res.send(`Hello, ${sessionInfo.member.email_address}! You're logged into the ${sessionInfo.organization.organization_name} organization`);
            return;
          }
          res.send("Log in to view this page");
        });

        async function getAuthenticatedMemberAndOrg(req) {
          const sessionToken = req.session[StytchSessionToken];
          if (!sessionToken) {
            return null;
          }

          const resp = await stytchClient.sessions.authenticate({session_token: sessionToken});
          if (resp.status_code !== 200) {
            console.error('Invalid session found');
            req.session[StytchSessionToken] = undefined;
            return null;
          }

          req.session[StytchSessionToken] = resp.session_token;
          return {
            member: resp.member,
            organization: resp.organization,
          };
        }
        ```
      </Step>

      <Step title="Full completed example:">
        ```javascript expandable lines theme={null}
        const express = require('express');
        const stytch = require('stytch');
        const session = require('express-session');
        const bodyParser = require('body-parser');

        const app = express();
        app.use(express.json());

        /**
        * Express session management
        */
        app.use(session({
          resave: true,
          saveUninitialized: false,
          secret: 'session-signing-secret',
          cookie: {maxAge: 60000}
        }));

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

        // Key to retrieve the session token from express-session
        // Referenced as req.session[StytchSessionToken]
        const StytchSessionToken = 'stytch_session_token'

        /**
        * Retrieves the authenticated member and organization from the session, if present.
        * @param req Express request.
        * @returns {Promise<{organization: Organization, member: Member}|null>}
        */
        async function getAuthenticatedMemberAndOrg(req) {
          const sessionToken = req.session[StytchSessionToken];
          if (!sessionToken) {
              return null;
          }

          const resp = await stytchClient.sessions.authenticate({session_token: sessionToken});
          if (resp.status_code !== 200) {
              console.error('Invalid session found');
              req.session[StytchSessionToken] = undefined;
              return null;
          }

          req.session[StytchSessionToken] = resp.session_token;
          return {
              member: resp.member,
              organization: resp.organization,
          };
        }

        /**
        * Routes
        */

        app.post('/login', (req, res) => {
          const email = req.body.email;
          stytchClient.magicLinks.email.discovery.send({
            email_address: email
          })
          .then(response => {
            res.json(response)
          })
          .catch(err => {
            res.status(500).send(err.toString())
          });
        });

        app.get('/authenticate', async (req, res) => {
          const token = req.query.token;
          const tokenType = req.query.stytch_token_type;

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

          const authResp = await stytchClient.magicLinks.discovery.authenticate({
            discovery_magic_links_token: token,
          });
          if (authResp.status_code !== 200) {
            console.error('Authentication error')
            res.status(500).send();
            return;
          }

          // Sign into existing Organization if already Member
          const ist = authResp.intermediate_session_token
          if (authResp.discovered_organizations.length > 0 ) {
            const exchangeResp = await stytchClient.discovery.intermediateSessions.exchange({
              intermediate_session_token: ist,
              organization_id: authResp.discovered_organizations[0].organization.organization_id,
            });
            if (exchangeResp.status_code !== 200) {
              console.error(`Error exchanging IST into Organization: ${JSON.stringify(exchangeResp, null, 2)}`);
              res.status(500).send();
              return;
            }
            // Store the returned session and return session member information
            req.session[StytchSessionToken] = exchangeResp.session_token;
            res.redirect('/dashboard');
            return;
          }

          // If not eligible to log into an existing org, create new one
          const createResp = await stytchClient.discovery.organizations.create({
            intermediate_session_token: ist,
          });
          if (createResp.status_code !== 200) {
            console.error(`Error creating Organization: '${JSON.stringify(createResp, null, 2)}'`);
            res.status(500).send();
            return;
          }
          // Store the returned session and return session member information
          req.session[StytchSessionToken] = createResp.session_token;
          res.redirect('/dashboard');
          return;
        });

        app.get('/logout', (req, res) => {
          req.session[StytchSessionToken] = undefined;
          res.redirect('/dashboard');
        });

        app.get('/dashboard', async (req, res) => {
          const sessionInfo = await getAuthenticatedMemberAndOrg(req);
          if (sessionInfo && sessionInfo.member && sessionInfo.organization) {
            res.send(`Hello, ${sessionInfo.member.email_address}! You're logged into the ${sessionInfo.organization.organization_name} organization`);
            return;
          }
          res.send("Log in to view this page");
        });

        app.listen(3000, () => console.log('Server running on http://localhost:3000'));
        ```
      </Step>

      <Step title="Test your application" icon="check">
        * Run your application and send a `POST` request to the `/login` endpoint with your email address.
        * You should receive an email magic link in your inbox.
        * Click on the magic link you recieved to finish the login or signup flow.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Go" icon="golang">
    <Columns cols={3}>
      <Card title="Example apps" icon="github" href="https://github.com/stytchauth/stytch-all-examples/tree/main/backend/go/b2b">
        Go code examples
      </Card>

      <Card title="Go SDK" icon="golang" href="https://github.com/stytchauth/stytch-go">
        Stytch Go library
      </Card>

      <Card title="API reference" icon="book-text" href="/api-reference/b2b/api/overview">
        API & SDK references
      </Card>
    </Columns>

    <Steps>
      <Step title="Before you start">
        Have your `public_token` and `secret_key` from your Stytch project & environment.
      </Step>

      <Step title="Install the Go SDK and configure API keys">
        1. Install the SDK:

        ```bash go icon="terminal" theme={null}
        go get github.com/stytchauth/stytch-go/v16
        ```

        2. Then add your Stytch API keys to your project's `.env` file:

        ```dotEnv .env icon=file-code theme={null}
        STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
        STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
        # Use your Project's 'live' or any available 'test' credentials
        ```
      </Step>

      <Step title="Set up your app and login routes">
        1. Initialize the Stytch client with your environment variables.
        2. Register an HTTP handler for a `/login` route that takes in a user's email address and initiates the login or signup flow:

        ```go lines theme={null}
        package main

        import (
          "context"
          "fmt"
          "log"
          "net/http"
          "os"

          gorillaSessions "github.com/gorilla/sessions"
          "github.com/joho/godotenv"
          "github.com/stytchauth/stytch-go/v16/stytch/b2b/b2bstytchapi"
          discoveryOrgs "github.com/stytchauth/stytch-go/v16/stytch/b2b/discovery/organizations"
          "github.com/stytchauth/stytch-go/v16/stytch/b2b/magiclinks/discovery"
          emlDiscovery "github.com/stytchauth/stytch-go/v16/stytch/b2b/magiclinks/email/discovery"
        )

        var ctx = context.Background()
        const sessionKey = "stytch_session_token"

        func main() {
          // Load variables from .env file into the environment.
          if err := godotenv.Load(".env.local"); err != nil {
            log.Fatalf("Error loading .env file: %v", err)
          }

          // Instantiate a new API service.
          service := NewAuthService(
            os.Getenv("STYTCH_PROJECT_ID"),
            os.Getenv("STYTCH_SECRET"),
          )

          // Register HTTP handlers.
          mux := http.NewServeMux()
          mux.HandleFunc("/login", service.sendMagicLinkHandler)

          // Start server.
          server := http.Server{
            Addr:    ":3000",
            Handler: mux,
          }
          log.Println("WARNING: For testing purposes only. Not intended for production use...")
          log.Println("Starting server on http://localhost:3000")
          if err := server.ListenAndServe(); err != nil {
            log.Fatal(err)
          }
        }

        type AuthService struct {
          client *b2bstytchapi.API
          store  *gorillaSessions.CookieStore
        }

        func NewAuthService(projectId, secret string) *AuthService {
          client, err := b2bstytchapi.NewClient(projectId, secret)
          if err != nil {
            log.Fatalf("Error creating client: %v", err)
          }

          return &AuthService{
            client: client,
            store:  gorillaSessions.NewCookieStore([]byte("your-secret-key")),
          }
        }

        func (s *AuthService) sendMagicLinkHandler(w http.ResponseWriter, r *http.Request) {
          if err := r.ParseForm(); err != nil {
            log.Printf("Error parsing form: %v\n", err)
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
          }

          email := r.Form.Get("email")
          if email == "" {
            http.Error(w, "Email is required", http.StatusBadRequest)
            return
          }

          _, err := s.client.MagicLinks.Email.Discovery.Send(
            ctx,
            &emlDiscovery.SendParams{
              EmailAddress: email,
          })
            if err != nil {
                log.Printf("Error sending email: %v\n", err)
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }

          w.WriteHeader(http.StatusOK)
            fmt.Fprintln(w, "Successfully sent magic link email!")
        }
        ```
      </Step>

      <Step title="Add a route to handle Stytch's redirect callback">
        1. Stytch calls the [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) with a token to complete a user's authentication flow.
        2. Add a route to handle the `/authenticate` redirect callback in your app.

        ```go lines theme={null}
        import (
          // [!code ++:2]
          // Add new imports
          "github.com/stytchauth/stytch-go/v16/stytch/b2b/magiclinks/discovery"
        )

        func main() {
          // [!code ++:2]
          // Add new HTTP handler
          mux.HandleFunc("/authenticate", service.authenticateHandler)
        }

        // [!code ++:25]
        func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
          tokenType := r.URL.Query().Get("stytch_token_type")
          token := r.URL.Query().Get("token")

          if tokenType != "discovery" {
            log.Printf("Error: unrecognized token type %s\n", tokenType)
            http.Error(
              w,
              fmt.Sprintf("Unrecognized token type %s", tokenType),
              http.StatusBadRequest,
            )
            return
          }

          resp, err := s.client.MagicLinks.Discovery.Authenticate(
            ctx,
            &discovery.AuthenticateParams{
              DiscoveryMagicLinksToken: token,
          })
          if err != nil {
            log.Printf("Error authenticating: %v\n", err)
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
          }
        }
        ```
      </Step>

      <Step title="Log in to an organization">
        1. At this point, a user has been authenticated but still needs to specify an [Organization](/api-reference/b2b/api/organizations/organization-object) to log in to and begin a session.
        2. `s.client.MagicLinks.Discovery.Authenticate()` returns an `intermediate_session_token` that you can use to preserve the user's authentication state while you provide them with options to proceed (e.g. selecting from a list of organizations).
        3. For this example, we'll add logic to automatically sign in to an existing organization or create one if none already exist:

        ```go lines theme={null}
        import (
          // [!code ++:3]
          // Add new imports
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/intermediatesessions"
          discoveryOrgs "github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/organizations"
        )

        func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
          tokenType := r.URL.Query().Get("stytch_token_type")
          token := r.URL.Query().Get("token")

          if tokenType != "discovery" {
            log.Printf("Error: unrecognized token type %s\n", tokenType)
            http.Error(
              w,
              fmt.Sprintf("Unrecognized token type %s", tokenType),
              http.StatusBadRequest
            )
            return
          }

          resp, err := s.client.MagicLinks.Discovery.Authenticate(
            ctx,
            &discovery.AuthenticateParams{
              DiscoveryMagicLinksToken: token,
          })
          if err != nil {
            log.Printf("Error authenticating: %v\n", err)
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
          }

          // [!code ++:42]
          ist := resp.IntermediateSessionToken
          var sessionToken string
          if len(resp.DiscoveredOrganizations) > 0 {
            organizationId := resp.DiscoveredOrganizations[0].Organization.OrganizationID
            resp, err := s.client.Discovery.IntermediateSessions.Exchange(
              ctx,
              &intermediatesessions.ExchangeParams{
                IntermediateSessionToken: ist,
                OrganizationID:           organizationId,
            })
            if err != nil {
              log.Printf("Error exchanging organization: %v\n", err)
              http.Error(w, err.Error(), http.StatusInternalServerError)
              return
            }
            sessionToken = resp.SessionToken
          } else {
            resp, err := s.client.Discovery.Organizations.Create(
              ctx,
              &discoveryOrgs.CreateParams{
                IntermediateSessionToken: ist,
            })
            if err != nil {
              log.Printf("Error creating organization: %v\n", err)
              http.Error(w, err.Error(), http.StatusInternalServerError)
              return
            }
            sessionToken = resp.SessionToken
          }

          // Store the session token
          session, _ := s.store.Get(r, sessionKey)
          session.Values["token"] = sessionToken
          _ = session.Save(r, w)

          w.WriteHeader(http.StatusOK)
          fmt.Fprintf(
            w,
            "Welcome %s! You're logged into the %s organization",
            resp.Member.EmailAddress,
            resp.Organization.OrganizationName
          )
        }
        ```
      </Step>

      <Step title="Add a session-protected route">
        1. Create a helper method that returns the user's [Session](/api-reference/b2b/api/sessions/session-object) information.
        2. Use the method to gate any protected route that should only be accessed by an authenticated user.

        ```go lines theme={null}
        import (
          // [!code ++:3]
          // Add new import
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/sessions"
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/organizations"
        )

        func main() {
          // [!code ++:2]
          // Add new HTTP handler
          mux.HandleFunc("/", service.indexHandler)
        }

        // [!code ++:15]
        func (s *AuthService) indexHandler(w http.ResponseWriter, r *http.Request) {
          member, organization, ok := s.authenticatedMemberAndOrg(w, r)
          if !ok {
            w.WriteHeader(http.StatusOK)
            fmt.Fprintln(w, "Please log in to see this page")
            return
          }
          w.WriteHeader(http.StatusOK)
          fmt.Fprintf(
            w,
            "Welcome %s! You're logged into the %s organization",
            member.EmailAddress,
            organization.OrganizationName,
          )
        }

        // [!code ++:23]
        func (s *AuthService) authenticatedMemberAndOrg(
          w http.ResponseWriter,
          r *http.Request,
        ) (*organizations.Member, *organizations.Organization, bool) {

          sessionToken, exists := s.getSession(w, r, sessionKey)
          if (!exists) {
            return nil, nil, false
          }

          resp, err := s.client.Sessions.Authenticate(
            ctx,
            &sessions.AuthenticateParams{
              SessionToken: sessionToken,
          })
          if err != nil {
            log.Printf("Error authenticating session: %v\n", err)
            s.clearSession(w, r, sessionKey)
            return nil, nil, false
          }

          return &resp.Member, &resp.Organization, true
        }

        // [!code ++:5]
        func (s *AuthService) clearSession(w http.ResponseWriter, r *http.Request, key string) {
          session, _ := s.store.Get(r, key)
          delete(session.Values, "token")
          _ = s.store.Save(r, w, session)
        }

        // [!code ++:8]
        func (s *AuthService) getSession(w http.ResponseWriter, r *http.Request, key string) (string, bool) {
          session, err := s.store.Get(r, key)
          if session == nil || err != nil {
            return "", false
          }
          token, ok := session.Values["token"].(string)
          return token, token != "" && ok
        }
        ```
      </Step>

      <Step title="Full completed example">
        ```go expandable lines theme={null}
        package main

        import (
          "context"
          "fmt"
          "log"
          "net/http"
          "os"

          gorillaSessions "github.com/gorilla/sessions"
          "github.com/joho/godotenv"
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/b2bstytchapi"
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/intermediatesessions"
          discoveryOrgs "github.com/stytchauth/stytch-go/v15/stytch/b2b/discovery/organizations"
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/magiclinks/discovery"
          emlDiscovery "github.com/stytchauth/stytch-go/v15/stytch/b2b/magiclinks/email/discovery"
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/organizations"
          "github.com/stytchauth/stytch-go/v15/stytch/b2b/sessions"
        )

        var ctx = context.Background()

        const sessionKey = "stytch_session_token"

        func main() {
          // Load variables from .env file into the environment.
          if err := godotenv.Load(".env.local"); err != nil {
            log.Fatalf("Error loading .env file: %v", err)
          }

          // Instantiate a new API service.
          service := NewAuthService(
            os.Getenv("STYTCH_PROJECT_ID"),
            os.Getenv("STYTCH_SECRET"),
          )

          // Register HTTP handlers.
          mux := http.NewServeMux()
          mux.HandleFunc("/", service.indexHandler)
          mux.HandleFunc("/login", service.sendMagicLinkHandler)
          mux.HandleFunc("/authenticate", service.authenticateHandler)
          mux.HandleFunc("/logout", service.logoutHandler)

          // Start server.
          server := http.Server{
            Addr:    ":3000",
            Handler: mux,
          }
          log.Println("WARNING: For testing purposes only. Not intended for production use...")
          log.Println("Starting server on http://localhost:3000")
          if err := server.ListenAndServe(); err != nil {
            log.Fatal(err)
          }
        }

        type AuthService struct {
          client *b2bstytchapi.API
          store  *gorillaSessions.CookieStore
        }

        func NewAuthService(projectId, secret string) *AuthService {
          client, err := b2bstytchapi.NewClient(projectId, secret)
          if err != nil {
            log.Fatalf("Error creating client: %v", err)
          }

          return &AuthService{
            client: client,
            store:  gorillaSessions.NewCookieStore([]byte("your-secret-key")),
          }
        }

        func (s *AuthService) sendMagicLinkHandler(w http.ResponseWriter, r *http.Request) {
          if err := r.ParseForm(); err != nil {
            log.Printf("Error parsing form: %v\n", err)
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
          }

          email := r.Form.Get("email")
          if email == "" {
            http.Error(w, "Email is required", http.StatusBadRequest)
            return
          }

          _, err := s.client.MagicLinks.Email.Discovery.Send(
            ctx,
            &emlDiscovery.SendParams{
              EmailAddress: email,
            })
          if err != nil {
            log.Printf("Error sending email: %v\n", err)
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
          }

          w.WriteHeader(http.StatusOK)
          fmt.Fprintln(w, "Successfully sent magic link email!")
        }

        func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
          tokenType := r.URL.Query().Get("stytch_token_type")
          token := r.URL.Query().Get("token")

          if tokenType != "discovery" {
            log.Printf("Error: unrecognized token type %s\n", tokenType)
            http.Error(w, fmt.Sprintf("Unrecognized token type %s", tokenType), http.StatusBadRequest)
            return
          }

          resp, err := s.client.MagicLinks.Discovery.Authenticate(
            ctx,
            &discovery.AuthenticateParams{
              DiscoveryMagicLinksToken: token,
            })
          if err != nil {
            log.Printf("Error authenticating: %v\n", err)
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
          }

          ist := resp.IntermediateSessionToken
          var sessionToken string
          if len(resp.DiscoveredOrganizations) > 0 {
            organizationId := resp.DiscoveredOrganizations[0].Organization.OrganizationID
            resp, err := s.client.Discovery.IntermediateSessions.Exchange(
              ctx,
              &intermediatesessions.ExchangeParams{
                IntermediateSessionToken: ist,
                OrganizationID:           organizationId,
              })
            if err != nil {
              log.Printf("Error exchanging organization: %v\n", err)
              http.Error(w, err.Error(), http.StatusInternalServerError)
              return
            }
            sessionToken = resp.SessionToken
          } else {
            resp, err := s.client.Discovery.Organizations.Create(
              ctx,
              &discoveryOrgs.CreateParams{
                IntermediateSessionToken: ist,
              })
            if err != nil {
              log.Printf("Error creating organization: %v\n", err)
              http.Error(w, err.Error(), http.StatusInternalServerError)
              return
            }
            sessionToken = resp.SessionToken
          }

          // Store the session token
          session, _ := s.store.Get(r, sessionKey)
          session.Values["token"] = sessionToken
          _ = session.Save(r, w)

          s.indexHandler(w, r)
        }

        func (s *AuthService) indexHandler(w http.ResponseWriter, r *http.Request) {

          member, organization, ok := s.authenticatedMemberAndOrg(w, r)
          if !ok {
            w.WriteHeader(http.StatusOK)
            fmt.Fprintln(w, "Please log in to see this page")
            return
          }

          w.WriteHeader(http.StatusOK)
          fmt.Fprintf(
            w,
            "Welcome %s! You're logged into the %s organization",
            member.EmailAddress,
            organization.OrganizationName,
          )
        }

        func (s *AuthService) logoutHandler(w http.ResponseWriter, r *http.Request) {
          s.clearSession(w, r, sessionKey)
          s.indexHandler(w, r)
          return
        }

        func (s *AuthService) authenticatedMemberAndOrg(
          w http.ResponseWriter,
          r *http.Request,
        ) (*organizations.Member, *organizations.Organization, bool) {

          sessionToken, exists := s.getSession(w, r, sessionKey)
          if !exists {
            return nil, nil, false
          }

          resp, err := s.client.Sessions.Authenticate(
            ctx,
            &sessions.AuthenticateParams{
              SessionToken: sessionToken,
            })
          if err != nil {
            log.Printf("Error authenticating session: %v\n", err)
            s.clearSession(w, r, sessionKey)
            return nil, nil, false
          }

          return &resp.Member, &resp.Organization, true
        }

        func (s *AuthService) clearSession(w http.ResponseWriter, r *http.Request, key string) {
          session, _ := s.store.Get(r, key)
          delete(session.Values, "token")
          _ = s.store.Save(r, w, session)
        }

        func (s *AuthService) getSession(w http.ResponseWriter, r *http.Request, key string) (string, bool) {
          session, err := s.store.Get(r, key)
          if session == nil || err != nil {
            return "", false
          }
          token, ok := session.Values["token"].(string)
          return token, ok && token != ""
        }
        ```
      </Step>

      <Step title="Test your application" icon="check">
        * Run your application and send a `POST` request to the `/login` endpoint with your email address.
        * You should receive an email magic link in your inbox.
        * Click on the magic link you recieved to finish the login or signup flow.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Python" icon="python">
    <Columns cols={2}>
      <Card title="Python SDK" icon="python" href="https://github.com/stytchauth/stytch-python">
        Stytch Python library
      </Card>

      <Card title="API reference" icon="book-text" href="/api-reference/b2b/api/overview">
        API & SDK references
      </Card>
    </Columns>

    <Steps>
      <Step title="Before you start">
        * Have your `public_token` and `secret_key` from your Stytch project & environment.
        * While the guide uses Flask, the Python SDK is framework agnostic.
      </Step>

      <Step title="Install the Python SDK and configure API keys">
        1. Install the SDK in your Express environment:

        ```bash pip icon="terminal" theme={null}
        pip install stytch
        ```

        2. Then add your Stytch API keys to your project's `.env` file:

        ```dotEnv .env icon=file-code theme={null}
        STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
        STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
        # Use your Project's 'live' or any available 'test' credentials
        ```
      </Step>

      <Step title="Set up your app and login routes">
        1. Initialize the Stytch client with your environment variables.
        2. Create a `/login` route that takes in a user's email address and initiates the login or signup flow.

        ```python title="POST '/login'" lines theme={null}
        import os
        import dotenv

        from flask import Flask, request, redirect, session, url_for
        from stytch import B2BClient
        from stytch.core.response_base import StytchError

        # load the .env file
        dotenv.load_dotenv()

        # Load stytch client
        stytch_client = B2BClient(
          project_id=os.getenv("STYTCH_PROJECT_ID"),
          secret=os.getenv("STYTCH_SECRET"),
          environment="test"
        )

        app = Flask(__name__)
        app.secret_key = 'some-secret-key'

        @app.route('/login', methods=['POST'])
        def login():

          data = request.get_json()
          email = data.get("email", None)
          if not email:
            return "Email required"

          try:
            stytch_client.magic_links.email.discovery.send(
              email_address=email
            )
          except StytchError as e:
            return e.details.original_json

          return "Success! Check your email"

        if __name__ == '__main__':
          app.run(host='localhost', port=3000, debug=True)
        ```
      </Step>

      <Step title="Add a route to handle Stytch's redirect callback">
        1. Stytch calls the [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) with a token to complete a user's authentication flow.
        2. Add an route to handle the `/authenticate` redirect callback in your app.

        ```python title="GET '/authenticate'" lines theme={null}
        @app.route('/authenticate', methods=['GET'])
        def authenticate():
          token = request.args.get('token', None)
          token_type = request.args.get('stytch_token_type', None)

          # Distinct token_type for each auth flow
          # so you know which authenticate() method to use
          if token_type != 'discovery':
            return f"token_type: {token_type} not supported"

          try:
            resp = stytch_client.magic_links.discovery.authenticate(
              discovery_magic_links_token=token,
            )
          except StytchError as e:
            return e.details.original_json
        ```
      </Step>

      <Step title="Log in to an organization">
        1. At this point, a user has been authenticated but still needs to specify an [Organization](/api-reference/b2b/api/organizations/organization-object) to log in to and begin a session.
        2. `stytch_client.magic_links.discovery.authenticate()` provides an `intermediate_session_token` that you can use to preserve the user's authentication state while you provide them with options to proceed (e.g. selecting from a list of organizations).
        3. For this example, we'll add logic to automatically sign in to an existing organization or create one if none already exist:

        ```python title="GET '/authenticate'" lines theme={null}
        @app.route('/authenticate', methods=['GET'])
        def authenticate():
          token = request.args.get('token', None)
          token_type = request.args.get('stytch_token_type', None)

          # Distinct token_type for each auth flow
          # so you know which authenticate() method to use
          if token_type != 'discovery':
            return f"token_type: {token_type} not supported"

          try:
            resp = stytch_client.magic_links.discovery.authenticate(
              discovery_magic_links_token=token,
            )
          except StytchError as e:
            return e.details.original_json

          # [!code ++:26]
          ist = resp.intermediate_session_token
          discovered_orgs = resp.discovered_organizations
          if len(discovered_orgs):
            # email belongs to >= 1 organization, log into the first one
            try:
              resp = stytch_client.discovery.intermediate_sessions.exchange(
                intermediate_session_token=ist,
                organization_id=discovered_orgs[0].organization.organization_id,
              )
            except StytchError as e:
              return e.details.original_json

            # Store the returned session in cookies and redirect to home
            session['stytch_session_token'] = resp.session_token
            return redirect(url_for("dashboard"))

          # email does not belong to any organizations, create a new one
          try:
            resp = stytch_client.discovery.organizations.create(
              intermediate_session_token=ist
            )
          except StytchError as e:
            return e.details.original_json

          session['stytch_session_token'] = resp.session_token
          return f"Welcome {resp.member.email_address}! You're logged into the {resp.organization.organization_name} organization"
        ```
      </Step>

      <Step title="Add a session-protected route">
        1. Create a helper method that returns the user's [Session](/api-reference/b2b/api/sessions/session-object) information.
        2. Use the method to gate any protected route that should only be accessed by an authenticated user.

        ```python title="GET '/dashboard'" lines theme={null}
        @app.route('/dashboard', methods=["GET"])
        def dashboard():
          member, organization = get_authenticated_member_and_organization()
          if not member or not organization:
            return "Log in to view this page."

          return f"Welcome {member.email_address}! You're logged into the {organization.organization_name} org!"

        def get_authenticated_member_and_organization():
          stytch_session = session.get('stytch_session_token')
          if not stytch_session:
            return None, None

          try:
            resp = stytch_client.sessions.authenticate(session_token=stytch_session)
          except StytchError as e:
            # Session has expired or is invalid, clear it
            session.pop('stytch_session_token', None)
            return None, None

          return resp.member, resp.organization
        ```
      </Step>

      <Step title="Full completed example:">
        ```python expandable lines theme={null}
        import os
        import dotenv

        from flask import Flask, request, redirect, session, url_for
        from stytch import B2BClient
        from stytch.core.response_base import StytchError

        # load the .env file
        dotenv.load_dotenv()

        # Load stytch client
        stytch_client = B2BClient(
          project_id=os.getenv("STYTCH_PROJECT_ID"),
          secret=os.getenv("STYTCH_SECRET"),
          environment="test"
        )

        app = Flask(__name__)
        app.secret_key = 'some-secret-key'

        @app.route('/login', methods=['POST'])
        def login():

          data = request.get_json()
          email = data.get("email", None)
          if not email:
            return "Email required"

          try:
            stytch_client.magic_links.email.discovery.send(
              email_address=email
            )
          except StytchError as e:
            return e.details.original_json

          return "Success! Check your email"

        @app.route('/authenticate', methods=['GET'])
        def authenticate():
          token = request.args.get('token', None)
          token_type = request.args.get('stytch_token_type', None)

          # Distinct token_type for each auth flow
          # so you know which authenticate() method to use
          if token_type != 'discovery':
            return f"token_type: {token_type} not supported"

          try:
            resp = stytch_client.magic_links.discovery.authenticate(
              discovery_magic_links_token=token,
            )
          except StytchError as e:
            return e.details.original_json

          ist = resp.intermediate_session_token
          discovered_orgs = resp.discovered_organizations
          if len(discovered_orgs):
            # email belongs to >= 1 organization, log into the first one
            try:
              resp = stytch_client.discovery.intermediate_sessions.exchange(
                intermediate_session_token=ist,
                organization_id=discovered_orgs[0].organization.organization_id,
              )
            except StytchError as e:
              return e.details.original_json

            # Store the returned session in cookies and redirect to home
            session['stytch_session_token'] = resp.session_token
            return redirect(url_for("dashboard"))

          # email does not belong to any organizations, create a new one
          try:
            resp = stytch_client.discovery.organizations.create(
              intermediate_session_token=ist
            )
          except StytchError as e:
            return e.details.original_json

          session['stytch_session_token'] = resp.session_token
          return redirect(url_for("dashboard"))

        @app.route('/dashboard', methods=["GET"])
        def dashboard():
          member, organization = get_authenticated_member_and_organization()
          if not member or not organization:
            return "Log in to view this page."

          return f"Welcome {member.email_address}! You're logged into the {organization.organization_name} org!"

        @app.route('/logout', methods=["GET"])
        def logout():
          session.pop('stytch_session_token', None)
          return redirect(url_for('dashboard'))

        def get_authenticated_member_and_organization():
          stytch_session = session.get('stytch_session_token')
          if not stytch_session:
            return None, None

          try:
            resp = stytch_client.sessions.authenticate(session_token=stytch_session)
          except StytchError as e:
            # Session has expired or is invalid, clear it
            session.pop('stytch_session_token', None)
            return None, None

          return resp.member, resp.organization

        if __name__ == '__main__':
          app.run(host='localhost', port=3000, debug=True)
        ```
      </Step>

      <Step title="Test your application" icon="check">
        * Run your application and send a `POST` request to the `/login` endpoint with your email address.
        * You should receive an email magic link in your inbox.
        * Click on the magic link you recieved to finish the login or signup flow.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Ruby on Rails" icon="gem">
    <Columns cols={2}>
      <Card title="Ruby SDK" icon="gem" href="https://github.com/stytchauth/stytch-ruby">
        Stytch Ruby gem
      </Card>

      <Card title="API reference" icon="book-text" href="/api-reference/b2b/api/overview">
        API & SDK references
      </Card>
    </Columns>

    <Steps>
      <Step title="Before you start">
        Have your `public_token` and `secret_key` from your Stytch project & environment.
      </Step>

      <Step title="Install the Ruby SDK and configure API keys">
        1. Install the SDK in your Express environment:

        ```bash gem icon="terminal" theme={null}
        gem 'stytch'
        ```

        2. Then add your Stytch API keys to your project's `.env` file:

        ```dotEnv .env icon=file-code theme={null}
        STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
        STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
        # Use your Project's 'live' or any available 'test' credentials
        ```
      </Step>

      <Step title="Set up your Rails app">
        1. Initialize the Stytch client.
        2. Define `/login` and `/authenticate` routes and controller actions to handle authentication.

        <CodeGroup>
          ```ruby title="config/routes.rb" icon="file-code" theme={null}
          Rails.application.routes.draw do
            post 'login', to: 'authentication#login'
            get 'authenticate', to: 'authentication#authenticate'
          end
          ```

          ```ruby title="app/controllers/authentication_controller.rb" icon="file-code" theme={null}
          class AuthenticationController < ApplicationController

            def stytch_client
              @stytch_client ||= StytchB2B::Client.new(
                project_id: ENV['STYTCH_PROJECT_ID'],
                secret: ENV['STYTCH_SECRET']
              )
            endf

            def login
              email = params[:email]
              response = stytch_client.magic_links.email.discovery.send(
                email_address: email
              )
              render json: response
            rescue => e
              render json: { error: e.message }, status: :internal_server_error
            end

            def authenticate
              token = params[:token]
              response = stytch_client.magic_links.discovery.authenticate(discovery_magic_links_token: token)
              render plain: "Hello, #{response.email_address}! Complete the Discovery flow by creating an Organization with your intermediate session token: #{response.intermediate_session_token}."
            rescue => e
              render plain: e.message, status: :unauthorized
            end
          end
          ```
        </CodeGroup>
      </Step>

      <Step title="Set a redirect URL">
        Specify an [authenticate redirect URL](https://stytch.com/dashboard/redirect-urls) in your Dashboard.
      </Step>

      <Step title="Test your application" icon="check">
        * Run your application and send a `POST` request to the `/login` endpoint with your email address.
        * You should receive an email magic link in your inbox.
        * Click on the magic link, which should redirect you to the `/authenticate` endpoint or redirect URL.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Java" icon="java">
    <Columns cols={2}>
      <Card title="Java SDK" icon="java" href="https://github.com/stytchauth/stytch-java">
        Stytch Java library
      </Card>

      <Card title="API reference" icon="book-text" href="/api-reference/b2b/api/overview">
        API & SDK references
      </Card>
    </Columns>

    ### Create an API client

    <CodeGroup>
      ```kotlin title="Kotlin" theme={null}
      import com.stytch.java.b2b.StytchB2BClient

      val client  = StytchB2BClient(
          projectId = "YOUR_PROJECT_ID",
          secret = "YOUR_SECRET_KEY"
      )
      ```

      ```java title="Java" theme={null}
      import com.stytch.java.b2b.StytchB2BClient;

      StytchB2BClient client = new StytchB2BClient(
          "YOUR_PROJECT_ID",
          "YOUR_SECRET_KEY"
      );
      ```
    </CodeGroup>

    ### Create an organization

    <CodeGroup>
      ```kotlin title="Kotlin" theme={null}
      when (val result = client.organizations.create(CreateRequest(
        organizationName = "Acme Co",
        organizationSlug = "acme-co",
        emailAllowedDomains = listOf("acme.co"),
      ))) {
        is StytchResult.Success -> println(result.value)
        is StytchResult.Error -> println(result.exception)
      }
      ```

      ```java title="Java" theme={null}
      var emailAllowedDomains = new ArrayList<String>();
      emailAllowedDomains.add("acme.co");
      CreateRequest request = new CreateRequest(
        "Acme Co",
        "acme-co",
        emailAllowedDomains
      );
      StytchResult<CreateResponse> response = client.organizations.createCompletable(request).get();
      if (response instanceof StytchResult.Error) {
        var exception = ((StytchResult.Error) response).getException();
        System.out.println(exception.getReason());
      } else {
        System.out.println(((StytchResult.Success<CreateResponse>) response).getValue());
      }
      ```
    </CodeGroup>

    ### Log a user into an organization

    <CodeGroup>
      ```kotlin title="Kotlin" theme={null}
      when (val result = client.magicLinks.email.loginOrSignup(LoginOrSignupRequest(
        organizationId = "organization-id-from-create-response-...",
        emailAddress = "admin@acme.co",
        loginRedirectURL = "https://example.com/authenticate",
        signupRedirectURL = "https://example.com/authenticate",
      )) {
        is StytchResult.Success -> println(result.value)
        is StytchResult.Error -> println(result.exception)
      }
      ```

      ```java title="Java" theme={null}
      LoginOrSignupRequest request = new LoginOrSignupRequest(
        "organization-id-from-create-response-...",
        "admin@acme.co",
        "https://example.com/authenticate",
        "https://example.com/authenticate"
      );
      StytchResult<LoginOrSignupResponse> response = client.magicLinks.getEmail().loginOrSignup(request).get();
      if (response instanceof StytchResult.Error) {
        var exception = ((StytchResult.Error) response).getException();
        System.out.println(exception.getReason());
      } else {
        System.out.println(((StytchResult.Success<LoginOrSignupResponse>) response).getValue());
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Next steps

* Handle a session
