SSO relogin bypassing microsoft login prompt

Hi Stytch team,

I’m experiencing an issue with Microsoft SSO authentication in my React app using Stytch B2B. The problem occurs specifically after logout and attempting to log in again. When i do login with microsoft, even though all the cookies and session is cleared the slug and token are getting attached to the url

Problem:

After logging out and clicking “Login with Microsoft”, the user is immediately redirected back to the app with a valid Stytch token, bypassing the Microsoft login prompt entirely.

My logout function that clears both client session and backend session, which pass successfully :

const handleLogout = async () => {
    try {
      console.log('🔐 Starting logout process...', { 
        currentUrl: window.location.href,
        hasMember: !!member,
        memberId: member?.member_id
      });

      // 1. Revoke Stytch session on the frontend FIRST
      try {
        console.log("🔐 Attempting to revoke Stytch session on the frontend...");
        await client.session.revoke();
        console.log("✅ Stytch session revoked on the frontend.");
      } catch (err) {
        console.error("❌ Failed to revoke Stytch session:", err);
      }

      // 2. Clear backend session with member_id
      const memberId = member?.member_id;
      try {
        console.log('🔐 Clearing backend session...', { memberId });
        await fetch("http://localhost:5001/auth/logout", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          credentials: "include",
          body: JSON.stringify({ member_id: memberId }),
        });
        console.log('🔐 Backend session cleared successfully');
      } catch (err) {
        console.warn('🔐 Failed to clear backend session:', err);
      }

      // 3. Clear our app's state
      setIsAuthenticated(false);
      setMemberId(null);
      setIntermediateSessionToken(null);

      // 4. Clear local storage and session storage
      console.log('🔐 Clearing storage...');
      localStorage.clear();
      sessionStorage.clear();

      // 5. Redirect to login page
      console.log('🔐 Redirecting to login page');
      window.location.href = '/login';
    } catch (err) {
      console.error("🔐 Logout failed:", err);
      window.location.href = '/login';
    }
  };

My sso and username pw + mfa flow (which could be getting in the way) :

