Next.js Discord

Discord Forum

Paywall

Unanswered
Yellow-bellied Flycatcher posted this in #help-forum
Open in Discord
Yellow-bellied FlycatcherOP
Hi there,
I'm trying to implement a paywall with Next.JS (Frontend) and Spring Boot (Backend). Currently, I'm fetching my data like this:
export const getServerSideProps = async ({ query }) => {
    try {
        const res = await api.get(`/v1/epochs/${query.name}`);
        const data = await res.json();

        const allRes = await api.get('/v1/epochs');
        const allData = await allRes.json();

        return {
            props: { json: data, epochs: allData },
        };
    } catch (error) {
        return {
            props: { json: {}, epochs: {} },
        };
    }
};

For rendering the paywall, I'm checking if the user is logged in (const { isLoggedIn } = useUser();). This works fine. However, I want to shorten the request data in getServerSideProps if the user is not logged in (like newspaper paywalls where the text is cut).

Now I'm challenging how to do this. The getServerSideProps cannot access local storage to authenticate to the backend. But I do not want to load the data with useEffect. Is there any alternative?

70 Replies

@Dutch cookies
Yellow-bellied FlycatcherOP
I currently store the auth token in the local storage. But the cookies aren't accessable from in the getServerSideProps, too?
i dont remember this syntax lol
@Dutch store both in cookies and see
Yellow-bellied FlycatcherOP
what do you mean? both?
@Yellow-bellied Flycatcher what do you mean? both?
Dutch
auth token i mean
Yellow-bellied FlycatcherOP
since the getServerSideProps is executed on the server side, how could the cookies be accessed??
Dutch
cookies are universal
@Dutch cookies are universal
Yellow-bellied FlycatcherOP
could you explain a little bit more? I don't understand how this should be accessed? Isn't it the same as local storage?
Dutch
next/cookies have this for both front and backend
i ll show you a code
@Dutch i ll show you a code
Yellow-bellied FlycatcherOP
thx
so I saw some cases where next middleware is required, so that's not the necessary in my case?
@Dutch its better if you have a lot protected routes
Yellow-bellied FlycatcherOP
ahh ok currently I'm doing it like this: but I just want to render the paywall on pages which are not compeltely protected
function MyApp({ Component, pageProps }) {

    const [user, setUser] = useState(null);
    const router = useRouter();

    useEffect(() => {

        const fetchUserProfile = async () => {
            const res = await api.get('/v1/auth/profile', true);

            if (res.ok) {
                const json = await res.json();
                setUser(json.data);
            } else {
                setUser(null);
                localStorage.removeItem("token");
                router.push("/login");
            }
        }

        if (localStorage.getItem("token") !== null) {
            fetchUserProfile();
        }

     

    }, [router]);

    if (pageProps.protected && !user) {
        return (
            <p>Not logged in</p>
        )
    } else if (pageProps.protected && user && pageProps.roles) {
        const authorized = pageProps.roles.some(element => {
            return user.authorities.some(authority => authority.authority.replaceAll("ROLE_", "") === element);
        });

        if (!authorized) {
            return (
                <p>Access denied</p>
            )
        }
    }

    return (
        <UserContext.Provider value={user}>
            <ThemeProvider>
                <Component {...pageProps} />
            </ThemeProvider>
        </UserContext.Provider>
    )
}

export default MyApp;
@Dutch so you have user types also, its not just member and non-member
Yellow-bellied FlycatcherOP
no its member and non-member for the paywall, the user types are just for internal purposes (admin/staff features)
Dutch
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export const getCustomer = async () => {
  const token = cookies().get("auth_token")?.value;

  if (!token) {
    redirect("/");
  }
  const data = await fetch(`${BASE_URL}/customers/my-account`, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });
