npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

with-refresh-token

v0.2.5

Published

A wrapper middleware for Next.js to handle refresh token

Downloads

36

Readme

With Refresh Token

With Refresh Token is an wrapper middleware for Next.js that helps you to use refresh token to get new access token when the access token is expired.

Usage

Install the package with your package manager of choice.

npm install with-refresh-token

Then import the function to get the middleware and configure it with your own options.

// src/lib/with-refresh-token.ts
import { getMiddleware } from "with-refresh-token";

export const withRefreshToken = getMiddleware({
  /* options */
});

Finally, import withRefreshToken and use it in your middleware stack.

// src/middleware.ts
import { withRefreshToken } from "./lib/with-refresh-token";

export const config = { /* config */ }; // see at the end the suggested `config`

function middleware(
  _req: NextRequest,
  res: NextResponse,
  _event: NextFetchEvent
) {
  // do other stuff here

  return res;
}

export default withRefreshToken(middleware);

// or just
// export default withRefreshToken();

Options

The getMiddleware function takes an object with the following properties:

  • shouldRefresh: (req: NextRequest) => boolean - A function that returns true if the access token should be refreshed.
  • fetchTokenPair: (req: NextRequest) => Promise<TokenPair> - A function that fetches new token pair.
  • onSuccess: (res: NextResponse, tokenPair: TokenPair) => void - A function that is called when the new token pair is fetched successfully.
  • onError?: (req: NextRequest, res: NextResponse, error: unknown) => NextResponse | void - An optional function that is called when an error occurs.

TokenPair

  • TokenPair is an object with the following properties:
    • accessToken: string - The new access token.
    • refreshToken: string - The new refresh token.

Default example

import { NextResponse } from "next/server";
import { getMiddleware } from "with-refresh-token";

const DEFAULT_OFFSET_SECONDS = 15; // refresh the token 15 seconds before it expires

export const withRefreshToken = getMiddleware({
  // get the jwt access token from the cookies through the original request
  // and check if it is expired
  shouldRefresh: (req) => {
    const accessToken = req.cookies.get("access-token")?.value;
    if (!accessToken) return true; // user is not logged in but may have a refresh token
    try {
      const exp = jwtDecode(accessToken).exp; // decode the jwt and get the expiration time
      if (!exp) return false; // token does not have an expiration time
      return exp - DEFAULT_OFFSET_SECONDS <= Date.now() / 1000; // check if the token is expired
    } catch {
      return true; // invalid token but a refresh token may be available
    }
  },
  fetchTokenPair: async (req) => {
    // if true is returned from shouldRefresh, this function will be called
    // do whatever you need to get the new token pair
    const refreshToken = req.cookies.get("refresh-token")?.value;
    if (!refreshToken) {
      // this error is caught by the onError function or ignored if not provided, following
      // the middleware flow normally
      throw new Error("Refresh token not found");
    }
    const response = await fetch("http://localhost:3000/api/refresh-token", {
      method: "POST",
      body: JSON.stringify({ refreshToken }),
      headers: { "Content-Type": "application/json" },
    });
    return await response.json(); // `TokenPair` object should be returned
  },
  onSuccess: (res, tokenPair) => {
    // with the new token pair, set the new access token and refresh token to the cookies
    // so that the middleware and Server Components can use the new token to make auth requests
    res.cookies.set({
      name: "access-token",
      value: tokenPair.accessToken,
      maxAge: 60 * 60 * 24 * 7, // 7 days
      path: "/",
    });
    res.cookies.set({
      name: "refresh-token",
      value: tokenPair.refreshToken,
      maxAge: 60 * 60 * 24 * 7, // 7 days
      path: "/",
    });
  },
  onError: (_req, _res, error) => {
    // optional function that is called when an error occurs during the refresh token process
    // you can clear the cookies and redirect to the login page if is unauthorized
    // if (error instanceof Response && error.status === 401) {
    //   res.cookies.set({ name: "access-token", value: "", maxAge: 0, path: "/" });
    //   res.cookies.set({ name: "refresh-token", value: "", maxAge: 0, path: "/" });
    //   return NextResponse.redirect(new URL("/login", req.url));
    // }
    console.error(error);
  },
});

Suggested config for middleware.ts

This configuration suggestion is available in the Next.js documentation itself. It will prioritize the execution of the middleware on all accessed pages. Adapt it according to your middleware needs.

// ...

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    {
      source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
      missing: [
        { type: "header", key: "next-router-prefetch" },
        { type: "header", key: "purpose", value: "prefetch" },
      ],
    },
    {
      source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
      has: [
        { type: "header", key: "next-router-prefetch" },
        { type: "header", key: "purpose", value: "prefetch" },
      ],
    },
    {
      source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
      has: [{ type: "header", key: "x-present" }],
      missing: [{ type: "header", key: "x-missing", value: "prefetch" }],
    },
  ],
};

// ...

Philosophy

The with-refresh-token middleware is designed to be as flexible as possible. It provides a way to refresh the access token when it is expired, but it does not enforce any specific way to do so. You can use any method to get the new token pair, as long as it returns a TokenPair object.