Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for next js 13 server side components #560

Closed
ajsharp opened this issue Nov 8, 2022 · 18 comments · Fixed by #574
Closed

Support for next js 13 server side components #560

ajsharp opened this issue Nov 8, 2022 · 18 comments · Fixed by #574

Comments

@ajsharp
Copy link

ajsharp commented Nov 8, 2022

The old getServerSideProps api is deprecated, and you no longer have access to the request and response objects. Instead you get access to headers and cookies.

It would be nice to have a new helper function like withIronSessionSsr for accessing the session in server side components.

@AlanMorel
Copy link

This is blocking my Next.js 13 upgrade path, would love to see a resolution for this, cheers!

@maorz555
Copy link

The issue is very urgent and critical for my project!

@poltang
Copy link

poltang commented Nov 23, 2022

I still use iron-session with Next 13 to handle auth/session. My solutions is like this:

// lib/getRequestCookie.ts
import { SessionUser } from "@/api/user";
import { unsealData } from "iron-session";
import { ReadonlyRequestCookies } from "next/dist/server/app-render";

/**
 * Can be called in page/layout server component.
 * @param cookies ReadonlyRequestCookies
 * @returns SessionUser or null
 */
export async function getRequestCookie(
  cookies: ReadonlyRequestCookies
): Promise<SessionUser | null> {
  const cookieName = process.env.SESSION_COOKIE_NAME as string;
  const found = cookies.get(cookieName);

  if (!found) return null;

  const { user } = await unsealData(found.value, {
    password: process.env.SESSION_COOKIE_PASSWORD as string,
  });

  return user as unknown as SessionUser;
}

And then, in protected layout or page (preferably layout), I do:

import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { getRequestCookie } from "@/lib/getRequestCookie";

export default async function TenantLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  console.log("Entering TenantLayout boundary");
  const user = await getRequestCookie(cookies());

  // Prevent non logged user to acces all pages in the tenant layout tree
  if (!user) {
    redirect("/login");
  }

  return (
    <>
      // header component
      {children}
      // footer component
    </>
  );
}

So far it works perfectly well.

@AlanMorel
Copy link

Thank you @poltang! I was able to use iron-session successfully on Next.js 13 using that sample code

@ajsharp
Copy link
Author

ajsharp commented Nov 24, 2022

Thanks for sharing this @poltang. Would be great to see this added to iron-session as a one-liner like withIronSessionSsr

@muslax
Copy link

muslax commented Nov 24, 2022

I agree with you @ajsharp that it

Would be great to see this added to iron-session as a one-liner like withIronSessionSsr

But for me it's a matter of preference. Writing-wise I prefer this new approach since we don't need to write separate ssr logic like this example:

export default function SsrProfile({
  user,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <Layout>
      {/* Page content goes here */}
    </Layout>
  )
}

export const getServerSideProps = withIronSessionSsr(async function ({ req, res }) {
  const user = req.session.user;

  if (user === undefined) {
    res.setHeader("location", "/login");
    res.statusCode = 302;
    res.end();
    return {
      props: {
        user: { isLoggedIn: false, login: "", avatarUrl: "" } as User,
      },
    };
  }

  return {
    props: { user: req.session.user },
  };
},
sessionOptions);

With new approach this logic goes into page body with simpler code and no need to create props:

export default async function Page() {
  const user = await getRequestCookie(cookies());

  if (!user) {
    redirect("/login");
  }

  return (
    <>
      {/* Page content goes here */}
    </>
  );
}

And again, as we now have layout, we can colocate this to it and then IF we have hundreds of pages we will be grateful not to call withIronSessionSsr on every single page. And preference-wise I can change all of my pages to be ssr-ed and only use useUser() on child components deep in page tree.

@brunoreis
Copy link

brunoreis commented Dec 21, 2022

Thanks for this solution @poltang, it was very useful. One thing that was still missing to me was a way to query the api passing the auth cookie. I was not really able to use the withSessionSsr, so I came up with this:

