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

next-jwt-auth

v1.2.2

Published

![NextJWTAuth Logo](assets/next-jwt-auth-logo.png)

Downloads

277

Readme

NextJWTAuth Logo

Next JWT Auth

The problem

In many application the frontend and backend are two separate application. Your JWT authentication service may be written in different language/framework and hosted somewhere else. You just want to call the auth service APIs from your NextJS app and manage JWT tokens.

Official NextJS auth library creates authentication API endpoints inside the Next application. That means there is an extra hop between your frontend and auth service.

Moreover, refresh token management is a pain in Next Auth library, there is just too much boilerplate code.

And lastly, Next Auth library is too much for a simple JWT authentication, it provides Social login. Many apps don't require social login. Many apps just want to implement JWT token without pain.

The solution

This library calls your own JWT auth APIs directly from frontend (skipping your NextJS backend hop) and manages JWT tokens in browser cookies. It will store both your access and refresh token and will refresh your access token automatically when the access token is expired.

This library will also automatically logout users when the refresh token also get expired.

Dependecies

You need to use React >=16. NextJS and Axios are the two peer dependencies. You need install next^14.1.4 and axios^1.6.8 first.

npm i next axios

npm i next-jwt-auth

Usage

Let's assume a Login API which will take email/password in the request body and will give the following response,

endpoint: https://awesome-api-service.com/auth/signin
method: POST

body:

{
  "email": "[email protected]",
  "password": "123456"
}

response:

{
  "user": {
    "id": "65d9ce7cefae2dd3c4da3e19",
    "email": "[email protected]",
    "phone": "01611223344",
    "firstName": "Mr.",
    "lastName": "Customer",
    "type": "Customer",
    "active": true,
    "verified": true,
    "lock": {
      "isLocked": false,
      "loginAttempts": 0
    }
  },
  "access": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1ZDljZTdjZWZhZTJkZDNjNGRhM2UxOSIsInR5cGUiOiJDdXN0b21lciIsImFjdGl2ZSI6dHJ1ZSwidmVyaWZpZWQiOnRydWUsImxvY2siOnsiaXNMb2NrZWQiOmZhbHNlLCJsb2dpbkF0dGVtcHRzIjowfSwiaWF0IjoxNzEyMTAwNjU0LCJleHAiOjE3MTIxMDA5NTR9.O80rJIkxEk2uCfviRvQPJwts5H8u-qRRPBCbjqX_oOk",
    "expiresAt": "2024-04-02T23:35:54.624Z"
  },
  "refresh": {
    "token": "AGrhx3aif0jZLZ3/8gsjbLpjqeFr+CDtkf+qrL1ErdZwufuYSNFUu4i5WsYO4MT+YeWHmM8A2lJa7bbSDeMwkg==",
    "expiresAt": "2024-04-03T00:00:54.625Z"
  }
}

Now, let's come to the library configuration.
Create a typescript file src/types/Auth.ts (it can be anywhere of your choice) for your User type,

import { AuthUser } from 'next-jwt-auth'

export interface LoggedInUser extends AuthUser {
  // the id: string | number property will come from AuthUser type
  email: string
  firstName: string
  lastName: string
  phone: string
  type: 'Super Admin' | 'Admin' | 'Customer'
  active: boolean
  verified: boolean
  photo?: string
  lock?: {
    isLocked: boolean
    loginAttempts: number
    feedback?: string
    lockedAt?: string
  }
}

After that, create another file in src/config/Auth.ts (again, it can be any file of your choice) and put the following configuration,

import { JWTAuthConfig, createJWTAuthProvider } from 'next-jwt-auth'
import { useContext } from 'react'
import { LoggedInUser } from '../types/Auth'

