Skip to content

Instantly share code, notes, and snippets.

@aidan-harding
Created August 15, 2022 09:23
Show Gist options
  • Save aidan-harding/245cf32799f6ba043e5ddaa97535623a to your computer and use it in GitHub Desktop.
Save aidan-harding/245cf32799f6ba043e5ddaa97535623a to your computer and use it in GitHub Desktop.
Auth. Provider for Client Credentials OAuth flow in Salesforce
/**
* @author aidan@nebulaconsulting.co.uk
* @date 21/10/2020
* @description For implementing the Client Credentials OAuth Flow using Named Credentials to store the client id
* and client secret. The initiate method is a dummy one, going straight to the callback, which does a callout to the
* token endpoint. Refreshing is also a dummy, simply obtaining a new token with the original client id and secret.
*/
public with sharing class ClientCredentialsAuthProvider extends Auth.AuthProviderPluginClass {
@TestVisible
private static final String CALLBACK_URL = Client_Credentials_Auth_Provider__mdt.Callback_URL__c.getDescribe().getName();
@TestVisible
private static final String NAMED_CREDENTIAL_FOR_TOKEN = Client_Credentials_Auth_Provider__mdt.Named_Credential_For_Token__c.getDescribe().getName();
public String getCustomMetadataType() {
return Client_Credentials_Auth_Provider__mdt.SObjectType.getDescribe().getName();
}
public PageReference initiate(Map<String, String> config, String stateToPropagate) {
PageReference result = new PageReference(config.get(CALLBACK_URL));
result.getParameters().put('state', stateToPropagate);
return result;
}
public Auth.AuthProviderTokenResponse handleCallback(Map<String, String> config, Auth.AuthProviderCallbackState callbackState) {
return new Auth.AuthProviderTokenResponse(
config.get(NAMED_CREDENTIAL_FOR_TOKEN),
retrieveToken(config.get(NAMED_CREDENTIAL_FOR_TOKEN)),
'dummy_refresh_token',
callbackState.queryParameters.get('state'));
}
public Auth.UserData getUserInfo(Map<String, String> config, Auth.AuthProviderTokenResponse response) {
// email field is used to display who is logged in on the OAuth Named Credential (not the token Named Credential)
return new Auth.UserData('fakeId', 'first', 'last', 'full', config.get(NAMED_CREDENTIAL_FOR_TOKEN), 'link', null, null, null, null, null);
}
public override Auth.OAuthRefreshResult refresh(Map<String, String> config, String refreshToken) {
return new Auth.OAuthRefreshResult(retrieveToken(config.get(NAMED_CREDENTIAL_FOR_TOKEN)), refreshToken, null);
}
private String retrieveToken(String tokenNamedCredential) {
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:' + tokenNamedCredential);
request.setHeader('Content-Type','application/json');
request.setHeader('Accept','application/json');
request.setMethod('POST');
request.setBody(JSON.serialize(new Map<String, String> {
'grant_type' => 'client_credentials',
'client_id' => '{!$Credential.UserName}',
'client_secret' => '{!$Credential.Password}'
}));
HttpResponse response = new Http().send(request);
AuthResponse parsedResponse = (AuthResponse)JSON.deserialize(response.getBody(), AuthResponse.class);
return parsedResponse.access_token;
}
private class AuthResponse {
@SuppressWarnings('PMD.VariableNamingConventions') // Has to match the JSON
public String access_token;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment