Account linking through Actions

Hi,

lately I’ve been struggling with simple email account linking. I’ve tried the “Account Linking Extension”, but it turned out not to be an option for my use case, because it requires the user to manually confirm the linking though the UI.

Then I tried to use actions (specifically the “Post login” flow) and it indeed links accounts with the same email addresses, but then it returns the original user id.

For example:

  1. I sign in with GitHub (github|...)
  2. I sign out
  3. I sign in again in with username/password (auth0|...)
  4. Action correctly links the profile, but it returns the original username/password identity (auth0|...). I’d expect to receive the primary user id (github|...)

To link accounts I used the /link endpoint in the Management API v2.

Is there a way how to return the primary user id after account linking?

2 Likes

Hello,
It is important to set the event.user object to the primary user after the accounts have been linked in the action. Doing this should return the github user_id as a sub claim in the id_token/access token you receive to your application.

Below is a similar post that could be helpful.

Let us know how it goes!

1 Like

Hi Praveen, thank you for your reply :slight_smile:

I’ve tried this before, but now I tried it again just in case I was doing something wrong. Unfortunately it seems that neither assigning to the event.user or returning it has any effect whatsoever.

I think this was possible before, but now it seems it’s not possible (at least according to your docs):

I’ll share the full code of the action just in case I’m doing something wrong:

Thank you.

Hi there.

This does not seem to be working.

I set event.user=murgedUser however, the access token still contains the original users data :frowning:

I’m not sure if I’'m reading this correctly, but I assume this means, that it is no longer possible to edit the access token without the use of api.command or am I missing something?

Hi Praveen,

I’m having the same problem and have tried set the event.user. But this does not seem to be working.
Is there an official way to link accounts in ‘Post Login’?

Below is my full code of the action
Example

Best regards,

Hey @praveen.addepally!

Would you be able to follow-up on that?

I’ve the same problem. Has someone found a solution for this problem already?

thx

Hi @konrad.sopala, can you follow this? I have been stuck on this problem for a while but no solution yet

I have a similar use case that the one explained in this thread and in this one.

I’ve been struggling to plan a working solution for the automatic linking of accounts matching their emails. I’ve read all across the docs that this isn’t supported by any consolidated auth0 solutions but I still don’t get why.

Our platforms are for internal use so all users will use our enterprise email addresses and I still can’t think of any worst-case scenario where automatically linking accounts by email can be a risk in any way possible. Sadly we can’t easily integrate this because the account linking extension doesn’t support passwordless connections.

We were very excited when we discovered that action flows might be useful, but when reading the docs it seems that Account Linking isn’t supported!

I guess I’ll try to create a serverless function somewhere that hit the management API to link accounts when necessary, triggered by the post-login trigger, as briefly described here.

Anybody know if this would work? Any alternatives?

The fact that you cannot log the user in as his/her actual account after linking it makes it impossible for us to migrate from rules to actions, is fixing this on the roadmap, or are you going to revert the deprecated state of rules?

For anyone else currently panicking about rules being deprecated, hold off on trying to migrate until this is resolved. The implementation below works, except the user is still being logged in as the account linked into the actual account. e.g. setting event.user or returning the new user is not working.

We ended up rolling back to using the old rules system for now.

