rooo
rooo

Reputation: 153

next-auth credentials provider authorize type error

Problem

I am only using a single CredentialsProvider in next-auth but I'm confused about how to handle async authorize() with a custom user interface.

I defined the user interface in types/next-auth.d.ts as follows:

import NextAuth from "next-auth"

declare module "next-auth" {
  interface User {
    id: string
    address: string
    name?: string
  }
}

This is the provider definition in [...nextauth].ts:

CredentialsProvider({
  name: "Ethereum",
  credentials: {
    message: {
      label: "Message",
      type: "text",
    },
    signature: {
      label: "Signature",
      type: "text",
    },
  },
  async authorize(credentials) {
    try {
      const nextAuthUrl = process.env.NEXTAUTH_URL
      if (!nextAuthUrl) return null
      if (!credentials) return null

      // [verify the credential here]
      // "message" contains the verified information

      let user = await prisma.user.findUnique({
        where: {
          address: message.address,
        },
      })
      if (!user) {
        user = await prisma.user.create({
          data: {
            address: message.address,
          },
        })
      }

      return {
        id: user.id,
        address: user.address,
        name: user.name
      }
    } catch (e) {
      console.error(e)
      return null
    }
  },
})

Now I see the typescript error in the async authorize(credentials)

Type '(credentials: Record<"message" | "signature", string> | undefined) => Promise<{ id: string; address: string; name: string | null; } | null>' is not assignable to type '(credentials: Record<"message" | "signature", string> | undefined, req: Pick<RequestInternal, "body" | "query" | "headers" | "method">) => Awaitable<...>'.
  Type 'Promise<{ id: string; address: string; name: string | null; } | null>' is not assignable to type 'Awaitable<User | null>'.
    Type 'Promise<{ id: string; address: string; name: string | null; } | null>' is not assignable to type 'PromiseLike<User | null>'.
      Types of property 'then' are incompatible.
        Type '<TResult1 = { id: string; address: string; name: string | null; } | null, TResult2 = never>(onfulfilled?: ((value: { id: string; address: string; name: string | null; } | null) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | unde...' is not assignable to type '<TResult1 = User | null, TResult2 = never>(onfulfilled?: ((value: User | null) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => PromiseLike<...>'.
          Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
            Types of parameters 'value' and 'value' are incompatible.
              Type '{ id: string; address: string; name: string | null; } | null' is not assignable to type 'User | null'.
                Type '{ id: string; address: string; name: string | null; }' is not assignable to type 'User'.
                  Types of property 'name' are incompatible.
                    Type 'string | null' is not assignable to type 'string | undefined'.
                      Type 'null' is not assignable to type 'string | undefined'.

Docs

Credentials provider

NextAuth with typescript/extend interface

Upvotes: 13

Views: 13870

Answers (5)

Andre wijaya
Andre wijaya

Reputation: 29

If you are using external API you can modify it like this

Credentials({
      authorize: async (credentials) => {
        // console.log({ credentials });
        // Validated the fields if correct or not
        const validateFields = LoginSchema.safeParse(credentials);
        if (validateFields.success) {
          // If Success check if user valid
          const user = await fetch("http://localhost:3000/auth",{
            method:"POST",
            body:JSON.stringify({
              email:validateFields.data.email,
              password:validateFields.data.password
            }),
            headers:{
              "Content-type":"application/json"
            }
          })
          // const user = { name: "jay", password: "dave" };
          // If user valid return user object
          if(user) return user.json()
        }
        // If not just return null
        return null;
      },

Just convert user doc to json it will fix type error

Upvotes: 0

Nisha Dave
Nisha Dave

Reputation: 789

TL;DR

just adding id field to the returned user solved problem for me

example

import nextAuth from "next-auth/next";
import { AuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

export const authOptions: AuthOptions = {
  providers: [
    CredentialsProvider({
      credentials: {
        email: {},
        password: {},
      },
      async authorize(credentials) {
        const user = { id: "hello", name: "jay", password: "dave" };
        if (!user || !user.password) return null;

        const passwordsMatch = user.password === credentials?.password;

        if (passwordsMatch) return user;
        return null;
      },
    }),
  ],
};

export default nextAuth(authOptions);

how I decided to add id, I looked into the types,

this is how credentials config interface is defined

export interface CredentialsConfig<
  C extends Record<string, CredentialInput> = Record<string, CredentialInput>
> extends CommonProviderOptions {
  type: "credentials"
  credentials: C
  authorize: (
    credentials: Record<keyof C, string> | undefined,
    req: Pick<RequestInternal, "body" | "query" | "headers" | "method">
  ) => Awaitable<User | null>
}

as you can see it returns awaitable user and user is defined like this

export interface DefaultUser {
  id: string
  name?: string | null
  email?: string | null
  image?: string | null
}

/**
 * The shape of the returned object in the OAuth providers' `profile` callback,
 * available in the `jwt` and `session` callbacks,
 * or the second parameter of the `session` callback, when using a database.
 *
 * [`signIn` callback](https://next-auth.js.org/configuration/callbacks#sign-in-callback) |
 * [`session` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
 * [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
 * [`profile` OAuth provider callback](https://next-auth.js.org/configuration/providers#using-a-custom-provider)
 */
export interface User extends DefaultUser {}

as you can see id is a compulsory field.

Upvotes: 1

TEhubSLV
TEhubSLV

Reputation: 89

I managed to fix the issue by typing the return type of the function as Promise

 async authorize(credentials, req): Promise<any> {
   //code here
}

Upvotes: 7

Froxx
Froxx

Reputation: 1077

Got the same issue. Since I didn't want to turn of strict mode in .tsconfig, I simply went with parsing the returned object of authorize() to any...

async authorize(credentials) {
  // ...
  return {
    // ...
  } as any. // <-- This here
}

I don't like it, but I think it's better than turning off strict mode.

Also it doesn't destroy type-safety in the follow up since the next step using the returned type would be in the jwt({user}) callback, and the typing still works there just fine.

Upvotes: 16

As stated on GitHub TypeScript error for the Credentials provider #2701 the current fix for this issue is to set strict to false ("strict": false) in the tsconfig.json file. The reason for this is that NextAuth.js was developed using "strict": false but they are currently working on it to become compatible with strict set to true.

Upvotes: 7

Related Questions