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,getUserwill returnnull.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
useSsohook 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
optionalAuthorrequiredAuthshould be used in this case, and usegetUser()fromsso.server.ts. - Remember that with
optionalAuth, theusermight benull, so ensure you account for that in the loader. - Take a look into the
SSO package documentation
for more details about
getUser()andfetchUserInfo().
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.
- Replace
optionalAuthwithrequireAuthin your loader. - Logout (or clear your cookies).
- Access the page at http://localhost:3000/remix-workshop/welcome.
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! 🙌