How do I add data to the client API in next-auth?
Asked Answered
S

3

6

I'm currently using next-auth for authorisation using the credentials provider, I have sessions working and the user can login etc. However, on the session I need to pass in some data using the client API, the users, firstname, lastname, username and email.

By default the client API passes, name, email and image, however, how do I change this to add the above data, here is what I have so far.

index.js

import { useState, useEffect  } from 'react';
import { getSession } from 'next-auth/client'
import { useRouter } from 'next/router';
import Link from 'next/link';
import Head from 'next/head';
import Sidebar from '../components/Sidebar';

export default function Dashboard({ user}) {
  return (
    <div>
      <Head>
        <title>Dashboard</title>
      </Head>

      <Sidebar />

      <section className="content dashboard-content">
        <h1>Dashboard</h1>

        <h3>Welcome to Ellis development {user.firstname }</h3>
      </section>
    </div>
  )
}

export async function getServerSideProps(ctx) {
  const session = await getSession(ctx);
  
  if (!session) {
    return {
      redirect: {
        destination: '/dashboard/auth/login',
        permanent: false
      },
    }
  }

  console.log(session);

  return {
    props: {
      user: {
        firstname: session.user.firstname,
        lastname: session.user.lastname,
        username: session.user.username,
        email: session.user.email,
      }
    },
  }
}

[...nextauth.js]

import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';

import { verifyPassword } from '../../../lib/auth';
import { connectToDatabase } from '../../../lib/mongodb';

export default NextAuth({
  session: {
    jwt: true,
  },
  providers: [
    Providers.Credentials({
      async authorize(credentials) {
        const client = await connectToDatabase();
        const usersCollection = client.db().collection('users');

        const user = await usersCollection.findOne({
          email: credentials.email,
        });

        if (!user) {
          client.close();
          throw new Error('No user found!');
        }

        const isValid = await verifyPassword(
          credentials.password,
          user.password
        );

        if (!isValid) {
          client.close();
          throw new Error('Could not log you in!');
        }

        client.close();

        return {
          firstname: user.firstname,
          lastname: user.lastname,
          username: user.username,
          email: user.email
        };
      },
    }),
  ],
});

Any help would be great, thanks.

edit

I've added the following to the [...next-auth] page

callbacks: {
  session: async (session) => {
    if (!session) return;

    const client = await connectToDatabase();
    const usersCollection = client.db().collection('users');
    
    const userData = await usersCollection.findOne({
      email: session.user.email,
    });

    return {
      session: {
        user: {
          id: userData._id,
          firstname: userData.firstname,
          lastname: userData.lastname,
          username: userData.username,
          email: userData.email
        }
      }
    };
  },
},

which gives me the following result

{
  session: {
    user: {
      id: '61a107f29ca24c12146d1b22',
      firstname: 'Ben',
      lastname: 'Bagley',
      username: 'benbagley',
      email: '[email protected]'
    }
  }
}

So I now have the values I need, however, how do I go rendering the data onto the page I now have the following

import { getSession } from 'next-auth/client'
import Head from 'next/head';
import Sidebar from '../components/Sidebar';

export default function Dashboard({ session }) {
  return (
    <div>
      <Head>
        <title>Dashboard</title>
      </Head>

      <Sidebar />

      <section className="content dashboard-content">
        <h1>Dashboard</h1>

        <h3>Welcome {session.user.firstname} to Ellis development</h3>
      </section>
    </div>
  )
}

export async function getServerSideProps(ctx) {
  const session = await getSession(ctx);
  
  if (!session) {
    return {
      redirect: {
        destination: '/dashboard/auth/login',
        permanent: false
      },
    }
  }

  console.log(session);

  return {
    props: {
      session: {
        user: {
          id: session.user.id,
          firstname: session.user.firstname,
          lastname: session.user.lastname,
          username: session.user.username,
        }
      }
    },
  }
}

However, I am getting TypeError: Cannot read properties of undefined (reading 'id')

Schweiker answered 25/11, 2021 at 21:55 Comment(0)
S
5

The issue here was two fold,

a) Not using a proper callback to add in and overwrite the next-auth api, for example:

callbacks: {
  session: async (session) => {
    if (!session) return;

    const client = await connectToDatabase();
    const usersCollection = client.db().collection('users');
    
    const userData = await usersCollection.findOne({
      email: session.user.email,
    });

    return {
      session: {
        user: {
          id: userData._id,
          firstname: userData.firstname,
          lastname: userData.lastname,
          username: userData.username,
          email: userData.email
        }
      }
    };
  },
},

Now that this is passing in the values, the next issue props up...

b) Not using the spread operator when passing props

export async function getServerSideProps(ctx) {
  const session = await getSession(ctx);
  
  if (!session) {
    return {
      redirect: {
        destination: '/dashboard/auth/login',
        permanent: false
      },
    }
  }

  return {
    props: {
      ...session,
    }
  }
}

Calling ...session gets all of the return object and allows it to be passes as such session.user.firstname, very handy.

Schweiker answered 27/11, 2021 at 1:28 Comment(1)
"The session callback is called whenever a session is checked." so It will hit session callback too many times, and hit database query too.Heliopolis
G
2

You should checkout next-auth callbacks. The user object will contain email, image and name. You can use it to fetch an internal api or so, and append the info to the session object which will be encoded in the jwt.

callbacks: {
        session: async (session, user) => {
            if (!session) return;
            const userServerData = fetch(...); // or some internal logic
            
            session.user.firstName = userServerData.firstName;
            session.user.lastname = userServerData.lastname;
            session.user.username = userServerData.username;
            
            return Promise.resolve(session);
        },
    },
Guinevere answered 25/11, 2021 at 22:31 Comment(1)
Please see the edit to the question based on your response, I'm not sure on what you want me to fetch here?Schweiker
D
0

This is how I solved the same problem in my project

Step 1: Add the following to ...[nextauth].ts

  pages: {
    // signIn: '/auth/signin',
    // signOut: '/auth/signout',
    // error: '/auth/error', // Error code passed in query string as ?error=
    // verifyRequest: '/auth/verify-request', // (used for check email message)
    newUser: '/auth/newuser' // New users will be directed here on first sign in 
  },

Step 2: Now you can update user data and post to your api

'/auth/newuser.ts'

    React.useEffect(() => {
   

       post updated user data to -> "/api/auth/newuserwelcome"

      }, [])

//
Step 3: create api endpoint to save updated user data to DB
    /api/auth/newuserwelcome.ts
      
Damn answered 26/11, 2021 at 22:58 Comment(1)
I have callback url in sign page so how can I pass that to new user page?Onset

© 2022 - 2024 — McMap. All rights reserved.