you can use that cookies at frontend with useCookies() hook
@Dutch ts import { cookies } from "next/headers"; import { redirect } from "next/navigation"; export const getCustomer = async () => { const token = cookies().get("auth_token")?.value; if (!token) { redirect("/"); } const data = await fetch(`${BASE_URL}/customers/my-account`, { headers: { Authorization: `Bearer ${token}`, }, });
Yellow-bellied FlycatcherOP
my code above already does exactly this, or am I wrong? my question is about a paywall where the compelte text is available for members and only a shortened version for the non-members. for exampel the text is available at [id].js. if I access the page as non-member, I can only see the short text.

I already implemented the logic to check if the user is logged in. If the user is not logged in, I show to short version. But the problem is that my paywall currentlyl is only realized in the rendering and it could be bypassed via dev tools, so I need to authenticate to the backend so that the text is already shortened by the request
it's not about basic authentication and protected sites, it's about authentication on a public page with SSR
@Dutch oh its totally different than what i said
Yellow-bellied FlycatcherOP
haha I was confused
Dutch
you are blocking render from backend
@Dutch you are blocking render from backend
Yellow-bellied FlycatcherOP
how? so this is the request
export const getServerSideProps = async ({ query }) => {
    try {
        const res = await api.get(`/v1/epochs/${query.name}`, false);
        const data = await res.json();

        const allRes = await api.get('/v1/epochs', false);
        const allData = await allRes.json();

        return {
            props: { json: data, epochs: allData },
        };
    } catch (error) {
        return {
            props: { json: {}, epochs: {} },
        };
    }
};

here I need to pass the auth token to get the short text (free version) or the full text (paid version)
@Dutch okay so i am saying get that token from cookies and pass there
Yellow-bellied FlycatcherOP
ahh OK so I just need to store it in the cookies instead of the local storage? why is it not working with the local storage?
@Dutch you cant access it from backend
Yellow-bellied FlycatcherOP
but how can I access the user's browser cookies from backend?
weird but true
@Dutch cuz cookies are universal 😂
Yellow-bellied FlycatcherOP
but do I have to check the cookie in the bakcend or can I pass it in the getServerSideProps as authentication header in the frontend?
it's a JWT token
@Dutch if check means existence, yeah
Yellow-bellied FlycatcherOP
so in the frontend? xD
@Yellow-bellied Flycatcher so in the frontend? xD
Dutch
no just get and use
frontend is not async
import { useCookies } from "next-client-cookies";
export default function Filters({ genres }: { genres: Genre[] }) {
  const cookies = useCookies();
  const query = cookies.get("query") as string;
but first you need to get rid of that next version and folder structure
@Dutch but first you need to get rid of that next version and folder structure
Yellow-bellied FlycatcherOP
what's wrong with the folder structure? I'm using the current next version
@Dutch tsx import { useCookies } from "next-client-cookies"; export default function Filters({ genres }: { genres: Genre[] }) { const cookies = useCookies(); const query = cookies.get("query") as string;
Yellow-bellied FlycatcherOP
import cookies from 'next-cookies';

export const getServerSideProps = async ({ query, req }) => {
    try {
        const { token } = cookies({ req });

        const res = await fetch(`${process.env.API_URL}/v1/epochs/${query.name}`, {
            method: 'GET',
            headers: {
                Authorization: `Bearer ${token}`,
            },
        });
        const data = await res.json();

        const allRes = await fetch(`${process.env.API_URL}/v1/epochs`, {
            method: 'GET',
            headers: {
                Authorization: `Bearer ${token}`, 
            },
        });
        const allData = await allRes.json();

        return {
            props: { json: data, epochs: allData },
        };
    } catch (error) {
        return {
            props: { json: {}, epochs: {} },
        };
    }
};

Like this?
@Dutch if it works yes
Yellow-bellied FlycatcherOP
ok and another improvement would be to move the auth process from the _app.js to middleware?
i dont care personally
@Dutch yea if you care clean architecture
Yellow-bellied FlycatcherOP
but doesn't it make a difference for the user? so the login features are already shown when the page is rendered?
you can check medium
it has paywall at premium blogs, but still you can move around
Yellow-bellied FlycatcherOP
but with my solution it's ont possible to move around?
Dutch
it should, otherwise people become reluctant to visit it even
either you put paywall to first page
@Dutch either you put paywall to first page
Yellow-bellied FlycatcherOP
I switched from local storage to cookies from next-headers but this appears:

⨯ ./src/pages/_app.js
Error: × You're importing a component that needs "next/headers". That only works in a Server Component which is not supported in the pages/ directory.
@Dutch yep thats why i said you need to get rid of pages arch
Yellow-bellied FlycatcherOP
well whats the solution for the page router?
having api routes
@Dutch https://nextjs.org/docs/pages/api-reference/functions/next-request
Yellow-bellied FlycatcherOP
well I tried it with nextrequest but thats also not available
Yellow-bellied FlycatcherOP
but how can i access it in my _app.js then
Dutch
useeffect probably
only way to fetch client data 😢
all the world circling around
Yellow-bellied FlycatcherOP
@Dutch but why do i need to create api routes? what about using an external cookies lib?
I tried it with this in pages/api/auth/login.js but there are tons of errors:
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req) {
    const { state } = await req.json();
    const res = await fetch(`${process.env.ROOT}/v1/auth/login`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(state),
    });

    const json = await res.json();

    if (json.success) {
        const response = NextResponse.json(json);
        response.cookies.set('token', json.data.token, { path: '/' });
        return response;
    } else {
        return NextResponse.json(json, { status: res.status });
    }
}
@Yellow-bellied Flycatcher <@1257609464811487255> but why do i need to create api routes? what about using an external cookies lib?
Dutch
cuz that folder works on server and you can access cookies from there
@Dutch cuz that folder works on server and you can access cookies from there
Yellow-bellied FlycatcherOP
but would it be possible to use something like nookie? dann i can use it in the _app.js?
@Yellow-bellied Flycatcher but would it be possible to use something like nookie? dann i can use it in the _app.js?
Dutch
if it works yes, i dont remember pages dir syntax