Search code examples
reactjscookiesserver-side-renderingauth0

How to load Auth0 with an initial user from cookies using server side react


The challenge: Preload Auth0Provider with a user from cookies.

The reason: I would love it if my server side rendered react app would fully render server side - unfortunately auth0-react doesn't appear to expose a method to set the user that I am aware of.

I am aware that I could simply set the user or its token in the cookies and preload things that way, only in this case useAuth0 hook won't have access to the user so I will need to implement more custom logic on top of it. Also withAuthenticationRequired function won't know about the user so it just makes more sense to set the user at the initial context level.

From what I have found googling around there are ways to do this using next.js but the app is simply built with react and rendered from an express server so switching to next isn't an option right now.

I've tried implementing Auth0Provider with a custom context where I set the user from cookies but it just doesn't seem to recognize it as a user.

I've tried using a custom cache and implementing the iCache interface using cookies-universal so it can retrieve the data both server side and client side. Not sure if this is just a lost cause.


Solution

  • Solved!

    The answer is to wrap the children of the Auth0Provider in withAuth0 and set the user here before rendering the rest of the app.

    If there is a user avaiable in the client rendered app, the Persistor component will set it into cookies. When rendering server-side: PreloadedAppUserState will check cookies for this user and add it to auth0.

    Also there must be available a signout page which the user is redirected to after completing the signout flow that removes the cookie. I just simply added this new route and then the page just has a useEffect which removes the cookie and navigates back to /. (I did not add this code to the snippets below since its pretty self-explanitory)

    Here's the code:

    import { createContext, useContext } from "react";
    import Cookie from "cookie-universal";
    const set = (k, v, opts, req, res) => {
        const cookie = Cookie(req, res);
        cookie.set(k, v, opts);
        return cookie;
    };
    
    const get = (k, opts, req, res) => {
        const cookie = Cookie(req, res);
        return cookie.get(k, opts);
    };
    const remove = (k, opts, req, res) => {
        const cookie = Cookie(req, res);
        cookie.remove(k, opts);
        return cookie;
    };
    
    const allKeys = (opts, req, res) => {
        const cookie = Cookie(req, res);
        return cookie.getAll(opts);
    };
    
    const Context = createContext({
        set,
        get,
        remove,
        allKeys: () => [],
    });
    export const useCookies = () => useContext(Context);
    export default function CookiesContext({ children, req, res }) {
        return (
            <Context.Provider
                value={{
                    set: (k, v, opts) => set(k, v, req, res),
                    get: (k, opts) => get(k, opts, req, res),
                    remove: (k, opts) => remove(k, opts, req, res),
                    allKeys: opts => allKeys(opts, req, res),
                }}
            >
                {children}
            </Context.Provider>
        );
    }

    import { useCookies } from "../Cookies";
    import { Auth0Provider, withAuth0, useAuth0 } from "@auth0/auth0-react";
    import { useEffect } from "react";
    import { useIsClient } from "usehooks-ts";
    const PreloadedAppUserState = withAuth0(({ auth0, children }) => {
        const cookies = useCookies();
        const initialUser = cookies.get("user");
    
        const isClient = useIsClient();
        if (isClient) return children;
        auth0.user = initialUser;
        auth0.isAuthenticated = !!initialUser;
    
        return <>{children}</>;
    });
    
    const Persistor = ({ children }) => {
        const { user } = useAuth0();
        const cookies = useCookies();
        useEffect(() => {
            if (!user) return;
            cookies.set("user", JSON.stringify(user));
        }, [user]);
        return <PreloadedAppUserState>{children}</PreloadedAppUserState>;
    };
    
    export default function AuthContext({ children }) {
        return (
            <Auth0Provider
                domain={process.env.REACT_APP_AUTH0_DOMAIN}
                clientId={process.env.REACT_APP_AUTH0_CLIENTID}
                authorizationParams={{
                    redirect_uri: process.env.REACT_APP_AUTH0_CALLBACK_URL,
                }}
            >
                <Persistor>{children}</Persistor>
            </Auth0Provider>
        );
    }