Gnusson
Gnusson

Reputation: 355

Passing variables from middleware to page in Next.js 12 new middleware api

Background to the Question

Vercel recently released their biggest update ever to Next.js. Next.js blog. They introduced a lot of new features but my favorite is Middleware which:

"enables you to use code over configuration. This gives you full flexibility in Next.js because you can run code before a request is completed. Based on the user's incoming request, you can modify the response by rewriting, redirecting, adding headers, or even streaming HTML."

The Question

The following structure is used in this question.

- /pages
    index.js
    signin.js
    - /app
      _middleware.js # Will run before everything inside /app folder
      index.js

The two important files here are /app/_middleware.js and /app/index.js.

// /app/_middleware.js

import { NextResponse } from 'next/server';

export function middleware(req, event) {
  const res = { isSignedIn: true, session: { firstName: 'something', lastName: 'else' } }; // This "simulates" a response from an auth provider
  if (res.isSignedIn) {

    // Continue to /app/index.js
    return NextResponse.next();
  } else {

    // Redirect user
    return NextResponse.redirect('/signin');
  }
}
// /app/index.js

export default function Home() {
  return (
    <div>
      <h1>Authenticated!</h1>
      
      // session.firstName needs to be passed to this file from middleware
      <p>Hello, { session.firstName }</p>
    </div>
  );
}

In this example /app/index.js needs access to the res.session JSON data. Is it possible to pass it in the NextResponse.next() function or do you need to do something else?

In express you can do res.locals.session = res.session

Upvotes: 23

Views: 28408

Answers (4)

user10475870
user10475870

Reputation: 131

There's a another way but just like using cookie to achieve this. Just pass you data through headers.

// middleware.ts

async function middleware(request: NextRequest) {
  const response = NextResponse.next();
  response.headers.set('X-HEADER', 'some-value-to-pass');
  return response;
}
// _app.ts

function MyApp({ data }) {
  // you can access your data here
  <div>{data}</div>
}

MyApp.getInitialProps = ({ ctx }) => {
  const data = ctx.res.getHeader('X-HEADER');
  ctx.res.removeHeader('X-HEADER');
  return { data };
};

Upvotes: 3

Colin Sidoti
Colin Sidoti

Reputation: 41

We found a solution for 12.2+ middleware - published here:

https://clerk.dev/blog/nextjs-pass-value-from-middleware-to-api-routes-and-getserversideprops

And copying here for posterity...

Usage: middleware.js

import { NextResponse } from "next/server";
import { withContext } from "./context";

// Pre-define the possible context keys to prevent spoofing
const allowedContextKeys = ["foo"];

export default withContext(allowedContextKeys, (setContext, req) => {
  setContext("foo", "bar");
  return NextResponse.next();
});

Usage: API route (Node)

import { getContext } from "../../context";

export default function handler(req, res) {
  res.status(200).json({ foo: getContext(req, "foo") });
}

Usage: API route (Edge)

import { getContext } from "../../context";

export default function handler(req) {
  return new Response(JSON.stringify({ foo: getContext(req, "foo") }));
}

Usage: getServerSideProps (Edge and Node)

import { getContext } from "../context";

export const getServerSideProps = ({ req }) => {
  return { props: { foo: getContext(req, "foo") } };
};

Source: (saved to context.js on your root)

import { NextResponse } from "next/server";

const ctxKey = (key) => `ctx-${key.toLowerCase()}`;

export const getContext = (req, rawKey) => {
  const key = ctxKey(rawKey);

  let headerValue =
    typeof req.headers.get === "function"
      ? req.headers.get(key) // Edge
      : req.headers[key]; // Node;

  // Necessary for node in development environment
  if (!headerValue) {
    headerValue = req.socket?._httpMessage?.getHeader(key);
  }

  if (headerValue) {
    return headerValue;
  }

  // Use a dummy url because some environments only return
  // a path, not the full url
  const reqURL = new URL(req.url, "http://dummy.url");

  return reqURL.searchParams.get(key);
};

