I am building out a Salesforce Connect customer adapter to translate rest responses into external data objects.
To authenticate these requests, I would like to use Oauth 2.0's client credentials flow as a custom auth provider.
While developing the AuthProviderPlugin, I am running into two roadblocks, the required initiate
and getUserInfo
methods.
For the initiate method I am required to return a page reference, but since this is a client credentials flow, I do not need a user to login, I only need to provide the client id, client secret, and access token url.
For the client crendentials flow, what page reference should I return if I do not need a user to login?
I know I need to get the token, but it looks like that should be handled in the handleCallBack
method.
For the getUserInfo
method, I do not have or need user info, I am simply looking to create an auth provider using Oauth 2.0's client credentials flow to use in my Salesforce Connect Adapter.
Can I return an empty Auth.UserData object back to satisfy the required return?
Lastly...
To me it appears the AuthProviderPluginClass and it's custom adapters do not support Oauth 2.0's client credentials flow by design... is this true? If so, what would be the recommended approach to unattended authentication? Open Id Connect?
Code In Progress:
global class myAuthProvider extends Auth.AuthProviderPluginClass {
// Use this URL for the endpoint that the
// authentication provider calls back to for configuration.
// *****Where do I need to redirect to?*****
public String redirectUrl = 'https://REDIRECTWHERE?.COM';
private String clientId;
private String secret;
// Application redirection to the website for
// authentication and authorization.
//*****How is this different from the accessTokenUrl?*****
private String authUrl;
// URI to get the new access token
private String accessTokenUrl;
// Api name for the custom metadata type created for this auth provider.
private String customMetadataTypeApiName = 'myMetaDataApiName';
// Api URL to access the user
// *****I do need to access the user.*****
private String userAPIUrl;
// Version of the user api URL to access data
// *****I do not need user data.*****
private String userAPIVersionUrl;
global String getCustomMetadataType() {
return customMetadataTypeApiName;
}
global PageReference initiate(Map<string,string>
authProviderConfiguration, String stateToPropagate)
{
clientId = authProviderConfiguration.get('Client_Id__c');
secret = authProviderConfiguration.get('Client_Secret__c');
accessTokenUrl = authProviderConfiguration.get('Access_Token_Url_c');
String url = accessTokenUrl + '?grant_type=client_credentials&client_id=' + clientId + '&client_secret=' + secret + '&redirect_uri=' + redirectUrl;
//*****Where do I need to redirect to since this is a client credentials flow, not a user login flow?*****
return new PageReference(url);
}
global Auth.AuthProviderTokenResponse handleCallback(Map<string,string>
authProviderConfiguration, Auth.AuthProviderCallbackState state )
{
// Here, the developer will get the callback with actual protocol.
// Their responsibility is to return a new object called
// AuthProviderTokenResponse.
// This will contain an optional accessToken and refreshToken
clientId = authProviderConfiguration.get('Client_Id__c');
secret = authProviderConfiguration.get('Client_Secret__c');
accessTokenUrl = authProviderConfiguration.get('Access_Token__c');
Http http = new Http();
httpRequest request = new httpRequest();
request.setHeader('Content-Type', 'application/x-www-form-urlencoded');
request.setHeader('Content-Length', '0');
String url = accessTokenUrl;
request.setEndpoint(url);
request.setBody('grant_type=client_credentials&client_id=' + clientId + '&client_secret=' + secret);
request.setMethod('POST');
HttpResponse res = new httpResponse();
system.debug(request);
res = http.send(request);
string getTokenString = res.getBody();
Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(getTokenString);
string getToken;
getToken = String.valueOf(results.get('access_token'));
//*****What if I do not receive/need a refresh token, non-expiring token*****
return new Auth.AuthProviderTokenResponse('myAuthProvider', getToken,
'refreshToken', getToken);
}
//*****I do not need user data, but a UserData Object must be returned. Can I compile with blank data?*****
global Auth.UserData getUserInfo(Map<string,string>
authProviderConfiguration,
Auth.AuthProviderTokenResponse response)
{
//Here the developer is responsible for constructing an
//Auth.UserData object
String token = response.oauthToken;
HttpRequest req = new HttpRequest();
userAPIUrl = authProviderConfiguration.get('API_User_Url__c');
userAPIVersionUrl = authProviderConfiguration.get
('API_User_Version_Url__c');
req.setHeader('Authorization', 'OAuth ' + token);
req.setEndpoint(userAPIUrl);
req.setHeader('Content-Type','application/x-www-form-urlencoded');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
String responseBody = res.getBody();
String id = '';
String fname = '';
String lname = '';
String flname = '';
String uname = '';
String locale = '';
Map<String,String> provMap = new Map<String,String>();
return new Auth.UserData(id, fname, lname, flname, uname, 'what', locale, null, 'myAuthProvider', null, provMap);
}
}
This related questions appears to support the statement that AuthProviderPluginClass does not support the client credentials flow, but I may be misinterpreting: Custom Auth Provider usage for unattended OAuth flow