Home > Software engineering >  nextjs-auth0: update user session (without logging out/in) after updating user_metadata
nextjs-auth0: update user session (without logging out/in) after updating user_metadata

Time:12-29

I'm currently struggling to refetch a user's data from Auth0 after updating the user_metadata:

Below a simplified index file. the user selects some object, and will be asked to add this object (or object-id) as a favorite. If the user wants to select this object as a favorite, we want to update the preference in the user_metadata.

// index.tsx

export default function home({user_data, some_data}) {

   const [selected, setSelect] = useState(null)
   
   async function handleAddToFavourite() {
      if (selected) {
         const data = await axios.patch("api/updateMetadata", {some_favorite: selected.id})
         // Errorhandling ...
      }
   }

   return (
      <div>
         <SearchData setData={setSelect} data={some_data}/>
         <Button onClick={handleAddToFavorite}>Add to Favorite</Button>
         <div>Selected: {selected.id}</div>
         <div>My Favorite: {user_data.user_metadata.some_favorite}</div>
      </div>
  )
}

export const getServerSideProps = withPageAuthRequired({
   returnTo: "/foo",
   async getServerSideProps(ctx) {
     const session = await getSession(ctx.req, ctx.res)
     const {data} = await axios.get("https://somedata.com/api")

   return {props: {some_data: data, user_data: session.user}}
})

The request is then sent to pages/api/updateMetadata, and the user_metadata is updated with the selected data.

// api/updateMetadata.ts
async function handler(req: NextApiRequest, res: NextApiResponse) {

  const session = await getSession(req, res);

  if (!session || session === undefined || session === null) {
    return res.status(401).end();
  }

  const id = session?.user?.sub;
  const { accessToken } = session;

  const currentUserManagementClient = new ManagementClient({
    token: accessToken,
    domain: auth0_domain.replace('https://', ''),
    scope: process.env.AUTH0_SCOPE,
  });

  const user = await currentUserManagementClient.updateUserMetadata({ id }, req.body);

  return res.status(200).json(user);
}

export default withApiAuthRequired(handler);


The [...auth0].tsx looks something like this.

// pages/api/auth/[...auth0].tsx
export default handleAuth({
  async profile(req, res) {
    try {
      await handleProfile(req, res, {
        refetch: true,
      });
    } catch (error: any) {
      res.status(error.status || 500).end(error.message);
    }
  },
  async login(req, res) {
    try {
      await handleLogin(req, res, {
        authorizationParams: {
          audience: `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/`,
          scope: process.env.AUTH0_SCOPE,
        },
      });
    } catch (error: any) {
      res.status(error.status || 400).end(error.message);
    }
  },
});


Now, I get the user_metadata every time I log in, however, I need to log out and log in to see the change take effect. I need to somehow refresh the user-session without logging out, every time the user_metadata is updated.

Does anybody know any workarounds for achieving what I'm trying to do, and perhaps see any mistakes?

Thanks in advance!

Notes:

  • I have tried using the client-side function useUser(), but this yields the same data as server-side function getSession() for the user_data in index.tsx

  • I've tried adding updateSession(req, res, session) at the end of the api/updateMetadata handler

  • I've added an Action to the Auth0 login flow

// Auth0 action flow - login
exports.onExecutePostLogin = async (event, api) => {
  const namespace = 'https://example.com';
  const { some_favorite } = event.user.user_metadata;

  if (event.authorization) {
    // Set claims 
    api.idToken.setCustomClaim(`${namespace}/some_favorite`, );
  }
};


CodePudding user response:

I figured it out, I'll post my solution in case someone else gets stuck with the same issue :)

In api/updateMetadata.ts:


// api/updateMetadata.ts

import { updateSession , ...  } from '@auth0/nextjs-auth0';
// ...
// ...
const user = await currentUserManagementClient.updateUserMetadata({ id }, req.body);

await updateSession(req, res, { ...session, user }); // Add this to update the session

return res.status(200) // ...

Then I used checkSession() from the useUser in the client side code, straight after fetching the data.

// index.tsx

import { useUser } from '@auth0/nextjs-auth0/client'

//...

   async function handleAddToFavourite() {
      if (selected) {
         const data = await axios.patch("api/updateMetadata", {some_favorite: selected.id})
         // Update the user session for the client side
         checkSession()
         // Errorhandling ...
      }
   }


//...


Now, this is what made it all work, modifying the profileHandler:

// pages/api/auth/[...auth0].tsx

// Updating with the new session from the server
const afterRefetch = (req, res, session) => {
     const newSession = getSession(req, res)
     if (newSession) {
          return newSession as Promise<Session>
     }
     return session
}


export default handleAuth({
  async profile(req, res) {
    try {
      await handleProfile(req, res, {
        refetch: true,
        afterRefetch // added afterRefetch Function
      });
    } catch (error: any) {
      res.status(error.status || 500).end(error.message);
    }
  },

  // ...

});

Also, it is worth noting that the Auth0 Action Flow for the login is correct too.

Hope this helps someone :)

  • Related