How to write authorize function correctly in typescript for nextauthjs

Viewed 2234

I am trying to use Credentials Provider with NextJs ("next": "^12.0.7") and NextAuth ("next-auth": "^4.1.2"). I am writing this in typescript, and facing issues in getting the function correctly.

Here's my /pages/api/[...nextauth].ts

import NextAuth, {  } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { verifyOTP } from "@/lib/otp";

// {
//   user: {
//     id: 1,
//     username: null,
//     phone: '+919876543210',
//     phoneVerified: true,
//     email: null,
//     emailVerified: false,
//     active: true,
//     token: 'SOME TOKEN',
//     createDate: null
//   },
//   result: 'approved'
// }

export default NextAuth({
  pages: {
    signIn: '/signup',
    newUser: '/signup',
    error: '/signup'
  },
  debug: false,
  secret: "SOME_SECRET",
  // Configure one or more authentication providers
  providers: [
    CredentialsProvider({
      credentials: {
        username: {label: "username", type: "text", placeholder: "markandre"},
        phone: { label: "Phone ", type: "text", placeholder: "+919876543210" },
        otp: {  label: "OTP", type: "text" }
      },
      authorize: async (credentials, _req) => {
        try {
          const res = await verifyOTP(credentials!.phone,
            credentials!.otp, credentials?.username);
          if (res.result === "approved") {
            return Promise.resolve( {
              id: res.user.id,
              email: res.user.email,
              name: res.user.phone,
              token: res.user.token
            });
          }
        } catch (e: any) {
          //const errorMessage = e.response.data.message;
          //throw new Error(errorMessage);
          return Promise.reject(null);
        }
      }
    })
  ],
  session: {
    strategy: 'jwt',
    maxAge: 3 * 60 * 60, // 3 hours
  },
  callbacks: {
    jwt: async ({token, user}) => {
      if (user) {
        token.accessToken = user.token
      }

      return token;
    },
    session:async ({session, token}) => {
      if (token) {
        session.accessToken = token.accessToken;
      }
      return session;
    }
  },
  events: {
    signIn: async (message) => {
      console.log('signIn', message);
    },
    signOut: async (message) => {
      console.log('signOut', message);
    },
    createUser: async(message) => {
      console.log('createUser', message);
    },
    updateUser: async(message) => {
      console.log('updateUser', message);
    },
    linkAccount: async(message) => {
      console.log('linkAccount',message);
    },
    session: async(message) => {
      // console.log('session', message);
    }
  }
})

When I am building this app, I keep getting the error

./src/pages/api/auth/[...nextauth].ts:36:7
Type error: Type '(credentials: Record<"username" | "phone" | "otp", string> | undefined, _req: Pick<IncomingRequest, "headers" | "body" | "query" | "method">) => Promise<...>' is not assignable to type '(credentials: Record<"username" | "phone" | "otp", string> | undefined, req: Pick<IncomingRequest, "headers" | "body" | "query" | "method">) => Awaitable<...>'.
  Type 'Promise<Omit<User, "id"> | null | undefined>' is not assignable to type 'Awaitable<Omit<User, "id"> | { id?: string | undefined; } | null>'.
    Type 'Promise<Omit<User, "id"> | null | undefined>' is not assignable to type 'Omit<User, "id">'.
      Index signature for type 'string' is missing in type 'Promise<Omit<User, "id"> | null | undefined>'.

  34 |         otp: {  label: "OTP", type: "text" }
  35 |       },
> 36 |       authorize: async (credentials, _req) => {
     |       ^
  37 |         try {
  38 |           const res = await verifyOTP(credentials!.phone,
  39 |             credentials!.otp, credentials?.username);
info  - Checking validity of types .%

My .tsconfig looks like below

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "strict": true,
    "alwaysStrict": false,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true,

    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "allowUnreachableCode": false,
    "noFallthroughCasesInSwitch": true,

    "target": "es5",
    "outDir": "out",
    "declaration": true,
    "sourceMap": true,

    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,

    "jsx": "preserve",
    "noEmit": true,
    "isolatedModules": true,
    "incremental": true,

    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@/public/*": ["./public/*"]
    }
  },
  "exclude": ["./out/**/*", "./node_modules/**/*"],
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

2 Answers

The authorize function needs to return either an object representing a user or false/null if the credentials are invalid.

Your current logic isn't explicitly returning false/null for the case when the res.result === "approved" check is false, hence the type error. You can fix this by explicitly returning null after the if block.

authorize: async (credentials, _req) => {
    try {
        const res = await verifyOTP(credentials!.phone, credentials!.otp, credentials?.username);
        if (res.result === "approved") {
            return {
                id: res.user.id,
                email: res.user.email,
                name: res.user.phone,
                token: res.user.token
            };
        }
        return null; // Add this line to satisfy the `authorize` typings
    } catch (e: any) {
        //const errorMessage = e.response.data.message;
        //throw new Error(errorMessage);
        return null;
    }
}

As a side note, you also don't need to wrap the user object and null with Promise.resolve()/Promise.reject(), so I've also omitted those.

Related