Newer Version Available
AuthProviderPlugin Interface
Namespace
Usage
To create a custom authentication provider for single sign-on, create a class that implements Auth.AuthProviderPlugin. This class allows you to store the custom configuration for your authentication provider and handle authentication protocols when users log in to Salesforce with their login credentials for an external service provider. In Salesforce, the class that implements this interface appears in the Provider Type drop-down list in Auth. Providers in Setup. Make sure that the user you specify to run the class has “Customize Application” and “Manage Auth. Providers” permissions.
AuthProviderPlugin Methods
The following are methods for AuthProviderPlugin.
getCustomMetadataType()
Signature
public String getCustomMetadataType()
Usage
The getCustomMetatadaType() method returns only custom metadata type names. It does not return custom metadata record names.
getUserInfo(authProviderConfiguration, response)
Signature
public Auth.UserData getUserInfo(Map<String,String> authProviderConfiguration, Auth.AuthProviderTokenResponse response)
Parameters
- authProviderConfiguration
- Type: Map<String,String>
- The configuration for the custom authentication provider. When you create a custom metadata type in Salesforce, the configuration populates with the custom metadata type default values. Or you can set the configuration with values you enter when you create the custom provider in Auth. Providers in Setup.
- response
- Type: Auth.AuthProviderTokenResponse
-
The OAuth access token, OAuth secret or refresh token, and state provided by the authentication provider to authenticate the current user.
handleCallback(authProviderConfiguration, callbackState)
Signature
public Auth.AuthProviderTokenResponse handleCallback(Map<String,String> authProviderConfiguration, Auth.AuthProviderCallbackState callbackState)
Parameters
- authProviderConfiguration
- Type: Map<StringString>
- The configuration for the custom authentication provider. When you create a custom metadata type in Salesforce, the configuration populates with the custom metadata type default values. Or you can set the configuration with values you enter when you create the custom provider in Auth. Providers in Setup.
- callbackState
- Type: Auth.AuthProviderCallbackState
- The class that contains the HTTP headers, body, and queryParams of the authentication request.
Return Value
Type: Auth.AuthProviderTokenResponse
Creates an instance of the AuthProviderTokenResponse class.
initiate(authProviderConfiguration, stateToPropagate)
Signature
public System.PageReference initiate(Map<String,String> authProviderConfiguration, String stateToPropagate)
Parameters
- authProviderConfiguration
- Type: Map<StringString>
- The configuration for the custom authentication provider. When you create a custom metadata type in Salesforce, the configuration populates with the custom metadata type default values. Or you can set the configuration with values you enter when you create the custom provider in Auth. Providers in Setup.
- stateToPropagate
- Type: String
- The state passed in to initiate the authentication request for the user.
Return Value
Type: System.PageReference
The URL of the page where the user is redirected for authentication.
AuthProviderPlugin Example Implementation
This is an example implementation of the Auth.AuthProviderPlugin interface.
The following example tests the implementation:
1global class Concur implements Auth.AuthProviderPlugin {
2
3 // Use this URL for the endpoint that the
4 // authentication provider calls back to for configuration
5 public String redirectUrl;
6 private String key;
7 private String secret;
8
9 // Application redirection to the Concur website for
10 // authentication and authorization
11 private String authUrl;
12
13 // URI to get the new access token from concur using the GET verb
14 private String accessTokenUrl;
15
16 // Api name for the custom metadata type created for this auth provider
17 private String customMetadataTypeApiName;
18
19 // Api url to access the user in concur
20 private String userAPIUrl;
21
22 // Version of the user api url to access data from concur
23 private String userAPIVersionUrl;
24
25 global String getCustomMetadataType() {
26 return customMetadataTypeApiName;
27 }
28
29 global PageReference initiate(Map<string,string>
30 authProviderConfiguration, String stateToPropagate)
31 {
32 authUrl = authProviderConfiguration.get('Auth_Url__c');
33 key = authProviderConfiguration.get('Key__c');
34 //Here the developer can build up a request of some sort
35 //Ultimately they’ll return a URL where we will redirect the user
36 String url = authUrl + '?client_id='+ key +'&scope=USER,EXPRPT,LIST&redirect_uri='+ redirectUrl + '&state=' + stateToPropagate;
37 return new PageReference(url);
38 }
39
40 global Auth.AuthProviderTokenResponse handleCallback(Map<string,string>
41 authProviderConfiguration, Auth.AuthProviderCallbackState state )
42 {
43 //Here, the developer will get the callback with actual protocol.
44 //Their responsibility is to return a new object called
45 //AuthProviderToken
46 //This will contain an optional accessToken and refreshToken
47 key = authProviderConfiguration.get('Key__c');
48 secret = authProviderConfiguration.get('Secret__c');
49 accessTokenUrl = authProviderConfiguration.get('Access_Token_Url__c');
50
51 Map<String,String> queryParams = state.queryParameters;
52 String code = queryParams.get('code');
53 String sfdcState = queryParams.get('state');
54
55 HttpRequest req = new HttpRequest();
56 String url = accessTokenUrl+'?code=' + code + '&client_id=' + key +
57 '&client_secret=' + secret;
58 req.setEndpoint(url);
59 req.setHeader('Content-Type','application/xml');
60 req.setMethod('GET');
61
62 Http http = new Http();
63 HTTPResponse res = http.send(req);
64 String responseBody = res.getBody();
65 String token = getTokenValueFromResponse(responseBody, 'Token', null);
66
67 return new Auth.AuthProviderTokenResponse('Concur', token,
68 'refreshToken', sfdcState);
69 }
70
71
72 global Auth.UserData getUserInfo(Map<string,string>
73 authProviderConfiguration,
74 Auth.AuthProviderTokenResponse response)
75 {
76 //Here the developer is responsible for constructing an
77 //Auth.UserData object
78 String token = response.oauthToken;
79 HttpRequest req = new HttpRequest();
80 userAPIUrl = authProviderConfiguration.get('API_User_Url__c');
81 userAPIVersionUrl = authProviderConfiguration.get
82 ('API_User_Version_Url__c');
83 req.setHeader('Authorization', 'OAuth ' + token);
84 req.setEndpoint(userAPIUrl);
85 req.setHeader('Content-Type','application/xml');
86 req.setMethod('GET');
87
88 Http http = new Http();
89 HTTPResponse res = http.send(req);
90 String responseBody = res.getBody();
91 String id = getTokenValueFromResponse(responseBody,
92 'LoginId',userAPIVersionUrl);
93 String fname = getTokenValueFromResponse(responseBody,
94 'FirstName', userAPIVersionUrl);
95 String lname = getTokenValueFromResponse(responseBody,
96 'LastName', userAPIVersionUrl);
97 String flname = fname + ' ' + lname;
98 String uname = getTokenValueFromResponse(responseBody,
99 'EmailAddress', userAPIVersionUrl);
100 String locale = getTokenValueFromResponse(responseBody,
101 'LocaleName', userAPIVersionUrl);
102 Map<String,String> provMap = new Map<String,String>();
103 provMap.put('what1', 'noidea1');
104 provMap.put('what2', 'noidea2');
105 return new Auth.UserData(id, fname, lname, flname,
106 uname, 'what', locale, null, 'Concur', null, provMap);
107 }
108
109 private String getTokenValueFromResponse(String response,
110 String token, String ns)
111 {
112 Dom.Document docx = new Dom.Document();
113 docx.load(response);
114 String ret = null;
115
116 dom.XmlNode xroot = docx.getrootelement() ;
117 if(xroot != null){
118 ret = xroot.getChildElement(token, ns).getText();
119 }
120 return ret;
121 }
122
123 }Sample Test Classes
The following example contains test classes for the Concur class.
1@IsTest
2 public class ConcurTestClass {
3
4 private static final String OAUTH_TOKEN = 'testToken';
5 private static final String STATE = 'mocktestState';
6 private static final String REFRESH_TOKEN = 'refreshToken';
7 private static final String LOGIN_ID = 'testLoginId';
8 private static final String USERNAME = 'testUsername';
9 private static final String FIRST_NAME = 'testFirstName';
10 private static final String LAST_NAME = 'testLastName';
11 private static final String EMAIL_ADDRESS = 'testEmailAddress';
12 private static final String LOCALE_NAME = 'testLocalName';
13 private static final String FULL_NAME = FIRST_NAME + ' ' + LAST_NAME;
14 private static final String PROVIDER = 'Concur';
15 private static final String REDIRECT_URL =
16 'http://localhost/services/authcallback/orgId/Concur';
17 private static final String KEY = 'testKey';
18 private static final String SECRET = 'testSecret';
19 private static final String STATE_TO_PROPOGATE = 'testState';
20 private static final String ACCESS_TOKEN_URL =
21 'http://www.dummyhost.com/accessTokenUri';
22 private static final String API_USER_VERSION_URL =
23 'http://www.dummyhost.com/user/20/1';
24 private static final String AUTH_URL =
25 'http://www.dummy.com/authurl';
26 private static final String API_USER_URL =
27 'www.concursolutions.com/user/api';
28
29 // In the real world scenario, the key and value would be read
30 // from the (custom fields in) custom metadata type record
31 private static Map<String,String> setupAuthProviderConfig ()
32 {
33 Map<String,String> authProviderConfiguration = new Map<String,String>();
34 authProviderConfiguration.put('Key__c', KEY);
35 authProviderConfiguration.put('Auth_Url__c', AUTH_URL);
36 authProviderConfiguration.put('Secret__c', SECRET);
37 authProviderConfiguration.put('Access_Token_Url__c', ACCESS_TOKEN_URL);
38 authProviderConfiguration.put('API_User_Url__c',API_USER_URL);
39 authProviderConfiguration.put('API_User_Version_Url__c',
40 API_USER_VERSION_URL);
41 authProviderConfiguration.put('Redirect_Url__c',REDIRECT_URL);
42 return authProviderConfiguration;
43
44 }
45
46 static testMethod void testInitiateMethod()
47 {
48 String stateToPropogate = 'mocktestState';
49 Map<String,String> authProviderConfiguration = setupAuthProviderConfig();
50 Concur concurCls = new Concur();
51 concurCls.redirectUrl = authProviderConfiguration.get('Redirect_Url__c');
52 PageReference expectedUrl = new PageReference(authProviderConfiguration.get('Auth_Url__c') + '?client_id='+
53 authProviderConfiguration.get('Key__c') +'&scope=USER,EXPRPT,LIST&redirect_uri='+
54 authProviderConfiguration.get('Redirect_Url__c') + '&state=' +
55 STATE_TO_PROPOGATE);
56 PageReference actualUrl = concurCls.initiate(authProviderConfiguration, STATE_TO_PROPOGATE);
57 System.assertEquals(expectedUrl.getUrl(), actualUrl.getUrl());
58 }
59
60 static testMethod void testHandleCallback()
61 {
62 Map<String,String> authProviderConfiguration =
63 setupAuthProviderConfig();
64 Concur concurCls = new Concur();
65 concurCls.redirectUrl = authProviderConfiguration.get
66 ('Redirect_Url_c');
67
68 Test.setMock(HttpCalloutMock.class, new
69 ConcurMockHttpResponseGenerator());
70
71 Map<String,String> queryParams = new Map<String,String>();
72 queryParams.put('code','code');
73 queryParams.put('state',authProviderConfiguration.get('State_c'));
74 Auth.AuthProviderCallbackState cbState =
75 new Auth.AuthProviderCallbackState(null,null,queryParams);
76 Auth.AuthProviderTokenResponse actualAuthProvResponse =
77 concurCls.handleCallback(authProviderConfiguration, cbState);
78 Auth.AuthProviderTokenResponse expectedAuthProvResponse =
79 new Auth.AuthProviderTokenResponse(
80 'Concur', OAUTH_TOKEN, REFRESH_TOKEN, null);
81
82 System.assertEquals(expectedAuthProvResponse.provider,
83 actualAuthProvResponse.provider);
84 System.assertEquals(expectedAuthProvResponse.oauthToken,
85 actualAuthProvResponse.oauthToken);
86 System.assertEquals(expectedAuthProvResponse.oauthSecretOrRefreshToken,
87 actualAuthProvResponse.oauthSecretOrRefreshToken);
88 System.assertEquals(expectedAuthProvResponse.state,
89 actualAuthProvResponse.state);
90
91
92 }
93
94
95 static testMethod void testGetUserInfo()
96 {
97 Map<String,String> authProviderConfiguration =
98 setupAuthProviderConfig();
99 Concur concurCls = new Concur();
100
101 Test.setMock(HttpCalloutMock.class, new
102 ConcurMockHttpResponseGenerator());
103
104 Auth.AuthProviderTokenResponse response =
105 new Auth.AuthProviderTokenResponse(
106 PROVIDER, OAUTH_TOKEN ,'sampleOauthSecret', STATE);
107 Auth.UserData actualUserData = concurCls.getUserInfo(
108 authProviderConfiguration, response) ;
109
110 Map<String,String> provMap = new Map<String,String>();
111 provMap.put('key1', 'value1');
112 provMap.put('key2', 'value2');
113
114 Auth.UserData expectedUserData = new Auth.UserData(LOGIN_ID,
115 FIRST_NAME, LAST_NAME, FULL_NAME, EMAIL_ADDRESS,
116 null, LOCALE_NAME, null, PROVIDER, null, provMap);
117
118 System.assertNotEquals(expectedUserData,null);
119 System.assertEquals(expectedUserData.firstName,
120 actualUserData.firstName);
121 System.assertEquals(expectedUserData.lastName,
122 actualUserData.lastName);
123 System.assertEquals(expectedUserData.fullName,
124 actualUserData.fullName);
125 System.assertEquals(expectedUserData.email,
126 actualUserData.email);
127 System.assertEquals(expectedUserData.username,
128 actualUserData.username);
129 System.assertEquals(expectedUserData.locale,
130 actualUserData.locale);
131 System.assertEquals(expectedUserData.provider,
132 actualUserData.provider);
133 System.assertEquals(expectedUserData.siteLoginUrl,
134 actualUserData.siteLoginUrl);
135 }
136
137
138 // Implement a mock http response generator for concur
139 public class ConcurMockHttpResponseGenerator implements HttpCalloutMock
140 {
141 public HTTPResponse respond(HTTPRequest req)
142 {
143 String namespace = API_USER_VERSION_URL;
144 String prefix = 'mockPrefix';
145
146 Dom.Document doc = new Dom.Document();
147 Dom.XmlNode xmlNode = doc.createRootElement(
148 'mockRootNodeName', namespace, prefix);
149 xmlNode.addChildElement('LoginId', namespace, prefix)
150 .addTextNode(LOGIN_ID);
151 xmlNode.addChildElement('FirstName', namespace, prefix)
152 .addTextNode(FIRST_NAME);
153 xmlNode.addChildElement('LastName', namespace, prefix)
154 .addTextNode(LAST_NAME);
155 xmlNode.addChildElement('EmailAddress', namespace, prefix)
156 .addTextNode(EMAIL_ADDRESS);
157 xmlNode.addChildElement('LocaleName', namespace, prefix)
158 .addTextNode(LOCALE_NAME);
159 xmlNode.addChildElement('Token', null, null)
160 .addTextNode(OAUTH_TOKEN);
161 System.debug(doc.toXmlString());
162 // Create a fake response
163 HttpResponse res = new HttpResponse();
164 res.setHeader('Content-Type', 'application/xml');
165 res.setBody(doc.toXmlString());
166 res.setStatusCode(200);
167 return res;
168 }
169
170 }
171 }