export const authConfig: JWTAuthConfig = {
  apiBaseUrl: 'https://awesome-api-service.com', // or, process.env.API_BASE_URL!
  user: {
    /**
     * This is the property name in the response
     * where your user object is located
     */
    property: 'user',
  },
  accessToken: {
    /**
     * This is the property name in the response
     * where your access token string is located
     */
    property: 'access.token',
    /**
     * Access token Expiry time is optional.
     * If no expiry time found, then access token
     * will automatically expire when there is an
     * Unathorized response (http status code 401) found
     * from your API service
     */
    expireTimeProperty: 'access.expiresAt', // optional
  },
  /**
   * Refresh token configuration is optional.
   * You can skip it if your backend API only gives access token in login response.
   * If refresh token configuration is undefined then
   * this library won't try to call refresh token API
   * and will automatically logout the user once the access token is expired
   */
  refreshToken: {
    /**
     * This is the property name in the response
     * where your access token string is located
     */
    property: 'refresh.token',
    /**
     * Refresh token Expiry time is optional.
     * If no expiry time found, then refresh token
     * will automatically expire when the library
     * can't get a new access token using the refresh token anymore
     */
    expireTimeProperty: 'refresh.expiresAt', // optional
  },
  /**
   * Here are the API endpoints that your custom Auth service exposes
   */
  endpoints: {
    login: { url: '/auth/signin', method: 'post' },
    logout: { url: '/auth/signout', method: 'post' },
    /**
     * (Optional)
     * You can skip 'refresh' property if your backend has no token refreshing mechanism
     */
    refresh: { url: '/auth/refresh-token', method: 'post' },
    /**
     * (Optional)
     * You can skip 'refresh' property if your backend has no user profile fetch API
     */
    user: { url: '/auth/profile', method: 'get' },
  },
  /**
   * This is the NextJS route for your login page.
   * This library will automatically redirect
   * user to this page when user session is expired.
   *
   * i.e refresh token is also expired and user needs to login again
   */
  pages: {
    login: { url: '/login' },
  },

  /**
   * (Optional)
   * This is the HTTP status code which is returned by the server whenever the access token is expired
   * Default is: 401
   */
  unauthorizedStatusCode: 401,

  /**
   * (Optional)
   * User profile fetch API call interval in millisecond.
   * For example: If this interval is set to 5000,
   * then this library will call the user profile fetch API after every 5000 milliseconds.
   *
   * This API call will be used to determine if the access token is still valid.
   * If the access token is expired, then the server will return an unauthorized
   * response and this library will logout the user from the app.
   */
  userFetchIntervalMS: 1000,
}

/**
 * Next, we create the React Context and Context Provider
 * using our own User type.
 *
 * The reason why you need to create the context is because,
 * you need to tell the User type to the library.
 *
 * Otherwise the library cannot infer the User type
 * (will explain later below)
 */
export const { JWTAuthContext, JWTAuthProvider } = createJWTAuthProvider<LoggedInUser>()

/**
 * (Optional)
 * This is just a custom hook to easily access the JWTAuthContext.
 * You can skip this and use useContext(JWTAuthContext)
 * in your component, but this approach is more clean.
 */
export const useJWTAuthContext = () => {
  const context = useContext(JWTAuthContext)

  if (!context) {
    throw new Error('JWTAuthContext not found, please check the provider')
  }

  return context
}

Now, add the JWTAuthProvider in you React layout component (src/components/layout/AppLayout.jsx),

'use client'

import { JWTAuthProvider, authConfig } from '../../config/Auth'

type AppLayoutProps = {
  children: React.ReactNode,
}

export default function AppLayout(props: AppLayoutProps) {
  return (
    <JWTAuthProvider config={authConfig}>
      <div>{props.children}</div>
    </JWTAuthProvider>
  )
}

You are ready to use the library in your login form component (src/components/auth/LoginForm.jsx)

'use client'

import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import { useJWTAuthContext } from '../../config/Auth'

