global class OIDCTrustBuilder implements Auth.RegistrationHandler{ // Function to check if we got mandatories values to create the SF user from the OIDC id_token // Can be adjust if more values from scopes are needed global boolean canCreateUser(Auth.UserData data) { System.debug('*-*-*-* canCreateUser was called for ' + data); Boolean retVal = (data != null && data.attributeMap.get('sub') != null && data.attributeMap.get('family_name') != null && data.attributeMap.get('given_name') != null); // id_token subject is the user email address System.debug('*-*-*-* data.username=' +data.attributeMap.get('sub')); System.debug('*-*-*-* data.lastName=' +data.attributeMap.get('family_name')); System.debug('*-*-*-* data.firstName='+data.attributeMap.get('given_name')); return retVal; } global User createUser(Id portalId, Auth.UserData data){ // data contains OIDC id_token datas User u; // use OIDC id_token sub as Subject String Subject = data.attributeMap.get('sub'); System.debug('*-*-*-* oidc: ' + data); System.debug('*-*-*-* oidc subject: ' + Subject); // Try to find an existing user first List existingUsers = [SELECT UserName FROM User WHERE UserName =: Subject ]; // log matching users nb System.debug('*-*-*-* Nb MatchingUsers : ' + existingUsers.size() ); if (existingUsers.size() > 0) { // matching user found, return user and SF will call the updateUser function System.debug('*-*-*-* 1st matching user : ' + existingUsers[0]); u = existingUsers[0]; } else { // No matching user found, trying to create the user in the SF org System.debug('*-*-*-* No matching user, try creating one'); // Check mandatory values if(!canCreateUser(data)) { // Returning null signals the auth framework we can't create the user System.debug('*-*-*-* Error creating user, check mandatory values'); return null; } // If no user exists, create a new one u = new User(); // OIDC uses given_name, family_name instead of firstName, lastname as the attributes keys with standard scope profile String firstName = data.attributeMap.containsKey('given_name') ? data.attributeMap.get('given_name') : 'NoFirstName'; String lastName = data.attributeMap.containsKey('family_name') ? data.attributeMap.get('family_name') : 'NoLastName'; // /!\ UserName MUST BE UNIQUE ACROSS ALL SalesForce org, so use a username likely unique like email address u.Username = data.attributeMap.containsKey('sub') ? data.attributeMap.get('sub') : 'default@email.com'; u.Email = data.attributeMap.containsKey('sub') ? data.attributeMap.get('sub') : 'default@email.com'; // 'Chatter Free User' profile useful for test' and demos as free SF licence Profile p = [SELECT Id FROM Profile WHERE Name = 'Chatter Free User' LIMIT 1]; // 'Standard User' profile for production or others, consult SF doc // Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1]; u.firstname = firstName ; u.lastname = lastName ; // Use the sub as FederationIdentifier u.FederationIdentifier = data.attributeMap.get('sub'); // Alias logic : 4 1st firstname digit + 4 1st lastname digit (SF mandatory 8 digits) u.Alias = (u.FirstName + u.LastName).length() > 8 ? ((u.firstName).substring(0, 4) + (u.lastName).substring(0, 4)) : (u.firstName + u.lastName); u.ProfileId = p.Id; // Profile Id, see above // Hard coded values, can be adjusted from OIDC scopes u.TimeZoneSidKey = 'GMT'; u.LocaleSidKey = 'fr_FR_EURO'; u.EmailEncodingKey = 'UTF-8'; u.LanguageLocaleKey = 'fr'; // If working with Communities or Portals, you might also need to set additional fields like AccountId, ContactId, etc. insert u; } return u; } // Called by the SF Auth Framework global void updateUser(Id userId, Id portalId, Auth.UserData data){ User u = [SELECT Id, Email, FirstName, LastName, ProfileId, alias, Username, FederationIdentifier FROM User WHERE Email =: data.attributeMap.get('sub') LIMIT 1]; System.debug('*-*-*-* Selected existing user: ' + u); // 'Chatter Free User' profile useful for test' and demos as free SF licence // 'Standard User' profile for production // Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1]; // Profile p = [SELECT Id FROM Profile WHERE Name = 'Chatter Free User' LIMIT 1]; System.debug('*-*-*-* data: ' + data); System.debug('*-*-*-* subject from idtoken: ' + data.attributeMap.get('sub')); System.debug('*-*-*-* firstname from idtoken: ' + data.attributeMap.get('firstname')); System.debug('*-*-*-* lastname from idtoken: ' + data.attributeMap.get('lastname')); // Update the user's informations based on the incoming data // Can be adjusted from OIDC scopes // use idtoken sub as email adress u.UserName = data.attributeMap.containsKey('sub') ? data.attributeMap.get('sub') : u.Email; u.FirstName = data.attributeMap.containsKey('firstname') ? data.attributeMap.get('firstname') : u.FirstName; u.LastName = data.attributeMap.containsKey('lastname') ? data.attributeMap.get('lastname') : u.LastName; // u.ProfileId = p.Id; // Update other fields as necessary System.debug('*-*-*-* Updated user: ' + u); update u; } }