Migrating Azure B2C/AAD Users Into Auth0

A lot of customers use Auth0’s Azure AD Enterprise Connector to federate their users into Auth0, but what if they want to migrate those users and their credentials in to be managed by Auth0? As of now Azure AD does not allow for exporting of passwords, so the easiest way to do this is through the use of Azure AD ROPC flow in Auth0’s Custom DB login scripts.

Prerequisites

  1. Both of these flows require creating an app registration in the respective tenants or directories that they apply to. This is where you will get client ids and client secrets that will be used in the API calls. Make sure to register applications as public clients, and that they gain access to the correct directories that apply to your sign in use case. You will see the below radio selection when creating the app registration:

image

  1. Additionally, grant the below API permissions to the app registration.

  1. (Only in B2C flows). B2C requires an extra step (creating the B2C user flow) which can be found here. The name of this flow will go in the endpoint URL in the B2C script below denoted by {{B2C_POLICY_NAME}}.

Once these steps are completed, your tenant or directory should be ready to accept ROPC calls.

Azure AD Login Script Template:

function login(email, password, callback) {
  const axios = require('axios').default;
  const qs = require('qs');
	const jwtDecode = require('jwt-decode').default;
  
   const data = { 'client_id': {{YOUR_AZURE_APP_CLIENT_ID}},
                 'scope': 'user.read openid profile offline_access',
                 'username': email,
                 'password': password,
                 'grant_type': 'password',
                 'client_secret': {{YOUR_AZURE_APP_CLIENT_SECRET}}
                 };
    const options = {
      method: 'POST',
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      data: qs.stringify(data),
      url: 'https://login.microsoftonline.com/{{AZURE_TENANT_ID}}/oauth2/v2.0/token',
    };
  
    axios(options).then(
    	response => {
      // in a production setting it would be best to validate this JWT fully before reading claims
      const claims = jwtDecode(response.data.id_token);
        
      if (response.statusCode === 401) return callback();
        
      callback(null, {
      // this is a simple example of properties that can be mapped back to the auth0 user profile
      // you are free to choose exactly what maps back over based on what data you get from the azure token
      user_id: claims.sub,
      nickname: claims.name,
      email: claims.preferred_username
    });
      
    }
  );


}

B2C Login Script Template:

function login(email, password, callback) {
  const axios = require('axios').default;
  const qs = require('qs');
	const jwtDecode = require('jwt-decode').default;
  
   const data = { 'client_id': {{YOUR_B2C_APP_CLIENT_ID}},
                 'scope': 'openid {{YOUR_B2C_APP_CLIENT_ID}} offline_access profile',
                 'username': email,
                 'password': password,
                 'grant_type': 'password',
                 };
    const options = {
      method: 'POST',
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      data: qs.stringify(data),
      url: 'https://{{TENANT_NAME}}.b2clogin.com/{{TENANT_NAME}}.onmicrosoft.com/{{B2C_POLICY_NAME}}/oauth2/v2.0/token',
    };
  
    axios(options).then(
    	response => {
      // in a production setting it would be best to validate this JWT fully before reading claims
      const claims = jwtDecode(response.data.access_token);
      if (response.statusCode === 401) return callback();
      
      callback(null, {
      // this is a simple example of properties that can be mapped back to the auth0 user profile
      // you are free to choose exactly what maps back over based on what data you get from the azure token
      user_id: claims.sub,
      nickname: claims.given_name,
      email: claims.emails[0]
    });
    }
  );
}
1 Like

Thanks a lot for sharing all that @zac.burrage !

1 Like