import { cookies } from 'next/headers'
async function getUser() {
    const sessionCookie = cookies().get(
        process.env.SESSION_COOKIE_NAME as string
    )
    if (sessionCookie) {
        const Cookie = `${sessionCookie.name}=${sessionCookie.value}`
        const res = await fetch(`${apiDomainAndProtocol}/api/user`, {
            headers: {
                Cookie,
            },
        })
       ...

Please, do you have another suggestion?

@poltang
Copy link

poltang commented Dec 23, 2022

@brunoreis I'm afraid I don't understand with what you mean by query the api passing the auth cookie. All my protected APIs now share the same shape like this:

async function handler(req: NextApiRequest, res: NextApiResponse) {
  const user = req.session.user;

  if (!user) {
    return res.status(401).json( ERROR_401 );
  }

  // do some stuff
  const result = ...
  return res.json(result);
}

export default withIronSessionApiRoute(handler, sessionOptions)

@R3flector
Copy link

Add next.js-13-typescript example pls

@brunoreis
Copy link

@poltang, I think I'm still a little bit confused in this SSR world logic.

I do have an /user route that I use in frontend to access the user data and I'm hitting that api route from the ssr in order to grab the same data when rendering in the server.

In fact I don't need to grab my data through an api call, right? I'm already in the server.
:-)

Although it feels like a good thing to access data from the server and from the frontend in the exact same way.
Specially because I'm intending to write a graphql schema for data. And also because that makes it possible to transform a backend component in a client component and vice versa with no extra effort.

@remidej
Copy link

remidej commented Apr 5, 2023

I also wish I could create my login, logout and signup routes in the /app directory using the new route handlers. It seems to me that withIronSessionApiRoute only works for endpoints in the pages/api directory

@brc-dd brc-dd closed this as completed Apr 22, 2023
@renchris
Copy link
Contributor

Is Poltang's example still the standard implementation method for using iron-session cookies with the current Next 13 app router? Or has there been anything new to newly use since the app router stable release in May?

@fandyajpo
Copy link

I still use iron-session with Next 13 to handle auth/session. My solutions is like this:

// lib/getRequestCookie.ts
import { SessionUser } from "@/api/user";
import { unsealData } from "iron-session";
import { ReadonlyRequestCookies } from "next/dist/server/app-render";

/**
 * Can be called in page/layout server component.
 * @param cookies ReadonlyRequestCookies
 * @returns SessionUser or null
 */
export async function getRequestCookie(
  cookies: ReadonlyRequestCookies
): Promise<SessionUser | null> {
  const cookieName = process.env.SESSION_COOKIE_NAME as string;
  const found = cookies.get(cookieName);

  if (!found) return null;

  const { user } = await unsealData(found.value, {
    password: process.env.SESSION_COOKIE_PASSWORD as string,
  });

  return user as unknown as SessionUser;
}

And then, in protected layout or page (preferably layout), I do:

import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { getRequestCookie } from "@/lib/getRequestCookie";

export default async function TenantLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  console.log("Entering TenantLayout boundary");
  const user = await getRequestCookie(cookies());

  // Prevent non logged user to acces all pages in the tenant layout tree
  if (!user) {
    redirect("/login");
  }

  return (
    <>
      // header component
      {children}
      // footer component
    </>
  );
}

So far it works perfectly well.

how to set the cookie? poltang

@renchris
Copy link
Contributor

How do we initially set cookies to the header securely with Iron Session? The code examples above are for when the cookie has been already set

@fandyajpo
Copy link

i want something like iron-session that can be use in nextjs 13, i love the way iron-session ecrypt the cookie and easily get the cookie with the actual value, is there any reference?

@erwanvivien
Copy link
Contributor

erwanvivien commented Aug 20, 2023

I'm using this as of recently, which is working quite well

Edit: It's for Route Handlers, it might be adapted for Server Components?

export type DynamicSegments = {
  params: { slug: string } | undefined;
};

export type RouteHandler = (
  request: Request,
  routeSegment: DynamicSegments
) => Promise<Response>;

export type RouteHandlerWithSession = (
  request: Request & { session: IronSession },
  routeSegment: DynamicSegments
) => Promise<Response>;

const ironSessionWrapper = (handler: RouteHandlerWithSession): RouteHandler => {
  return async (request, routeSegment) => {
    const cookieResponse = new Response();
    const session = await getIronSession(
      request,
      cookieResponse,
      sessionOptions
    );

    const sessionRequest = Object.assign(request, { session });
    const response = await handler(sessionRequest, routeSegment);

    const setCookie = cookieResponse.headers.get("set-cookie");
    if (setCookie) {
      response.headers.set("set-cookie", setCookie);
    }

    return response;
  };
};

And using it like this:

const POST: RouteHandlerWithSession = ironSessionWrapper(async (request) => {
  request.session.destroy();
  return NextResponse.json({});
});

@fandyajpo
Copy link

fandyajpo commented Aug 23, 2023

I'm using this as of recently, which is working quite well

Edit: It's for Route Handlers, it might be adapted for Server Components?

export type DynamicSegments = {
  params: { slug: string } | undefined;
};

export type RouteHandler = (
  request: Request,
  routeSegment: DynamicSegments
) => Promise<Response>;

export type RouteHandlerWithSession = (
  request: Request & { session: IronSession },
  routeSegment: DynamicSegments
) => Promise<Response>;

const ironSessionWrapper = (handler: RouteHandlerWithSession): RouteHandler => {
  return async (request, routeSegment) => {
    const cookieResponse = new Response();
    const session = await getIronSession(
      request,
      cookieResponse,
      sessionOptions
    );

    const sessionRequest = Object.assign(request, { session });
    const response = await handler(sessionRequest, routeSegment);

    const setCookie = cookieResponse.headers.get("set-cookie");
    if (setCookie) {
      response.headers.set("set-cookie", setCookie);
    }

    return response;
  };
};

And using it like this:

const POST: RouteHandlerWithSession = ironSessionWrapper(async (request) => {
  request.session.destroy();
  return NextResponse.json({});
});

it work perfectly thanks erwanvivien, but i still waitting till the iron-session maintainer release the official trick or docs
for n13

@renchris
Copy link
Contributor

Hey everyone 👋

I have Iron Session now fully working with support for NextJS App Router and Server Actions. It works without the need for wrappers and handlers.

The feature update is provided with documentation and a working example application. Please refer to my comment under the Roadmap v8 issue thread for it, being available as a pull request.

I would love your support in getting this PR approved so that we can complete the V8 branch to be pushed into the main branch to have Iron Session with NextJS 13 Server Side Components available for everyone to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.