export const withContext = (allowedKeys, middleware) => {
  // Normalize allowed keys
  for (let i = 0; i < allowedKeys.length; i++) {
    if (typeof allowedKeys[i] !== "string") {
      throw new Error("All keys must be strings");
    }
    allowedKeys[i] = ctxKey(allowedKeys[i]);
  }

  return (req, evt) => {
    const reqURL = new URL(req.url);

    // First, make sure allowedKeys aren't being spoofed.
    // Reliably overriding spoofed keys is a tricky problem and
    // different hosts may behave different behavior - it's best
    // just to safelist "allowedKeys" and block if they're being
    // spoofed
    for (const allowedKey of allowedKeys) {
      if (req.headers.get(allowedKey) || reqURL.searchParams.get(allowedKey)) {
        throw new Error(
          `Key ${allowedKey.substring(
            4
          )} is being spoofed. Blocking this request.`
        );
      }
    }

    const data = {};

    const setContext = (rawKey, value) => {
      const key = ctxKey(rawKey);
      if (!allowedKeys.includes(key)) {
        throw new Error(
          `Key ${rawKey} is not allowed. Add it to withContext's first argument.`
        );
      }
      if (typeof value !== "string") {
        throw new Error(
          `Value for ${rawKey} must be a string, received ${typeof value}`
        );
      }
      data[key] = value;
    };

    let res = middleware(setContext, req, evt) || NextResponse.next();

    // setContext wasn't called, passthrough
    if (Object.keys(data).length === 0) {
      return res;
    }

    // Don't modify redirects
    if (res.headers.get("Location")) {
      return res;
    }

    const rewriteURL = new URL(
      res.headers.get("x-middleware-rewrite") || req.url
    );

    // Don't modify cross-origin rewrites
    if (reqURL.origin !== rewriteURL.origin) {
      return res;
    }

    // Set context directly on the res object (headers)
    // and on the rewrite url (query string)
    for (const key in data) {
      res.headers.set(key, data[key]);
      rewriteURL.searchParams.set(key, data[key]);
    }

    // set the updated rewrite url
    res.headers.set("x-middleware-rewrite", rewriteURL.href);

    return res;
  };
};

Upvotes: 4

Korayem
Korayem

Reputation: 12507

Only weird solution is to inject your custom object into req.body because next.js v12 middleware doesn't allow altering the NextApiRequest

export const middleware = async (req: NextApiRequest) => {
  //   return new Response("Hello, world!");
  req.body = { ...req.body, foo: "bar" };
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  await middleware(req);
  // now req.body.foo=='bar'
}

They do however explain how you can extend middleware here, but the example given (copied below) isn't meaningful enough because it doesnt show how withFoo() is implemented

import { NextApiRequest, NextApiResponse } from 'next'
import { withFoo } from 'external-lib-foo'

type NextApiRequestWithFoo = NextApiRequest & {
  foo: (bar: string) => void
}

const handler = (req: NextApiRequestWithFoo, res: NextApiResponse) => {
  req.foo('bar') // we can now use `req.foo` without type errors
  res.end('ok')
}

export default withFoo(handler)

I assumed based on the above, withFoo.ts should be like this. But still wasn't successful in accessing request.Foo()

import { NextApiHandler, NextApiRequest } from "next";


export const withFoo = (handler: NextApiHandler) => {
  //do stuff

};

Maybe someone can chip in?

Upvotes: 1

Ben
Ben

Reputation: 5646

According to the examples (look specifically at /pages/_middleware.ts and /lib/auth.ts) it looks like the canonical way to do this would be to set your authentication via a cookie.

In your middleware function, that would look like:

// /app/_middleware.js

import { NextResponse } from 'next/server';

export function middleware(req, event) {
  const res = { isSignedIn: true, session: { firstName: 'something', lastName: 'else' } }; // This "simulates" a response from an auth provider
  if (res.isSignedIn) {

    // Continue to /app/index.js
    return NextResponse.next().cookie("cookie_key", "cookie_value"); // <--- SET COOKIE
  } else {

    // Redirect user
    return NextResponse.redirect('/signin');
  }
}

Upvotes: 2

Related Questions