How to write authorize function correctly in typescript for nextauthjs
Asked Answered
D

5

7

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"]
}

Dr answered 4/2, 2022 at 17:11 Comment(0)
D
4

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.

Daemon answered 5/2, 2022 at 19:40 Comment(3)
The OP states a typescript issue relating to the credentials input, and your answer goes into details about return type, which is not the culprit here.Hellkite
@Hellkite The Promise<Omit<User, "id"> | null | undefined> type the error refers to is the return type of the authorize function. The last line of the error states: "Index signature for type 'string' is missing in type 'Promise<Omit<User, "id"> | null | undefined>'", this is related to the return type.Daemon
@Hellkite That being said, I'm not claiming it's the actual solution to OP's issue, as they never got back to me. It could very well be a bug like you said it is.Daemon
H
3

This is actually a bug not yet solved: https://github.com/nextauthjs/next-auth/issues/2701.

Hellkite answered 31/5, 2022 at 10:47 Comment(0)
M
3

This issue is still in the NextAuth latest version of 4.20.1

To fix the issue you just need to correct one type issue in the return User interface that they only consider your data for ID is string and not number type, which is fine as usually using this provider will be visiting database and most of them are returning string not number

CredentialsProvider({
      id: "password",
      name: "Email and Password",
      credentials: {
        email: { label: "Email", type: "text", placeholder: "[email protected]" },
        password: { label: "Password", type: "password" }
      },
      authorize: async (credentials, req) => {
        const user = { id: '1', name: 'J Smith', email: '[email protected]' };
        if (user) {
          return user;
        } else {
          return null;
        }
      }
    }),

Make sure you return the User object with ID using string like 1 to "1" will fix the type issue, do not need to change tsconfig.json

Masterful answered 31/3, 2023 at 3:12 Comment(0)
S
0

This has worked for me by setting the strict mode from true to false in tsconfig.json.

See the image below.

enter image description here

Scleritis answered 28/9, 2022 at 15:49 Comment(1)
Never do this. So your code is no different from jsCumings
R
0

To fix this error, make sure that the user object that is returned by the authorize function has "id" property with a numeric string value like so:

authorize: async (credentials, req) => {
        await connectDB();
        try {
          if (!credentials?.username) {
            return null;
          }
          if (!credentials?.password) {
            return null;
          }
          const user = {
            id: "1",
            username: "[email protected]",
            name: "Bello shehu",
          };
          // fetch user from db
          return user;
        } catch (error) {
          return null;
        }
Raul answered 3/8, 2023 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.