This currently works with named credentials / external credentials. The only reason you would stick with Auth.JWT is if you are using domain wide delegation and need to dynamically change the user you are impersonating.
I recently had to set this up and could find nothing online providing clear directions how to do it, most (like the existing answers here) say it cannot be done and to use Auth.JWT. I am adding this in the hope it saves someone an hour or two of going around in circles in the future.
The instructions below assume you are creating a server to server connection between SF and Google, to allow your system to interact with googleapis with no permission required from the running user.
Step 1:
Import a certificate to SF from a JKS keystore, you will need to create the keystore from a .p12 format key that you export from your service account in GCP. Once you have the p12, the keytool command probably looks like this
keytool -importkeystore -srckeystore ./serviceaccountfilename.p12 -destkeystore destination_jks_filename.jks -srcstorepass notasecret -srcalias privatekey -srcstoretype pkcs12 -deststoretype jks -destalias set_your_destination_cert_alias -deststorepass notasecret
note: Before you can import any .jks you must first have enabled an identity provider in salesforce. If you haven't done this, the easy way is to create a certificate (in the sf UI) and add an identity provider using the certificate you just created.
Step 2:
Create an External credential with the following settings;
Common Claims;
- Issuer (iss): <your_service_account>
- Subject (sub): <your_service_account> (use a different user email here if you have enabled domain wide delegation and wish to impersonate a user)
- Audience (aud): https://oauth2.googleapis.com/token
- JWT Expiration (Seconds): 3600 (or whatever is appropriate for your use case)
JWT Signing
- Signing Certificate: <your_uploaded_cert>
- Signing Algorithm: RS256
The trick is to add your scope to the claims body. Once you have saved the record scroll down to the claims section and click the 'edit' button, when the modal opens click the 'add' button to add a claim with the following params;
- Name: scope
- Value: <space_separated_list_of_scopes_required>
- Type: JWT Body Claim
The scopes requested here must match the access defined for your service account (and domain wide delegation if you are using it)
To allow your users to access your named credentials you should add a principal record with the following settings;
- Parameter Name: <your_choice>
- Sequence Number: <your_choice>
- Identity Type: Named Principal
- Scope: LEAVE THIS BLANK
If you add a scope at this point and use domain wide delegation you will get an error stating "Invalid downscoping, scopes should not be specified as a request parameter"
You will also need a permission set providing access to the principal and assign it to all users who will run the process.
Step 3:
Create a named credential using the following;
Authentication
- External Credential: <Select_your_external_credential>
Callout Options
- Generate Authorization Header: true
Make a note of the named credential api name, you need this for the callout.
Step 4:
You can now callout to google api using your service account like this
// get a list of calendars belonging to the user specified in subject (sub) claim of the external credential
// (use the service account address to access the service account itself)
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:named_credential_api_name/users/me/calendarList/primary');
request.setMethod('GET');
Http http = new Http();
try {
HTTPResponse response = http.send(request);
System.debug('Response Status: ' + response.getStatus());
System.debug('Response Body: ' + response.getBody());
} catch(System.CalloutException e) {
System.debug('Callout error: ' + e.getMessage());
}
Things to note:
- If you just created your service account and get a list of calendars, expect it to be empty
- You must create and manage the service account resources using the API, you cannot do it from the UI.
- Depending on your environment you may prefer to use the google sdk client libraries (node, python or java) from admin/setup tasks on the service account
- You should test your service account using the client libraries if you have any permission issues to ensure the account is set up correctly at google's end
I get: "System.CalloutException: Unable to complete the JWT token exchange. Error: invalid_scope. Error description: Invalid OAuth scope or ID token audience provided.."
I tried snooping on the payload it sends and it turned out that the "scope" was being passed by the NC as a separate parameter in the body, rather than part of the JWT assertion.
– pnoytechie Sep 01 '22 at 23:20scopein the token: https://cloud.google.com/iap/docs/signed-headers-howto – identigral Sep 05 '22 at 17:40