export default function LoginForm() {
  /**
   * Here you can use the custom hook
   * to access the library API easily
   */
  const { loginWithCredentials } = useJWTAuthContext()

  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [isLoading, setIsLoading] = useState(false)

  const router = useRouter()
  const onSubmit = () => {
    try {
      setIsLoading(true) // show loading animation to user

      const isSuccess = await loginWithCredentials({
        email: values.email,
        password: values.password,
      })

      if (isSuccess) {
        router.push('/dashboard')
      }

    } catch (error: any) {
      console.log(error)
      // display error message to the user
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div>
      <div className="flex flex-col gap-2 w-full mx-auto">
        <EmailInputField .../>
        <PasswordInputField .../>
        <PrimaryActionButton
          label="Sign In"
          isLoading={isLoading}
          onClick={onSubmit}
        />
      </div>
    </div>
  )
}

Congratulations ! now you have a working login page. Now we need to access the currently logged in user information from the library. Let's assume we are displaying the user information in a Profile component. (src/components/user/Profile.jsx)

import { useJWTAuthContext } from '../../config/Auth'

export default function Profile() {
  /**
   * This 'user' object has the type of `LoggedInUser`
   * which we defined in the src/config/Auth.ts file
   *
   * We used the factory pattern
   * (createcreateJWTAuthProvider<LoggedInUser>()) to solve this problem.
   *
   * Without the factory pattern our 'user' object
   * will have the type of 'AuthUser'
   * which have only {id: string} in it.
   */
  const { user } = useJWTAuthContext()

  if (!user) {
    /**
     * User can be null. For example,
     * after logout we don't have any user object
     */
    return null
  }

  return (
    <div className="font-medium text-neutral text-sm">
      <span>{user.firstName}</span>
      <span>{user.lastName}</span>
    </div>
  )
}

Finally, for logout button, (src/components/auth/LogoutButton.jsx)

'use client'

import { useJWTAuthContext } from '../../config/Auth'

/**
 * User will be automatically redirected to 'login' page
 * (configured in src/config/Auth.ts) after logout
 */
export default function LogOutButton() {
  const { logout } = useJWTAuthContext()

  return (
    <button onClick={() => logout()}>
      <span>LogOut</span>
    </button>
  )
}

One important Note:
You should use the axios client from this library to call your APIs, otherwise this library won't detect when your access token gets rejected by your API server.

When you use the axios client from this library, the client will automatically add 'Authorization' header to your requests. It will also detect when your JWT token expires and will call your Refresh Token API to get a new Authorization token.

For example, let's say you have a custom hook that uses react-query to make a request to your API service, you can use the axios client from this library to make the request like below,

import { useQuery } from '@tanstack/react-query'
import { useJWTAuthContext } from '../../config/Auth'

type EmployeeListFetchParams = {
  page: number
  limit: number
  search: string
}

type EmployeeItem = {
  id: number
  name: string
  email: string
}

export const useEmployeeListFetchAPI = (params: EmployeeListFetchParams) => {
  const { apiClient } = useJWTAuthContext()

  return useQuery({
    queryKey: ['employee-list', params],
    async queryFn() {
      const endpoint = `/user/employee-list`
      const { data } = await apiClient().get<EmployeeItem[]>(endpoint, {
        params,
      })

      return data
    },
  })
}

The above apiClient() will use the same apiBaseUrl that you defined in src/config/Auth.ts. If you have different API base URL then just add the full URL to the above endpoint variable (example: const endpoint = 'https://my.another-api-service.com/user/employee-list')

Next Auth Middleware

You can use the isAuthenticatedRequest() method to check if the user is authenticated on the Next.js Server side. Here is a sample middleware:

import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { isAuthenticatedRequest } from 'next-jwt-auth'

const publicRoutes = ['/', '/login', '/signup', '/reset', '/success', '/verify']

// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
  const isUnprotectedRoute = publicRoutes.includes(request.nextUrl.pathname)

  if (!isUnprotectedRoute && !isAuthenticatedRequest(request)) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

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)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}