[SalesForce] Named Credential with JWT Token Exchange for authorizing calls to Google Cloud

I can make requests with Bearer from Auth.JWT:

Auth.JWT jwt = new Auth.JWT();
jwt.setSub('service-portal-rlj@appspot.gserviceaccount.com'); 
jwt.setAud('https://accounts.google.com/o/oauth2/token'); 
jwt.setIss('service-portal-rlj@appspot.gserviceaccount.com');

Map<String, Object> claims = new Map<String, Object>();
claims.put('scope', 'https://www.googleapis.com/auth/bigquery');
jwt.setAdditionalClaims(claims);
Auth.JWS jws = new Auth.JWS(jwt, 'privatekey');
String token = jws.getCompactSerialization();
String tokenEndpoint = 'https://accounts.google.com/o/oauth2/token';
Auth.JWTBearerTokenExchange bearer = new Auth.JWTBearerTokenExchange(tokenEndpoint, jws);
String accessToken = bearer.getAccessToken();

But if I copied all fields to Named Credentials:

sf

and try to make request:

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:GoogleCloud/projects/service-portal-rlj/queries');
req.setMethod('POST');
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());

I receive error:

System.CalloutException: Unable to complete the JWT token exchange.

Best Answer

GCP also allows JWT as a bearer token (no exchange). On paper, Named Credential supports this option. Unfortunately since this is a proprietary option that is not standardized by a published RFC, the token payload varies from service to service. In case of GCP, it wants a key id (kid) in the token header and Named Credential can't cope with that.

Therefore building a JWT in Apex and exchanging it for access token or sending it as a bearer token is the way to go.

UPDATE (Aug 2021): talked to Prod Mgmt about this. Their response is that this works as designed and that it was designed primarily for interoperability with Mulesoft. In Spring '22 (Safe Harbor) they plan on providing a flexible backend data model for Named Credentials that will handle all flavors of claims.

Related Topic