vvo/iron-session

middleware error 'The edge runtime does not support Node.js 'crypto' module.'

Clumsy-Coder opened this issue · 8 comments

I'm trying to use middleware for protecting routes, but when running the NextJS, it returns an error

image

NOTE: the middleware is not being used as a edge function. ONLY as a checkpoint for protected routes


packages

  • next: ^12.3.0
  • iron-session: ^6.2.1
  • react: ^18.2.0

file structure

.
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public/
├── src
│   ├── components/
│   ├── lib/
│   │   └── AuthSession
│   │       └── index.ts             # contains helper functions for iron-session. obtained from iron-session README
│   ├── middleware.ts                # middleware used for protecting routes
│   ├── pages
│   │   ├── 404/
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   ├── api/
│   │   │   ├── auth/
│   │   │   │   ├── login.ts          # create session using iron-session if the user is authenticated
│   │   │   │   ├── logout.ts         # destroy iron-session session
│   │   │   │   ├── session.ts        # return iron-session if authenticated, else return empty strings
│   │   │   │   └── unauthorized.ts   # return message the request url is unauthorized
│   │   │   └── summary.ts            # get summary of a system. REQUIRES authentication
│   │   └── index.tsx
│   └── utils/
├── tsconfig.json
└── tsconfig.tsbuildinfo

21 directories, 43 files

files

src/lib/AuthSession/index.ts

/* eslint-disable @typescript-eslint/return-await */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-explicit-any */
// this file is a wrapper with defaults to be used in both API routes and `getServerSideProps` functions
import type { IronSessionOptions } from 'iron-session';
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next';
import {
  GetServerSidePropsContext,
  GetServerSidePropsResult,
  NextApiHandler,
  NextApiRequest,
} from 'next';

/**
 * Iron session data format to be used
 */
export interface IAuthSession {
  ipAddress: string;
  port: string;
  password: string;
}

const ironSessionTTL = 30 * 60;

/**
 * Iron session configs
 */
export const sessionOptions: IronSessionOptions = {
  // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
  password: process.env.SECRET_COOKIE_PASSWORD as string,
  cookieName: 'iron-session/pihole/auth',
  ttl: ironSessionTTL,
  // https://github.com/vvo/iron-session#ironoptions
  cookieOptions: {
    // secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
    secure: process.env.NODE_ENV === 'production',
    // https://github.com/vvo/iron-session#session-cookies
    // maxAge: undefined // session expires when closing window/tab.
  },
};

// This is where we specify the typings of req.session.*
declare module 'iron-session' {
  interface IronSessionData {
    authSession: IAuthSession;
  }
}

export function withSessionRoute(handler: NextApiHandler) {
  return withIronSessionApiRoute(handler, sessionOptions);
}

export function withSessionSsr<P extends Record<string, unknown> = Record<string, unknown>>(
  handler: (
    context: GetServerSidePropsContext,
  ) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>,
) {
  return withIronSessionSsr(handler, sessionOptions);
}

src/middleware.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getIronSession } from 'iron-session';

import { sessionOptions } from '@lib/AuthSession';

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const { authSession } = await getIronSession(req, res, sessionOptions);

  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
  if (authSession === undefined) {
    return NextResponse.redirect(new URL('/api/auth/unauthorized', req.url));
  }
  return res;
}

export const config = {
  matcher: ['/api/summary'],
};

question

Is there a way to use NextJS middleware to protect routes using iron-session

That error is because you are using non-edge version inside your middleware. Refer #537 for discussion.

Changed the code to use import iron-session/edge. I'm still getting the same issue

image

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getIronSession } from 'iron-session/edge';

import { sessionOptions } from '@lib/AuthSession';

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const { authSession } = await getIronSession(req, res, sessionOptions);

  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
  if (authSession === undefined) {
    return NextResponse.redirect(new URL('/api/auth/unauthorized', req.url));
  }
  return res;
}

export const config = {
  matcher: ['/api/summary'],
};

Use a separate lib/AuthSession for edge. You cannot import non edge version, not even transitively.

Use a separate lib/AuthSession for edge. You cannot import non edge version, not even transitively.

?
Did you mean create a duplicate sessionOptions in middleware.ts? Is there a reason behind it?


Something like

src/middleware.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getIronSession, IronSessionOptions } from 'iron-session/edge';

export const sessionOptions: IronSessionOptions = {
  // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
  password: process.env.SECRET_COOKIE_PASSWORD as string,
  cookieName: 'iron-session/pihole/auth',
  ttl: 30 * 60,
  // https://github.com/vvo/iron-session#ironoptions
  cookieOptions: {
    // secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
    secure: process.env.NODE_ENV === 'production',
    // https://github.com/vvo/iron-session#session-cookies
    // maxAge: undefined // session expires when closing window/tab.
  },
};

export const middleware = async (req: NextRequest) => {
  const res = NextResponse.next();
  const { authSession } = await getIronSession(req, res, sessionOptions);

  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
  if (authSession === undefined) {
    return NextResponse.redirect(new URL('/api/auth/unauthorized', req.url));
  }
  return res;
};

export const config = {
  matcher: ['/api/summary'],
};

@Clumsy-Coder I got this fixed by putting sessionOptions on other files.
Not sure why that happen but it work..

Can you give me an example? Thanks.

Aralhi commented

hi, how did you fixed this issue? cloud you share it? thanks

Aralhi commented

hi, how did you fixed this issue? cloud you share it? thanks

And my sessionOptions define in /lib/session.ts, and when i use getIronSession in a edge runtime api (/api/chat/get) meet the same error.