exports.onExecutePostLogin = async (event, api) => {
  
	const ManagementClient = require("auth0").ManagementClient;
  const DOMAIN = event.secrets.DOMAIN;
	const CLIENT_ID = event.secrets.CLIENT_ID;
	const CLIENT_SECRET = event.secrets.CLIENT_SECRET;

	const management = new ManagementClient({
		domain: DOMAIN,
		clientId: CLIENT_ID,
		clientSecret: CLIENT_SECRET
	});

	let currentAccount = event.user;

  let accounts = await management.getUsersByEmail(currentAccount.email);
  
  if (accounts.length > 2) {
    api.access.deny("[!] Rule: Multiple user profiles already exist - cannot select base profile to link with");
    return;
  } else if (accounts.length === 0) {
    api.access.deny("Unable to login, please contact support if this problem persists");
    return;
  } else if (accounts.length === 2) {
    
    const sorted = accounts.sort((a,b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
    const originalUser = sorted[0];
    const newUser = sorted[1];

    // logging back in with original user, confirm
    if (currentAccount.user_id === originalUser.user_id) {
      return;
    }

    // one profile exists, new one is not yet verified
    if (!originalUser.email_verified || !newUser.email_verified) {
      api.access.deny("Please verify your email before logging in.");
      return;
    }

    const provider = newUser.identities[0].provider;
    const providerUserId = newUser.identities[0].user_id;

    originalUser.identities = await management.linkUsers(originalUser.user_id, {
      provider: provider,
      user_id: providerUserId
    });

    event.user = originalUser;
    
    return {
      user: originalUser
    }
  }
};
1 Like

Hi @leif.ross,
May you please share your rules that do account linking?

Sure thing, it looks more or less like this.

function (user, context, callback) {
  const request = require('request');
  
  const userApiUrl = auth0.baseUrl + '/users';
  const userSearchApiUrl = auth0.baseUrl + '/users-by-email';

  request({
    url: userSearchApiUrl,
    headers: {
      Authorization: 'Bearer ' + auth0.accessToken
    },
    qs: {
      email: user.email
    }
  },
  function(err, response, body) {
    if (err) return callback(err);
    if (response.statusCode !== 200) return callback(new Error(body));

    const data = JSON.parse(body);

    if (data.length === 1) {
      return callback(null, user, context);
    }
    
    if (data.length === 0) {
      return callback(new UnauthorizedError('Unable to login, please contact support if this problem persists'));
    }

    if (data.length > 2) {
      return callback(new Error('[!] Rule: Multiple user profiles already exist - cannot select base profile to link with'));
    }

    // Find original and new user
    const sorted = data.sort((a,b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
    const originalUser = sorted[0];
    const newUser = sorted[1];

    // logging back in with original user, confirm
    if (user.user_id === originalUser.user_id) {
      return callback(null, user, context);
    }

    // one profile exists, new one is not yet verified
    if (!originalUser.email_verified || !newUser.email_verified) {
      return callback(new UnauthorizedError('Please verify your email before logging in.'));
    }
 
    const provider = newUser.identities[0].provider;
    const providerUserId = newUser.identities[0].user_id;

    request.post({
      url: userApiUrl + '/' + originalUser.user_id + '/identities',
      headers: {
        Authorization: 'Bearer ' + auth0.accessToken
      },
      json: {
        provider: provider,
        user_id: String(providerUserId)
      }
    }, function(err, response, body) {
      if (response.statusCode >= 400) {
        return callback(new Error('Error linking account: ' + response.statusMessage));
      }
      context.primaryUser = originalUser.user_id;
      callback(null, user, context);
    });
  });
}
3 Likes

Thanks for sharing that with the rest of community!

konrad.sopala
May you say what is ETA of support Account Linking in actions is?
Because now seems it doesn’t work because of limitations

Hey there!

Let me follow up with the team on that front!

1 Like

@konrad.sopala any updates about this?

1 Like

Hi @konrad.sopala
Is there a recommended solution to this problem?

1 Like

Same issue encountered in my current test, confirmed with a Auth0 engineer, he replied:

Similarly, I reviewed your post login flow and I see that you tried to change the event.user to target user. I don’t think this would ever work, since Auth0 has already processed the identity that was used to log in. event.user would only persist as long as you are in the Actions webtask (container running the script).

Which means, in this scenario updating event.user won’t work.

The suggestion was to “redirect the user to another page and log them out to let them log in again”:

I double checked with my teammates and I’m not sure there is a way to avoid showing the profile of the user that just logged in. The management API call in Rules happens but Auth0 has already processed the user credentials entered. So you would have to log the user back in somehow in order to force the linked accounts to work.

Typically, our customers redirect a user out of Rules/Actions and present them with a page asking them to link accounts. So that gives the system time to process the link and when the user logs in again, their primary account is used. But I know that you guys are trying to give your users a streamlined experience so you would route the user to another login, at least for the first time their account is linked.

1 Like

I do not understand how this is a viable solution, it’s literally asking thousands of customers to redo their entire authorization flow. The old rules system should not be deprecated before this is fixed, if not we need an actual warning in due time so we can start migrating to a competitor.

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