I have a Rest API Endpoint that I am looking to callout to.
Something like this: https://myrestservice.com/service/logs
When setting up the Named Credential,
I set up the URL to: https://myrestservice.com/service/logs
I select OAuth 2.0 as my provider and I am then prompted to select an authentication provider.
I have not created one and when I go to create one and I am provided a list of pre-defined provider types, none of which are my providers.
The documentation indicates I need to create a custom metadata type: https://help.salesforce.com/articleView?id=sso_provider_plugin_custom.htm&type=5#plugin_build
I create the MetaData Type myProvider and add two custom fields to store the Client Id, Client Secret, and Access Token.
Documentation then indicates I need to create a class that extends Auth.AuthProviderPluginClass. Documentation here: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_class_Auth_AuthProviderPluginClass.htm#apex_class_Auth_AuthProviderPluginClass
In my custom provider, I generate an access token, and I am provided with the following:
- Access Token URL
- Client ID
- Client Secret
What I am struggling with is how this translates to the example provided within the documentation that I have provided below..
Upon setting the customMetaDataTypeApiName private string to my metadata api name, I see the fields appear in the auth provider configuration page.
However, in the flow I am working with, there does not appear to be a need for the initiate method, but it is required. Am I misunderstanding Oauth, does a user need to log in to receive a token back? Or can I just pass the clientid, clientsecret, and grant type and received a response?
I tried getting the token response by an HTTP GET Request, but I am returned with a 403 forbidden error.
15:57:02:002 CALLOUT_REQUEST [5]|System.HttpRequest[Endpoint=https://myendpoint.com/token?grant_type=oauth2&client_id=myClientId12345&client_secret=lknvdas898-89fdn-fdnfdb, Method=GET]
global class Concur extends Auth.AuthProviderPluginClass {
// Use this URL for the endpoint that the
// authentication provider calls back to for configuration.
public String redirectUrl;
private String key;
private String secret;
// Application redirection to the Concur website for
// authentication and authorization.
private String authUrl;
// URI to get the new access token from concur using the GET verb.
private String accessTokenUrl;
// Api name for the custom metadata type created for this auth provider.
private String customMetadataTypeApiName;
// Api URL to access the user in Concur
private String userAPIUrl;
// Version of the user api URL to access data from Concur
private String userAPIVersionUrl;
global String getCustomMetadataType() {
return customMetadataTypeApiName;
}
global PageReference initiate(Map<string,string>
authProviderConfiguration, String stateToPropagate)
{
authUrl = authProviderConfiguration.get('Auth_Url__c');
key = authProviderConfiguration.get('Key__c');
// Here the developer can build up a request of some sort.
// Ultimately, they return a URL where we will redirect the user.
String url = authUrl + '?client_id='+ key +'&scope=USER,EXPRPT,LIST&redirect_uri='+ redirectUrl + '&state=' + stateToPropagate;
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
key = authProviderConfiguration.get('Key__c');
secret = authProviderConfiguration.get('Secret__c');
accessTokenUrl = authProviderConfiguration.get('Access_Token_Url__c');
Map<String,String> queryParams = state.queryParameters;
String code = queryParams.get('code');
String sfdcState = queryParams.get('state');
HttpRequest req = new HttpRequest();
String url = accessTokenUrl+'?code=' + code + '&client_id=' + key +
'&client_secret=' + secret;
req.setEndpoint(url);
req.setHeader('Content-Type','application/xml');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
String responseBody = res.getBody();
String token = getTokenValueFromResponse(responseBody, 'Token', null);
return new Auth.AuthProviderTokenResponse('Concur', token,
'refreshToken', sfdcState);
}
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/xml');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
String responseBody = res.getBody();
String id = getTokenValueFromResponse(responseBody,
'LoginId',userAPIVersionUrl);
String fname = getTokenValueFromResponse(responseBody,
'FirstName', userAPIVersionUrl);
String lname = getTokenValueFromResponse(responseBody,
'LastName', userAPIVersionUrl);
String flname = fname + ' ' + lname;
String uname = getTokenValueFromResponse(responseBody,
'EmailAddress', userAPIVersionUrl);
String locale = getTokenValueFromResponse(responseBody,
'LocaleName', userAPIVersionUrl);
Map<String,String> provMap = new Map<String,String>();
provMap.put('what1', 'noidea1');
provMap.put('what2', 'noidea2');
return new Auth.UserData(id, fname, lname, flname,
uname, 'what', locale, null, 'Concur', null, provMap);
}
private String getTokenValueFromResponse(String response,
String token, String ns)
{
Dom.Document docx = new Dom.Document();
docx.load(response);
String ret = null;
dom.XmlNode xroot = docx.getrootelement() ;
if(xroot != null){ ret = xroot.getChildElement(token, ns).getText();
}
return ret;
}
}