---
schema_version: "1"
id: e15600f4886e9e9a0ca481390ea74ca1baa6f5856846a86e5bdec6648cb11e34
title: Create a Custom Authentication Provider Plug-in
collection: legacydeveloper
version: current
locale: en-us
portal_url: https://docs.salesforce.com/legacydeveloper/current/en-us/apexcode/authproviderplugin.md
canonical_url: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/authproviderplugin.htm
source_site: developer.salesforce.com
content_type: documentation
content_hash: 77b11dd3f56a5e430aca5a26cfcff046bced34202b6270b12399e4bf1c26e755
description: You can use Apex to create a custom OAuth-based authentication provider plug-in for single sign-on (SSO) to Salesforce.
---
# Create a Custom Authentication Provider Plug-in

You can use Apex to create a custom OAuth-based authentication provider plug-in for single sign-on (SSO) to Salesforce.

Out of the box, Salesforce supports several external authentication providers for single sign-on, including Facebook, Google, LinkedIn, and service providers that implement the OpenID Connect protocol. By creating a plug-in with Apex, you can add your own OAuth-based authentication provider. Your users can then use the SSO credentials they already use for non-Salesforce applications with your Salesforce orgs.

Before you create your Apex class, you create a custom metadata type record for your authentication provider. For details, see [Create a Custom External Authentication Provider](https://help.salesforce.com/HTViewHelpDoc?id=sso_provider_plugin_custom.htm&language=en_US "HTML (New Window)").

## Sample Classes

This example extends the abstract class Auth.AuthProviderPluginClass to configure an external authentication provider called Concur. Build the sample classes and sample test classes in the following order.

1. Concur
2. ConcurTestStaticVar
3. MockHttpResponseGenerator
4. ConcurTestClass

![Note](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/images/icon_note.png)

#### Note

The Auth.AuthProviderPluginClass class doesn't include a method for single logout. You can easily configure single logout in Setup. For steps, see [Configure OpenID Connect Single Logout with Salesforce as the Relying Party](https://help.salesforce.com/s/articleView?id=xcloud.security_auth_slo_oidc_rp_configuring.htm&language=en_US) in Salesforce Help. Alternatively, create custom methods for single logout.

public with sharing class Concur extends Auth.AuthProviderPluginClass {
               
               public String redirectUrl; // use this URL for the endpoint that the authentication provider calls back to for configuration
               private String key;
               private String secret;
               private String authUrl;    // application redirection to the Concur website for authentication and authorization
               private String accessTokenUrl; // uri to get the new access token from concur  using the GET verb
               private String customMetadataTypeApiName; // api name for the custom metadata type created for this auth provider
               private String userAPIUrl; // api url to access the user in concur
               private String userAPIVersionUrl; // version of the user api url to access data from concur
               
       
               public String getCustomMetadataType() {
                   return customMetadataTypeApiName;
               }
       
               public 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’ll 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);
                }
        
               public 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 AuthProviderToken
                   //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 accessToken = getTokenValueFromResponse(responseBody, 'AccessToken', null);
                   //Parse access token value
                   String refreshToken = getTokenValueFromResponse(responseBody, 'RefreshToken', null);
                   //Parse refresh token value
                   
                   return new Auth.AuthProviderTokenResponse('Concur', accessToken, 'refreshToken', sfdcState);
                   //don’t hard-code the refresh token value!
                }
    
    
                 public 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;
                }  
    
}

## Sample Test Classes

The following example contains test classes for the Concur class.

