GOAL: Retrieve an access token from a 3rd party service so as to make a call with this token to 3rd party API. This is the quote from the doc.
The ExternalApp API is accessible using an Oauth2 JWT obtained from ExternalApp auth service following Oauth2 Client Credentials flow. More simply, this is an exchange of the client credentials for a limited lifespan token that can be used for authentication and authorization.
SANITY TEST: I used cURL (see below) and got a token that worked for GET calls to the endpoint. (ID and secret removed)
curl --location --request POST "https://auth-stage.ExternalApp.org/oauth/token"
--header "Accept: application/json"
--header "Content-Type: application/x-www-form-urlencoded"
--data-urlencode "grant_type=client_credentials"
--data-urlencode "scope=create:loan_draft read:loans"
--data-urlencode "audience=https://partner-api-stage.ExternalApp.org"
--data-urlencode "client_id=<client ID>"
--data-urlencode "client_secret=<client secret from Partner Admin>"
the response was good! (the access token below is not real)
{"access_token":"yJhbGciXXXUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjFublhjRFRHIn0.eyJhdWQiOlsiaHR0cHM6Ly9wYXJ0bmVyLWFwaS5rMS5raXZhLm9yZyJdLCJzY29wZSI6WyJjcmVhdGU6bG9hbl9kcmFmdCIsInJlYWQ6bG9hbnMiXSwiaXNzIjoiaHR0cHM6Ly9hdXRoLmsxLmtpdmEub3JnLyIsInBhcnRuZXJJZCI6IjEiLCJleHAiOjE2MDIxNTY2MTgsImp0aSI6IlJVc2l2VVhHZ2hoeC1Zdjl6emEzZ2daaTZhbXXXImNsaWVudF9pZCI6IlFEMmxPRzZMbTN2RWQ5QTZEdVh3eFJWOE1OMEp6cDVreSJ9.U_tCMX5ra7Q0NFwr1FKlgqCBEmlprY-PuXXX6bNzEREtJABh0hBr-zEKXQEhHYTpHjjNquOHK7Q8hnQ30IVVhE6jXUO8_OgRfmczlQ8sDkRzmx5PTc99my0bs6zn8owRfEEwBGJcvNt_oT8iRASnlij99d7dozTFguBnT7_hauXoq2C4DFmRx3rjfnCbI9G7Ue_4Gh3jnF7VYI9HefLvYHBCS0SP3a-QqNuR5w1itRevj8KOIhC5lKuJn22cRXW9PQL3G9XGyK0h8sFZj7blhLETMLFAHbrWFUGzawEBAeLQbQhvvu78dp0RzgY0OvS2XXzTgxpg0TcgsrWuDdjFAA",
"token_type":"bearer",
"expires_in":43199,
"scope":"create:loan_draft read:loans",
"iss":"https://auth-stage.ExternalApp.org/",
"partnerId":"468",
"jti":"erBXqikrdZ8QP1Jlc1HyjoJyoGQ"}
APPROACH #1: I tried to make a Named Credential
and then ran this APEX anonymously
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ExternalApp_Stage/some_path');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());
Result of approach #1 Error: System.CalloutException: Unable to complete the JWT token exchange.
This is I guess not too surprising, since there was no place in the Named Credential for me to put the client id, Client secret, grant type... also I can't set the header... but I read it is taken care of by Salesforce.
APPROACH #2: all out APEX
i was trying to learn from https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Auth_JWTBearerTokenExchange.htm#apex_Auth_JWTBearerTokenExchange_constructors
// aud, iss, sub, and exp still need to be specified, but these Auth classes
// take care of everything else for you.
Auth.JWT jwt = new Auth.JWT();
jwt.setSub('[email protected]');
jwt.setAud('https://partner-api-stage.ExternalApp.org');
jwt.setIss('ClientSecret+++1BYjySfmsPhT/eWCTy40r1PgtDkpNWGL7NR/6MEeCfuO95tSfAb');
jwt.setValidityLength(36000);
//Additional claims to set scope
Map<String, Object> claims = new Map<String, Object>();
claims.put('scope', 'create:loan_draft read:loans');
claims.put('client_id', '<client id>');
claims.put('client_secret', 'ClientSecret+++1BYjySfmsPhT/eWCTy40r1PgtDkpNWGL7NR/6MEeCfuO95tSfAd');
jwt.setAdditionalClaims(claims);
//Create the object that signs the JWT bearer token
Auth.JWS jws = new Auth.JWS(jwt, 'Cert2021July9');
//Get the resulting JWS in case debugging is required
String token = jws.getCompactSerialization();
//Set the token endpoint that the JWT bearer token is posted to
String tokenEndpoint = 'https://auth-stage.ExternalApp.org/oauth/token';
//POST the JWT bearer token
Auth.JWTBearerTokenExchange bearer = new Auth.JWTBearerTokenExchange();
bearer.setTokenEndpoint(tokenEndpoint);
bearer.setJWS(jws);
bearer.setGrantType('client_credentials');
//Get the access token
String accessToken = bearer.getAccessToken();
result of approach #2 Error - i probably messed up the iss, Aud, Sub parameters... :(
Error 401: <html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Wed Sep 15 06:36:11 GMT 2021</div><div>There was an unexpected error (type=Unauthorized, status=401).</div><div></div></body></html>