useEffect(() => {

    // Check for SSO token in URL first
    const params = new URLSearchParams(window.location.search);
    const token = params.get("token");
    const isSSO = params.get("stytch_token_type") === "oauth";
    const error = params.get("error");

    // Handle OAuth errors
    if (error) {
      console.error("🔄 SSO Flow - OAuth error:", error);
      message.error("SSO login failed. Please try again or use password login.");
      
      // Clear URL parameters
      window.history.replaceState({}, document.title, "/login");
      setLoading(false);
      return;
    }

    if (token && isSSO && !isAuthenticating) {
      console.log("🔄 SSO Flow - Starting SSO authentication...", { currentUrl: window.location.href });
      setIsAuthenticating(true);
      setLoading(true);

      const handleSSOCallback = async () => {
        try {
          const result = await stytchClient.oauth.authenticate({
            oauth_token: token,
            session_duration_minutes: 60,
          });

          // Log state, storage, and cookies AFTER successful SSO login
          console.log('=== AFTER LOGIN ===');
          console.log('Auth state:', {
            isAuthenticated: true,
            memberId: result.member?.member_id,
            intermediateSessionToken: result.intermediate_session_token
          });
          console.log('localStorage:', {...localStorage});
          console.log('sessionStorage:', {...sessionStorage});
          console.log('document.cookie:', document.cookie);

          // Store intermediate session token in localStorage
          if (result.intermediate_session_token) {
            localStorage.setItem('intermediate_session_token', result.intermediate_session_token);
          }

          // Clear the URL parameters before setting auth state
          window.history.replaceState({}, document.title, "/");
          console.log("🔄 SSO Flow - URL parameters cleared", { newUrl: window.location.href });

          // Set auth state
          setIsAuthenticated(true);
          setAuthMemberId(result.member?.member_id || null);
          setIntermediateSessionToken(result.intermediate_session_token || null);

          console.log("🔄 SSO Flow - Auth state updated, navigating to home", { currentUrl: window.location.href });
          navigate("/", { replace: true });

        } catch (err) {
          console.error("🔄 SSO Flow - Authentication failed:", { 
            error: err,
            currentUrl: window.location.href 
          });
          setIsAuthenticated(false);
          // Clear all URL parameters on error
          window.history.replaceState({}, document.title, "/login");
          navigate("/login", { replace: true });
        } finally {
          setLoading(false);
          setIsAuthenticating(false);
        }
      };

      handleSSOCallback();
    } else if (!token && !isAuthenticated) {
      console.log("🔄 SSO Flow - No SSO token and not authenticated, showing login form", { 
        currentUrl: window.location.href,
        searchParams: Object.fromEntries(params.entries())
      });
      // Clear any leftover URL parameters
      if (window.location.search) {
        window.history.replaceState({}, document.title, "/login");
      }
      setLoading(false);
    }
  }, [navigate, setIsAuthenticated, setAuthMemberId, setIntermediateSessionToken, stytchClient.oauth, isAuthenticating, isAuthenticated]);

  const handlePasswordLogin = async () => {
    try {
      console.log("🔑 Password Login - Starting...", { currentUrl: window.location.href });
      // Clear any SSO session marker since this is password login
      localStorage.removeItem('sso_session');

      const result = await stytchClient.passwords.authenticate({
        organization_id: organizationId,
        email_address: email,
        password,
        session_duration_minutes: 60,
      });

      const memberId = result.member?.member_id;
      const isMfaEnrolled = result.member?.mfa_enrolled;
      const intermediateSessionToken = result.intermediate_session_token;
      const mfaRequired = result.mfa_required;

      console.log("🔑 Password Login - Auth result:", {
        isMfaEnrolled,
        mfaRequired,
        hasIntermediateToken: !!intermediateSessionToken
      });

      // Store intermediate session token in localStorage
      if (intermediateSessionToken) {
        localStorage.setItem('intermediate_session_token', intermediateSessionToken);
      }

      setIntermediateSession(intermediateSessionToken || '');
      setMemberId(memberId || '');
      setAuthMemberId(memberId || '');
      setIntermediateSessionToken(intermediateSessionToken || '');

      if (isMfaEnrolled) {
        console.log("🔑 Password Login - MFA verification required", { currentUrl: window.location.href });
        setShowMFA(true);
      } else if (mfaRequired) {
        console.log("🔑 Password Login - MFA setup required", { currentUrl: window.location.href });
        navigate("/totp-setup", {
          state: {
            memberId,
            intermediateSessionToken,
          },
        });
      } else {
        console.log("🔑 Password Login - No MFA required, proceeding to home", { currentUrl: window.location.href });
        setIsAuthenticated(true);
        navigate("/");
      }
    } catch (error) {
      console.error("🔑 Password Login - Failed:", { 
        error,
        currentUrl: window.location.href 
      });
      message.error("Failed to login. Please try again.");
    }
  };

Hey Irena!

One quick note at the top for posterity - based on the code snippets you provided, it looks like you’re utilizing our Microsoft OAuth flow in B2B (which is actually slightly different than our SSO flows, but definitely similar).

When i do login with microsoft, even though all the cookies and session is cleared the slug and token are getting attached to the url

The behavior you’re describing is actually unrelated to the Stytch Session and cookies; this behavior is controlled by the Identity Provider (Microsoft in this case) side of things. With Microsoft, if you’re already logged into a single Microsoft account on your browser, the OAuth flow will by default not incur any user interaction, and immediately redirect back to Stytch with a token.

Each Identity Provider has a different way of handling this and offering ways to change this flow.

Microsoft does this via a prompt parameter, and has a few different options depending on the behavior you’d like. The relevant Microsoft documentation can be found here: Microsoft identity platform and OAuth 2.0 authorization code flow - Microsoft identity platform | Microsoft Learn (in particular, search for prompt on that page).

You can leverage IdP-specific query parameters, like prompt, in Stytch via the provider_parameter query parameter in the OAuth Start call. For instance, if you’d like to force account selection, the query parameter would be

&provider_prompt=select_account

, which you would add to the OAuth Start call.

I hope this helps; more than happy to answer any additional questions you might have about this as well!

1 Like