@IsTest
public with sharing class ConcurTestClass {

    private static final String OAUTH\_TOKEN = 'testToken';
    private static final String STATE = 'mocktestState';
    private static final String REFRESH\_TOKEN = 'refreshToken';
    private static final String LOGIN\_ID = 'testLoginId';
    private static final String USERNAME = 'testUsername';
    private static final String FIRST\_NAME = 'testFirstName';
    private static final String LAST\_NAME = 'testLastName';
    private static final String EMAIL\_ADDRESS = 'testEmailAddress';
    private static final String LOCALE\_NAME = 'testLocalName';
    private static final String FULL\_NAME = FIRST\_NAME + ' ' + LAST\_NAME;
    private static final String PROVIDER = 'Concur';
    private static final String REDIRECT\_URL = 'http://localhost/services/authcallback/orgId/Concur';
    private static final String KEY = 'testKey';
    private static final String SECRET = 'testSecret';
    private static final String STATE\_TO\_PROPOGATE  = 'testState';
    private static final String ACCESS\_TOKEN\_URL = 'http://www.dummyhost.com/accessTokenUri';
    private static final String API\_USER\_VERSION\_URL = 'http://www.dummyhost.com/user/20/1';
    private static final String AUTH\_URL = 'http://www.dummy.com/authurl';
    private static final String API\_USER\_URL = 'www.concursolutions.com/user/api';

    // in the real world scenario , the key and value would be read from the (custom fields in) custom metadata type record
    private static Map<String,String\> setupAuthProviderConfig () {
            Map<String,String\> authProviderConfiguration = new Map<String,String\>();
           authProviderConfiguration.put('Key\_\_c', KEY);
           authProviderConfiguration.put('Auth\_Url\_\_c', AUTH\_URL);
           authProviderConfiguration.put('Secret\_\_c', SECRET);
           authProviderConfiguration.put('Access\_Token\_Url\_\_c', ACCESS\_TOKEN\_URL);
           authProviderConfiguration.put('API\_User\_Url\_\_c',API\_USER\_URL);
           authProviderConfiguration.put('API\_User\_Version\_Url\_\_c',API\_USER\_VERSION\_URL);
           authProviderConfiguration.put('Redirect\_Url\_\_c',REDIRECT\_URL);
           return authProviderConfiguration;
          
    }

    @IsTest
    static void testInitiateMethod() {
           String stateToPropogate = 'mocktestState';
           Map<String,String\> authProviderConfiguration = setupAuthProviderConfig();
           Concur concurCls = new Concur();
           concurCls.redirectUrl = authProviderConfiguration.get('Redirect\_Url\_\_c');
           
           PageReference expectedUrl =  new PageReference(authProviderConfiguration.get('Auth\_Url\_\_c') + '?client\_id='\+ 
                                               authProviderConfiguration.get('Key\_\_c') +'&scope=USER,EXPRPT,LIST&redirect\_uri='\+ 
                                               authProviderConfiguration.get('Redirect\_Url\_\_c') + '&state=' + 
                                               STATE\_TO\_PROPOGATE);
           PageReference actualUrl = concurCls.initiate(authProviderConfiguration, STATE\_TO\_PROPOGATE);
           Assert.areEqual(expectedUrl.getUrl(), actualUrl.getUrl());
       }
    
    @IsTest
    static void testHandleCallback() {
           Map<String,String\> authProviderConfiguration = setupAuthProviderConfig();
           Concur concurCls = new Concur();
           concurCls.redirectUrl = authProviderConfiguration.get('Redirect\_Url\_c');

           Test.setMock(HttpCalloutMock.class, new ConcurMockHttpResponseGenerator());

           Map<String,String\> queryParams = new Map<String,String\>();
           queryParams.put('code','code');
           queryParams.put('state',authProviderConfiguration.get('State\_c'));
           Auth.AuthProviderCallbackState cbState = new Auth.AuthProviderCallbackState(null,null,queryParams);
           Auth.AuthProviderTokenResponse actualAuthProvResponse = concurCls.handleCallback(authProviderConfiguration, cbState);
           Auth.AuthProviderTokenResponse expectedAuthProvResponse = new Auth.AuthProviderTokenResponse('Concur', OAUTH\_TOKEN, REFRESH\_TOKEN, null);
           
           Assert.areEqual(expectedAuthProvResponse.provider, actualAuthProvResponse.provider);
           Assert.areEqual(expectedAuthProvResponse.oauthToken, actualAuthProvResponse.oauthToken);
           Assert.areEqual(expectedAuthProvResponse.oauthSecretOrRefreshToken, actualAuthProvResponse.oauthSecretOrRefreshToken);
           Assert.areEqual(expectedAuthProvResponse.state, actualAuthProvResponse.state);
           

    }
    
    
    @IsTest
    static void testGetUserInfo() {
           Map<String,String\> authProviderConfiguration = setupAuthProviderConfig();
           Concur concurCls = new Concur();
                      
           Test.setMock(HttpCalloutMock.class, new ConcurMockHttpResponseGenerator());

           Auth.AuthProviderTokenResponse response = new Auth.AuthProviderTokenResponse(PROVIDER, OAUTH\_TOKEN ,'sampleOauthSecret', STATE);
           Auth.UserData actualUserData = concurCls.getUserInfo(authProviderConfiguration, response) ;
           
           Map<String,String\> provMap = new Map<String,String\>();
           provMap.put('key1', 'value1');
           provMap.put('key2', 'value2');
                     
           Auth.UserData expectedUserData = new Auth.UserData(LOGIN\_ID, FIRST\_NAME, LAST\_NAME, FULL\_NAME, EMAIL\_ADDRESS,
                          null, LOCALE\_NAME, null, PROVIDER, null, provMap);
          
           Assert.isNotNull(expectedUserData);
           Assert.areEqual(expectedUserData.firstName, actualUserData.firstName);
           Assert.areEqual(expectedUserData.lastName, actualUserData.lastName);
           Assert.areEqual(expectedUserData.fullName, actualUserData.fullName);
           Assert.areEqual(expectedUserData.email, actualUserData.email);
           Assert.areEqual(expectedUserData.username, actualUserData.username);
           Assert.areEqual(expectedUserData.locale, actualUserData.locale);
           Assert.areEqual(expectedUserData.provider, actualUserData.provider);
           Assert.areEqual(expectedUserData.siteLoginUrl, actualUserData.siteLoginUrl);
    }
    
    
   // implementing a mock http response generator for concur 
   public with sharing class ConcurMockHttpResponseGenerator implements HttpCalloutMock {
     public HTTPResponse respond(HTTPRequest req) {
        String namespace = API\_USER\_VERSION\_URL;
        String prefix = 'mockPrefix';

        Dom.Document doc = new Dom.Document();
        Dom.XmlNode xmlNode =  doc.createRootElement('mockRootNodeName', namespace, prefix);
        xmlNode.addChildElement('LoginId', namespace, prefix).addTextNode(LOGIN\_ID);
        xmlNode.addChildElement('FirstName', namespace, prefix).addTextNode(FIRST\_NAME);
        xmlNode.addChildElement('LastName', namespace, prefix).addTextNode(LAST\_NAME);
        xmlNode.addChildElement('EmailAddress', namespace, prefix).addTextNode(EMAIL\_ADDRESS);
        xmlNode.addChildElement('LocaleName', namespace, prefix).addTextNode(LOCALE\_NAME);            
        xmlNode.addChildElement('AccessToken', null, null).addTextNode(OAUTH\_TOKEN);
        xmlNode.addChildElement('RefreshToken', null, null).addTextNode(REFRESH\_TOKEN);
        System.debug(doc.toXmlString());
        // Create a fake response
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/xml');
        res.setBody(doc.toXmlString());
        res.setStatusCode(200);
        return res;
    }
   
  }
}

#### See Also

- [_Apex Reference Guide_: AuthProviderPlugin Interface](https://developer.salesforce.com/docs/atlas.en-us.264.0.apexref.meta/apexref/apex_interface_Auth_AuthProviderPlugin.htm "Apex Reference Guide: AuthProviderPlugin Interface - HTML (New Window)")
- [Salesforce Help: Create a Custom External Authentication Provider](https://help.salesforce.com/HTViewHelpDoc?id=sso_provider_plugin_custom.htm&language=en_US "Salesforce Help: Create a Custom External Authentication Provider - HTML (New Window)")