api.user.setUserMetadata not working with postLogin action

As we migrate our Auth0 Rules to Actions, I stumbled into this problem with setUserMetaData not working with the postLogin action.

I have found this related topic and it was mentioned that the resolution was to instead use multiple actions. However, I just confirmed that this does not work too.

Any suggestions for a workaround are much appreciated.

Thanks

Hi @cmoreno,

You can do this all in a single Action by changing step 6 in the topic you linked.

Instead of

Now, get the newly set event.user.user_metadata.hasuraUser attribute, then set custom claims for tokens.

You already know the claims that you want set from step 5, just set them directly in a token without fetching them from the event object.

Feel free to share your Action and I can help with the code.

Thanks,
Dan

1 Like

Hi @dan.woda,

Apologies for not clearly stating our use-case.

In our case, we’re using a single action for this. Actually I did a test where I just set the user metadata like below:

exports.onExecutePostLogin = async (event, api) => {
    api.user.setUserMetadata('test', true);
}

That simple code does not update the user’s userMetadata at all for postLogin action.

Any thoughts?

Are you sure that the action is firing? It’s possible it has not been added to the flow.

1 Like

@dan.woda,

I’ve tested the simple code that I’ve sent and I can confirm that it works. My bad for that. However, for a postLogin action that’s doing other stuffs, it seems that calling setUserMetadata does not work similar to the other article that I referenced.

I’ll do another test to provide more information about my issue.

Really appreciated your response.

UPDATE:
Yep, did another round of testing and still doesn’t set the user metadata in the same postLogin action. Here’s the steps of our postLogin action:

  1. Using async.parallel, we’re fetching the following:
  • Fetch auth0 users with the given event.user.email
  • Fetch user from external database if existing (by email)
  1. Set userMetadata for the event.user if equivalent user was found in the external database
  2. Account Linking process (if merging is possible)

Would you please share the action with any sensitive data removed?

1 Like

@dan.woda,

Here’s the stripped version of the postLogin action. This postLogin action is responsible for merging accounts for universal login and passwordless login.

exports.onExecutePostLogin = async (event, api) => {
  const _ = require('lodash');
  const async = require('async');
  const axios = require('axios').default;
  const ManagementClient = require('auth0').ManagementClient;

  const management = new ManagementClient({
    clientId: event.secrets.AUTH0_APP_CLIENT_ID,
    clientSecret: event.secrets.AUTH0_APP_CLIENT_SECRET,
    domain: event.secrets.AUTH0_DOMAIN
  });

  // Lookup Users by Email within Auth0 and External DB
  async.parallel({
    auth0: function(cb) {
      management.usersByEmail.getByEmail(
        { email: event.user.email }
      ).then(response => {
        cb(null, response.data);
      }).catch(err => {
        cb(`ERROR: ${JSON.parse(err.body)['message']}`, null);
      });
    },
    external: function(cb) {
      var options = {
        method: 'GET',
        url: `${event.secrets.TARGET_DOMAIN}/api/users/lookup`,
        params: {email: event.user.email},
        headers: {authorization: `Bearer ***`},
        validateStatus: () => true
      };
      // External DB Lookup
      axios.request(options).then(async function (response) {
        if(response.status === 200) {
          console.log("Lookup: User found!");
          cb(null, response.data);
        } else if (response.status === 404) {
          try { 
            var createdUser = await createUser();
            cb(null, createdUser);
          } catch (error) {
            cb(error, response.data);
          }
        } else {
          cb(new Error("Error Requesting User Profile!"), response.data);
        }
      }).catch(function (err) {
        cb(new Error(`ERROR - User Lookup: ${err}`), null);
      });
    }
  }, async function(err, results) {
    // Break on Errors Requesting Data
    if(err) {
      console.error("Error Requesting User Profile Data: " + err);
      return;
    }

    if ([1, 2].includes(results.auth0.length)) {
      // Add external user id to user_metadata
      api.user.setUserMetadata('external_user_id', results.external.user.id);
      // Workflow: Single Auth0 User Found - Merger Not Needed
      if (results.auth0.length === 1) {
        console.log('Single Auth0 User Found - Merger Not Needed');
        return;
      }
    } else if (results.auth0.length === 0) {
      // Workflow: Auth0 User Not Found
      console.log('Auth0 user not found.');
      return;
    } else if (results.auth0.length > 2) {
      // Workflow: Manual Work Needed to Merge Account - Over 2 Auth0 Account Exist
      console.log('Rule: Multiple User Profiles Already Exist');
      return;
    }

    // Collect identities
    var identities = [];
    _.forEach(results.auth0, function(e) {
      _.forEach(e.identities, function(u) {
        var merged = u;
        merged.auth0_id = e.user_id;
        merged.created_at = new Date(e.created_at).getTime();
        merged.updated_at = new Date(e.updated_at).getTime();
        identities.push(merged);
      });
    });

    // Determine Base Auth0 User Account - Base User should be Provider: Auth0
    identities = _.sortBy(identities, ['created_at']);
    var baseIdentity = _(identities).filter({'provider': 'auth0'}).first();

    // Determine if current user is the base identity
    if (event.user.user_id === baseIdentity.auth0_id) {
      var secondIdentity = _(identities).filter({'provider': 'email'}).first();
      // Link secondary (email) to base identity (auth0 provider)
      management.users.link(
        { id: baseIdentity.auth0_id }, 
        { provider: secondIdentity.provider, user_id: secondIdentity.auth0_id }
      ).then(data => {
        console.log('Link User (1) - Successful');
      }
      ).catch(err => {
        console.log(`Link User (1) - ERROR: ${err}`);
      });
    } else {
      var userIdentity = _(event.user.identities).first();

      // Prevent account-linking for users using the same email address but does not match with 
      // the base identity connection. Except for email connection (passwordless).
      if(userIdentity.connection != 'email' && userIdentity.connection !== baseIdentity.connection) {
        console.log('Connection did not match the base identity connection. Skipping.');
        return;
      }
      // Link current user to base identity (auth0 provider)
      management.users.link(
        { id: baseIdentity.auth0_id }, 
        { provider: userIdentity.provider, user_id: userIdentity.user_id }
      ).then(data => {
        console.log('Link User (2) - Successful');
      }
      ).catch(err => {
        console.log(`Link User (2) - ERROR: ${err}`);
      });
    }
  });
};

Again, thank you so much for attending to my issue. Really appreciated it.

Thanks for sharing that.

To be clear, you’re saying that line 60, api.user.setUserMetadata('external_user_id', results.external.user.id); isn’t working? But everything else fires properly in this action?

Is the external_user_id object showing up null or undefined in the user object after login, or just non-existent?

P.S. This Action could cause some issues for you in the long run by slowing down your login process or hitting the management API rate limit. Actions run on every authentication (including silent auth), and you should not be calling the management API every time a user authenticates.

1 Like

Hey @dan.woda,

Exactly, everything works other than the setUserMetadata line. For the value of results.external.user.id, that will always have a value however the external_user_id userMetadata appears to be non-existent on the auth0 user’s page.

Will take your note into consideration too, appreciate it.

Have you tried setting a console.log there to confirm that the conditional is being triggered?

You can also try using a static value to rule out any issues with the results object.

Hey @dan.woda,

Yup I’ve already done that by adding the following right after the setUserMetadata line:

console.log("[TEST] External User ID: " + results.external.user.id);

I can confirm that there is no issue with the results…external object and the condition is really being run through.

Would you please share the name of your tenant with me in a DM?

Sure no problem, I will DM you.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.