custom session next js next-auth
Asked Answered
P

4

10

I got an problem when migrate my js file jo tsx, what I'm doing is signin with credentials and custom the session user to my user data

// api/auth/[...nextauth].js

import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import { ConnectDatabase } from "../../../lib/db";
import { VertifyPassword } from "../../../lib/password";
import { getSelectedUser } from "../../../helpers/database";
import { MongoClient } from "mongodb";
import { NextApiRequest } from "next";

interface credentialsData {
 data: string | number;
 password: string;
}
export default NextAuth({
 session: {
   jwt: true,
 },
 callbacks: {
   async session(session) {
     const data = await getSelectedUser(session.user.email);
     session.user = data.userData;

// inside data.userdata is a object
// {
//   _id: '60a92f328dc04f58207388d1',
//   email: '[email protected]',
//   phone: '087864810221',
//   point: 0,
//   role: 'user',
//   accountstatus: 'false'
// }
     return Promise.resolve(session);
   },
 },
 providers: [
   Providers.Credentials({
     async authorize(credentials: credentialsData, req: NextApiRequest) {
       let client;
       try {
         client = await ConnectDatabase();
       } catch (error) {
         throw new Error("Failed connet to database.");
       }

       const checkEmail = await client
         .db()
         .collection("users")
         .findOne({ email: credentials.data });
       const checkPhone = await client
         .db()
         .collection("users")
         .findOne({ phone: credentials.data });

       let validData = {
         password: "",
         email: "",
       };

       if (!checkEmail && !checkPhone) {
         client.close();
         throw new Error("Email atau No HP tidak terdaftar.");
       } else if (checkEmail) {
         validData = checkEmail;
       } else if (checkPhone) {
         validData = checkPhone;
       }

       const checkPassword = await VertifyPassword(
         credentials.password,
         validData.password
       );
       if (!checkPassword) {
         client.close();
         throw new Error("Password Salah.");
       }
       client.close();

// inside validData is a object
// {
//   _id: '60a92f328dc04f58207388d1',
//   email: '[email protected]',
//   phone: '087864810221',
//   point: 0,
//   role: 'user',
//   accountstatus: 'false'
// }

       return validData;
     },
   }),
 ],
});
// as default provider just return session.user just return email,name, and image, but I want custom the session.user to user data what I got from dababase

This in client side

// index.tsx

export const getServerSideProps: GetServerSideProps<{
  session: Session | null;
}> = async (context) => {
  const session = await getSession({ req: context.req });

  if (session) {
    if (session.user?.role === "admin") {
      return {
        redirect: {
          destination: "/admin/home",
          permanent: false,
        },
      };
    }
  }
  return {
    props: {
      session,
    },
  };
};

But in client side I got warning

Property 'role' does not exist on type '{ name?: string; email?: string; image?: string; 

actually my file still working fine, but when my file in js format, its not warning like that

can someone help me to fix it ?

Pergrim answered 29/5, 2021 at 9:43 Comment(3)
Can you show me the implementation of the type Session that you're using in your index.tsx?Inlet
on top in my question, you can see I put in "this client side" @RafaelUmbelinoPergrim
I really need to see the interface to help you. Currently I'm suspecting that the problem is that your interface doesn't have a 'role' value, so just the comments saying what properties the object holds isn't enouth because in TS you can only access the properties declared in the interface type.Inlet
T
8

I imagine by now you have this solved, but since I ran across this page with the same issue I figured I'd post my solution. Just in case someone else runs across it. I'm new to typescript/nextjs and didn't realize I simply had to create a type definition file to add the role field to session.user

What you're doing here is merging your changes into the next-auth module, as explained here.

I created /types/next-auth.d.ts

import NextAuth from "next-auth";

declare module "next-auth" {
  interface Session {
    user: {
      id: string;
      username: string;
      email: string;
      role: string;
      [key: string]: string;
    };
  }
}

Then I had to add this to my tsconfig.json

  "include": ["next-env.d.ts", "types/**/*.ts", "**/*.ts", "**/*.tsx"],
Tetrabranchiate answered 24/7, 2022 at 15:34 Comment(1)
Simple instructions to follow and worked as expected. Thank you!Vitreous
M
6

Not sure if you found a workaround yet but you need to configure the jwt callback as well! Here is an example from a project of mine:

callbacks: {
        async session(session, token) {
            session.accessToken = token.accessToken;
            session.user = token.user;
            return session;
        },
        async jwt(token, user, account, profile, isNewUser) {
            if (user) {
                token.accessToken = user._id;
                token.user = user;
            }
            return token;
        },
    },

To explain things. jwt function always runs before session, so whatever data you pass to jwt token will be available on session function and you can do whatever you want with it. In jwt function i check if there is a user because this only returns data only when you login.

Mage answered 3/7, 2021 at 15:15 Comment(0)
G
4

I like the @klc3rd's answer, but you can be more precise with types extension and not override the properties defined in the DefaultSession entirely:

import { type DefaultSession } from 'next-auth';

declare module 'next-auth' {
  /**
   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {
    user?: {
      id: string;
      role?: string;
      username?: string;
      someExoticUserProperty?: string;
    } & DefaultSession['user'];
  }
}

Also, see that that user is marked as optional. That's because useSession, by default, works on the client and fetches the user's session. And before the fetching process has finished the user property can be undefined

Gittle answered 11/3, 2023 at 15:14 Comment(0)
O
0

To extend your whole User to the token and session:

  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.user = user;
      }
      return token;
    },
    async session({ session, token }) {
      if (token && token.user) {
        session.user = token.user as User;
      }
      return session;
    },
  }
Otto answered 4/1 at 3:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.