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.
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:
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)
Set userMetadata for the event.user if equivalent user was found in the external database
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.
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.
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.