Skip to content

Use SSO

We’re nearing the end of the workshop, but one essential piece of the Statista platform is still missing: user authentication. Handling logged-in and logged-out users, requiring login for specific actions, or toggling UI elements based on a user’s login state or account type is simpler than it sounds.

This functionality is provided out of the box by the @cpe-orga/remix-sso package, which is pre-installed with the Remix Stack. Additionally, the setup is located in /app/services/sso.server.ts.

And here’s the best part: following the "every API is mocked" idea, even SSO is mocked in your local environment. This ensures a seamless developer experience right from the start.

For full documentation, visit the GitHub repository.

Key Functions and Their Uses

useSso

The useSso hook is used in React components to access user login state and information during rendering.

const { isLoggedIn, user } = useSso();
const buttonText = isLoggedIn ? "Logout" : "Login";

return <Button>{buttonText}</Button>;

optionalAuth, requiredAuth, and getUser

These server-side utilities handle authentication in loaders or actions:

  • optionalAuth: Allows the route to function for both logged-in and logged-out users. If a user is logged out, getUser will return null.
  • requiredAuth: Enforces login. If a user is not logged in, they are redirected to the Statista login page before proceeding.
  • getUser: Retrieves user information if available.

Example optionalAuth

export const loader = ({ request }: LoaderFunctionArgs) => {
  return optionalAuth(request, async () => {
    const user = getUser();
    return json({ name: user?.name });
  });
};

Example requiredAuth

export const loader = ({ request }: LoaderFunctionArgs) => {
  return requiredAuth(request, async () => {
    const user = getUser();
    return json({ name: user.name });
  });
};

Key Difference

Notice the ? in the optionalAuth example (user?.name). This is called optional chaining and means the user object can be null. With requiredAuth, the user object is guaranteed to exist.

Practical Example: Starter Account Purchase

The Account Overview (AO) for the Starter Account journey demonstrates requiredAuth in action. This flow checks whether a user is eligible to purchase the Starter Account based on their current product ID. If a logged-out user clicks "Buy Now," they are redirected to the login page. After successful login, the user is returned to the AO and proceeds with the purchase journey.

Note: requiredAuth should only be used in loaders, not actions. Redirects cannot occur from actions (e.g. POST requests).

Note: On mock environments, login happens seamlessly without a login screen. Don't be confused when this occurs during local development.

📝 Task: Change the "Sign Up" button depending on the user's login state

Let's revisit the button we built in part 3 of the workshop with Atlas. This button currently says "Sign up."

The Goal: Change this text to "Upgrade" for logged-in users. Why? A registered user doesn't need to sign up again!

  • Use the useSso hook to check the user's login state.
  • Dynamically toggle the button's text between "Sign up" and "Upgrade" depending on whether the user is logged in.

When you're done, click "Login" in the top-right corner to see the changes in action. To logout, either press "Logout" or clear your cookies.

Solution
export default function Welcome() {
  const { isLoggedIn } = useSso();
  const buttonCopy = isLoggedIn ? "Upgrade" : "Sign up";

  return (
    // ...
    <div className="mb-8 flex gap-2">
      <Link variant="buttonPrimary" className="justify-center" to="/">
        {buttonCopy}
      </Link>
      <Link variant="buttonOutline" className="justify-center" to="/">
        Book a demo
      </Link>
    </div>
    // ...
  );
}

📝 Task: Fetch additional user information data and create a more personal headline

The current headline from part 3 of the workshop reads: "Drive efficient business growth with trusted market and consumer data."

The Goal: Let’s make this message more personalized by including the user’s given name. For example: "Hey Max, drive efficient business growth..."

Since useSso only provides displayName (a combination of the given and family name), you’ll need to fetch the user’s givenName. This can be done using fetchUserInfo() from the user object, you get with getUser().

  • Create a loader to fetch the user’s givenName.
  • Pass this information to the component.
  • Enhance the headline and handle both logged-in and logged-out cases in your logic.

Some hints:

  • Decide whether optionalAuth or requiredAuth should be used in this case, and use getUser() from sso.server.ts.
  • Remember that with optionalAuth, the user might be null, so ensure you account for that in the loader.
  • Take a look into the SSO package documentation for more details about getUser() and fetchUserInfo().

Note: Make sure you're going to import optionalAuth and getUser from sso.server.js, not the SSO package directly.

Solution

Let's assume both logged-out and logged-in users should be able to access the page. In this scenario, we use optionalAuth, as requireAuth would force users to log in, which is not what we want. Since optionalAuth allows unauthenticated access, the user object can be null. This means the loader must handle the case where no user is logged in.

Example loader code:

export const loader = ({ request }: LoaderFunctionArgs) => {
  return optionalAuth(request, async () => {
    const user = getUser();
    let givenName;

    if (user) {
      const userInfo = await user.fetchUserInfo(request);
      givenName = userInfo?.user?.name?.givenName;
    }

    return json({ givenName });
  });
};

Example component code:

export default function Welcome() {
  const { givenName } = useLoaderData<typeof loader>();
  const { isLoggedIn } = useSso();

  const buttonCopy = isLoggedIn ? "Upgrade" : "Sign up";

  return (
    // ...
    <Headline renderAs="h2" size="lg" className="mb-2 break-normal">
      <strong className="font-bold">
        {givenName
          ? `Hey ${givenName}, drive efficient business growth`
          : "Drive efficient business growth"}
      </strong>{" "}
      with trusted market and consumer data
    </Headline>
    // ...
  );
}

Note: If you achieve the goal but how you handle it looks different, that is totally fine.

📝 Task: Require the user to be logged-in

To understand how requireAuth behaves compared to optionalAuth, let’s update the loader to use requireAuth instead.

What happens when you try this?

Solution

You don’t need to change anything else in the code, just swap optionalAuth with requireAuth.

Example loader code:

export const loader = ({ request }: LoaderFunctionArgs) => {
  return requireAuth(request, async () => {
    const user = getUser();
    let givenName;

    if (user) {
      const userInfo = await user.fetchUserInfo(request);
      givenName = userInfo?.user?.name?.givenName;
    }

    return json({ givenName });
  });
};

After testing, make sure to revert back to optionalAuth to keep the page accessible for both logged-in and logged-out users.


Congrats! 🎉 You’ve taken another big step in handling users based on their login state and integrating user data. If you encountered any challenges or have questions about these tasks, feel free to share. Great job! 🙌