useUser user_metadata undefined after user first sign's up

I have a Nextjs client (using @auth0/nextjs-auth0 package). I have a post-login action that uses the provided email address to retrieve their full name from SalesForce and store it in user_metadata.

When I test this by logging in, I can see the user_metadata populating correctly, and the Nextjs front-end correctly displays their name (“Hello, [NAME]” at the top of the site).

But if the user is signing up for the first time, the Nextjs client says “Hello, undefined” until I log out/in after first signing up. I can see the user_metadata is populated correctly but it looks like the user_metadata isn’t available when Auth0 redirects back to the nextjs client.

It seems I can use the post-login action trigger to handle both logins and sign-ups as both of these events are triggering the action correctly and populating the user_metadata.

But why do I not have access to this user_metadata initially for sign-ups?

Here’s a snippet of code where I retrieve the User metadata. All the other Nextjs code is boilerplate

import Link from 'next/link';
import { useUser } from '@auth0/nextjs-auth0/client';
import { User } from '@/types/types';

const Profile = () => {
  const { user, error, isLoading } = useUser();
  const meta = user?.['NAMESPACE/meta'] as User;

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>{error.message}</div>;

  if (!user) {
    return (
      <Link href="/api/auth/login">
        Login
      </Link>
    );
  }

  return (
    <div>
      <Link href="#">{`Hello, ${meta.first_name}`}</Link> (<Link href="/api/auth/logout">log out</Link>)
    </div>
  );
};

export default Profile;

Hi @wfsmith

Welcome to the Auth0 Community!

Just to confirm, are you setting user metadata inside the PostLogin trigger, right?
Basically, if that is the case, the cause is the fact that the user profile is already generated and the user metadata is set after that, thus your application receiving a profile without the user metadata.

In the example below:

exports.onExecutePostLogin = async (event, api) => {
  const namespace = "https:/my-cool-name-space";
  api.idToken.setCustomClaim(`${namespace}/meta`, "random_data");
  api.accessToken.setCustomClaim(`${namespace}/mtea`, "random_data");

  const metadata = "test_data";

  api.user.setUserMetadata("some_user_metadata", metadata);

  console.log(event.user.user_metadata);
};

If a user signs up, the metadata will not be available in the user profile but it will be visible inside the dashboard. However, the custom claims set will be accessible after a successful signup and might be more suitable for your use case.

An workaround for the issue that you are experiencing would be to perform a silent authentication once the user authenticates so that it returns the user profile containing the set metadata.

If you have any other question, let me know!

Kind Regards,
Nik

Thanks. Yes I am updating the user_metadata as well as calling setCustomClaim. Here is the action code:

exports.onExecutePostLogin = async (event, api) => {

    // get secrets
    const sfDomain = event.secrets.SALESFORCE_DOMAIN;
    const sfClientId = event.secrets.SALESFORCE_CLIENT_ID;
    const sfClientSecret = event.secrets.SALESFORCE_CLIENT_SECRET;
    if (!sfDomain || !sfClientId || !sfClientSecret) {
      console.log(`Environment variables not configured correctly`);
      return;
    }

    // fetch the token from the cache or regenreate it if it cannot be retrieved, see
    // https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_endpoints.htm
    const fetchAccessToken = async () => {
      const cachedToken = api.cache.get('sf_access_token');
      if (cachedToken) {
          return cachedToken.value;
      }
      const sfLogin = `https://${sfDomain}/services/oauth2/token`;
      const body = new FormData();
      body.set('grant_type', 'client_credentials');
      body.set('client_id', sfClientId);
      body.set('client_secret', sfClientSecret);
      
      // force weekly refresh on a per-host basis
      const expiry = Date.now() + 3600000;
      const response = await fetch(sfLogin, {
        method: 'POST',
        body: body,
      });

      if (!response.ok) {
        throw new Error('Unable to fetch token');
      }
      const data = await response.json();
      api.cache.set('sf_access_token', data.access_token, {
        expires_at: expiry,
      });

      return data.access_token;
    };

    const getContactData = async (access_token, email, newsletters) => {
      // code that retrieves contact info from salesforce
    };

    try {
      if(event.user.email) {
        const token = await fetchAccessToken();
        const contact = await getContactData(token, event.user.email, newsletters);

        const { FirstName, LastName, Email, Phone } = contact;
        if(contact) {
          // set user fields
          api.user.setUserMetadata('first_name', FirstName);
          api.user.setUserMetadata('last_name', LastName);
          api.user.setUserMetadata('email', Email);
          api.user.setUserMetadata('phone', Phone);

          // make the user meta accessible on the client
          let namespace = event.secrets.NAMESPACE || '';
          if (namespace && !namespace.endsWith('/')) {
              namespace += '/';
          }
          api.idToken.setCustomClaim(namespace + 'meta', event.user.user_metadata);
        }
      }
    }
    catch (error) {
      throw new Error('Unable to update user_meta');
    }
};