A customer reached out the other day as they were unable to make Azure Active Directory B2C work with Salesforce for single-sign-on using OpenID Connect (OIDC). They were seeing a
No_Oauth_Token error and couldn’t make it work so they asked if I would look into it.
After spending a bit of time I was able to make it work. First step was to add the Application ID of the app in Azure as a scope in the Auth. Provider configuration in Salesforce. That removed the
No_Oauth_Token error but the authentication to Salesforce still failed. The reason was that Salesforce was attempting to reach our the
userinfo-endpoint which wasn’t specified as a
userinfo-endpoint is not provided by Azure Active Directory B2C when using a standard policy (a policy is how the authentication flow is configured on the Azure side). As no
userinfo-endpoint was provided the solution I came up with was to build a small simple web application that could be a stand-in for that missing endpoint.
userinfo endpoint of the web app is called from Salesforce after the user has been authenticated through Azure Active Directory B2C but before the user is let into Salesforce. Salesforce will provide a Bearer token in the
Authorization header. The Bearer token is the signed JWT from Azure Active Directory B2C.
The endpoint will do the following:
- Verify the signature of the JWT by getting the key ID (
kid) from the JWT header, then attempt to find the key among the keys loaded from the
.wellknownendpoint in Azure when the app started, extract the public key if found and verify the JWT signature
- Once the signature has been verified it returns a JSON response with a single claim being the subject identifier (
- The Registration Handler on the Salesforce side can then use this subject identifier to lookup the User record in Salesforce and return it to complete the authentication
The web app is available in a repo on Github (https://github.com/lekkimworld/userinfo-endpoint-for-salesforce-with-azure-ad-b2c). The repo also contains a sample Registration Handler.
6 thoughts on “Single Sign On to Salesforce with Azure Active Directory B2C as the IdP through OpenID Connect”
Hey Mikkel, finding your posts on Azure AD and Salesforce SSO very helpful in working though some issues in my implementation. My question, while not specific to this topic, is whether you have tackled how to map non-default or custom fields from Azure AD to Salesforce as part of a regular OIDC based SSO setup. I am finding that no matter what I specify for scopes or add via custom claims, the attributes passed to the reg handler never vary. Specifically I am looking at how to obtain the object ID (OID) for a user for use within the reg handler. I expected it to be in the attributemap, but it seems to only ever contain the same six attribute/values, i.e. sub, name, given_name, family_name, picture, email. Using Microsoft auth provider, v2.0 endpoints, scopes = openid, email, profile.
Any thoughts are appreciated.
It being a while since I looked into it I think there are two things in play here. The scopes you specify in the Auth. Handler define what an access token issued as part of the authentication process access. The claims passed from Azure AD to Salesforce is another thing – they are probably standard claims that can be overridden on the Azure AD side just like we can pass custom claims (we call them custom attributes) from a Connected App on the Salesforce side.
To do what you mention I think you need to either 1) customize the claims that Azure AD sends to Salesforce after a successful login to Azure AD or 2) reach back to Azure AD from the Auth Provider on Salesforce using the access token. I do not seem to remember the access token being exposed to an Auth Provider nor that an access token is even issued fore a pure OIDC (OpenID Connect) login process. I think only an id_token is sent which would bring you back to point 1 above.
Does that make sense?
Thinking a bit more about this there must be an access token as Salesforce always reach back to talk to the userinfo endpoint. How much of that it parses and passes in the attributes map I cannot remember.
Thanks for the quick response! Yes, there is definitely an access token, and the ID token gets issued when you include the openid scope. The issue as I described earlier is that it appears that the auth provider itself (either Microsoft or Open ID), using the AuthProviderPluginClass does not seem to vary in what it pulls from the tokens or userinfo endpoints. If I could find a copy of the code those auth providers use I might be able to figure it out – trying to avoid writing a custom one.
Also, if you are looking for a challenging blog entry, try getting Azure AD provisioning via SCIM to Salesforce working with OIDC based SSO. SCIM and SAML works great, SCIM and OIDC, not so much.
Writing your own Auth Provider is actually easier than what you might think. Worst part will be parsing the response and potentially verifying the signature on the id_token as we (Salesforce) have no support for JKS built in. But the core Auth Provider is quite easy.
Please elaborate on the SCIM provision with OIDC issues.
So the issue with SCIM and OIDC comes down to some inflexibility on both the Azure and Salesforce sides. Here is the gist of it:
1. When you setup Salesforce in Azure AD for automatic provisioning, you are effectively pointing at the Salesforce user management API and creating users there from Azure AD user attributes via mappings. In setting up these mappings you have to choose a unique identifier for establishing and maintaining the connection between the two – the primary choices on the Azure side are Object ID (OID) or User Principal Name (UPN). The target on the salesforce side is ID, username or federation ID. A typical match for SAML would be OID to Federation ID or UPN to username. Regardless of what combo you pick a user is provisioned in Salesforce that will continue to receive updates from Azure AD when something changes.
2. For SSO between the two, if you choose SAML you can specify in the Salesforce Auth provider configuration to use the username or federation ID as the unique ID, and SSO into a provisioned account will work fine. When you setup OIDC for SSO in Salesforce you do not have a choice on the unique identifier, it takes the value passed in the login from the SUB claim and uses it to find an existing user or create one using the ThirdPartyAccountLink object, which is attached to a user object – this is a protected object, not readily visible. The createuser and updateuser methods in the reg handlers perform the creation/updates but the initial lookup of the user via ThirdPartyAccountLink seems fixed.
3. The sub claim sent by Azure AD to Salesforce is a calculated value (pairwise hash of app ID and user OID), and while it is immutable it is also application specific – same user accesses two different apps, they will have two different sub values, whereas OID for a user stays the same. There does not appear to be a way to alter what Azure sends in the Sub claim, you can’t switch it to hold the OID, although the OID is also sent in the access and ID tokens as a separate claim.
1. There is no option in Azure AD provisioning to use the sub as the source value for the unique identifier, it simply isn’t an mapping option in the list of source attributes.
2. There is no option to specify the ThirdPartyAccountLink object or one of its fields as a target in Salesforce for the unique ID.
IOW you cannot provision a user in Salesforce from Azure AD using the sub, and when you login via OIDC SSO Salesforce only looks at the sub to find a matching user – so you can guess what happens, it never finds the provisioned user and wants to create a new one using the sub to populate the ThirdPartyAccountLink object.
I do believe however if I were able to get the OID from the auth provider I could pre-empt a create in the reg handler by doing a search on that first, and force an update on the existing user object.
Now, I am a bit of a noob here on the salesforce side, but I have extensive experience on the Azure AD side, and I feel if anyone can figure out how this might work, I suspect it will be via some customization within Salesforce, and not in Azure. That is unless someone can convince Microsoft that they should include sub in their attribute mappings, which I find mildly infuriating given their rigid stance on its use in OIDC.
Any help is appreciated.