When doing integrations from Salesforce you sometimes need to do this using single identity instead as in the context of the current user. Salesforce supports both through Named Credentials i.e. both working as the current user or as a Named Principal. Through the Named Credentials and Auth. Provider concepts from Salesforce you can setup a connection between Salesforce and an OAuth 2.0 enabled endpoint such as Microsoft Azure i.e. if needing the access the Microsoft Graph API. This can however be an issue as the target system (i.e. Microsoft Azure in this case) may not use a user that is able to login or you may not want to use a user license in the target system.
In this case you might want to use the
client_credentials OAuth flow as that identifies the caller using a
client_id and a
client_secret instead of a user id. Unfortunately that OAuth flow type is not supported out-of-the-box with Salesforce. The reason is that the Auth. Providers in Salesforce usually deals with a user behind the keyboard. Due to this the OAuth 2.0 Auth. Provider flows was designed for a
refresh_token World. This makes it impossible to use the built in capabilities for the
The solution is to write a custom Auth. Provider in Apex and use that from your Named Credential. To make this easier I’ve already implemented this for you. The code is available on Github in my salesforce-azure-clientcredentials-authprovider repo. This implementation just plays along with the
refresh_token requirements and just requests a new
access_token using the
client_credentials flow whenever a (new)
access_token is needed and hence do not need a user behind the keyboard.
README.md in the repo has instructions for installing and configuring the code in the org. Once deployed you can create an Auth. Provider and a Named Credential. Please note you also need to create an App Registration in Azure Active Directory with the required Application Permissions.
11 thoughts on “Custom Salesforce Auth. Provider for Microsoft Azure client_credentials flow”
Hi Mikkel – thanks for the Code and the video walkthrough. But we now have an issue where the token expires after 2hrs (session setting) – response returns 403 – but the refresh AccessToken method is never called – which seems to be part under the hood in the generateRefreshToken method . is this related to this limitation – https://ideas.salesforce.com/s/idea/a0B8W00000GdY4PUAV/custom-oauth-authentication-provider-should-call-refresh-on-receiving-http-403
Hmmm that’s interesting. And this is against regular Azure AD right? Let me know and I’ll try and ask internally about this.
Reading a bit on this at microsoft.com (https://docs.microsoft.com/en-us/graph/resolve-auth-errors) it seems more like a mismatch of permissions might be happening. You say this happens after the initially obtained access_token expires based on session settings on the Azure side? If yes do you have some steps to reproduce?
Hi Mikkel. This code worked for me! Thanks for posting it. However, I’m now seeing the same issue as Martin, except our OAuth Token provider is PingFed. It seems as though the refresh() method is not retrieving and/or appending the refreshed token. Or perhaps refresh() isn’t getting called at all. At any rate, the message is a 500 access token expired. Any idea whats going on behind the scenes with the refresh method?
Also, I can’t find a way to get the code to generate a debug log since it’s not called imperatively by a user. Seems to run a bit farther beneath the hood than what is accessible to Trace Flags.
Best guess is that Salesforce only calls the refresh method if using an access token and the server returns a 401 Unauthenticated. 500 is an error so guess that’s why refresh is not called. But again my guess is that we only call refresh for a 401.
Maybe it’s due to missing arguments? Haven’t tried Ping but maybe worth looking into.
I think this is probably the issue, what do you think Mikkel?
P.S. by ‘refreshed token’ I actually mean new access token, as you described above.
Yes, The Named credentials will only invoke the refresh flow on receiving the 401 error code. As Paul mentioned there is an idea on this to invoke the refresh flow on 403 error code.