In the JWT Bearer OAuth flow, the connected app is identified by the connected app's consumer key (provided in the "iss" parameter of the JWT claims). Pre-authorizing users has very little to do with it (Profiles will need to be pre-authorized with the connected app, or users will need to approve the connected app through some other OAuth flow before you can successfully complete the flow and get your access token, but that is unrelated to the consumer key).
So far as I can tell, there are 4 items required in the JWT claims:
- issuer "iss", this is the consumer key for your connected app
- audience "aud", this is always
https://login.salesforce.com
for production, or https://test.salesforce.com
for sandboxes (or whatever your Salesforce community url is, if you have one)
- subject "sub", this is the username (user@yourcompany.tld) of the user you want to execute requests as
- expiration "exp", this is the expiration time of the JWT itself, and provides a way to tolerate differences in client & server time. This is the unix timestamp (seconds or milliseconds since unix epoch) + a little more time to allow for the JWT to make it to Salesforce. Really, any timestamp 1 minute or more in the future should work fine here. It has nothing to do with how long the access token is valid for
The client secret is, as far as I can tell, not used at any point in the JWT Bearer flow. Instead, that's why you create a certificate to use. If you created a certificate in Salesforce to use for this purpose, you'll need to download the individual certificate (not export to a keystore, you should get a .crt file out of it). You'll also need to make sure that your connected app has "use digital signatures" checked (after which, when editing your connected app, you can upload the .crt file).
The permissions (Oauth scopes) that your connected app requires for the JWT Bearer flow to work are:
- Access and manage your data (api)
- Perform requests on your behalf at any time (refresh_token, offline_access)
A note about session timeout:
Once you get an access token, it is treated just like any other session in Salesforce. If you make a request every so often, the same access token will remain valid. If you go to your connected app and click the "manage" button, and then click "edit policies" you can adjust the session timeout independent of your org's session timeout. If you don't explicitly specify a session timeout in your connected app, it'll just use your org's default session timeout.
As for your third question, I'm not quite sure how to answer that. It doesn't sound (to me) like you need that information at all. Everything you need to know (besides the "expr") should generally be static, and information available in or related to Salesforce.
To help out a bit more, I'll go through the steps required to manually construct the JWT in anonymous apex. It's covered in the help article that you linked in your question, but the wording in that help article was a bit on the obtuse side, I thought.
// The consumer key for a connected app of mine
String iss = '3MVG9jfQT7vUue.EIXJ6Vbqu4LHxslR9fX0MHAp1SyQhCTocvhaXPT9eWuD7kxHoHsXPIiTjMQKv2Ln<last couple of characters removed';
// I'm doing this in a sandbox, so aud is test.salesforce.com
// Replace with login.salesforce.com for production environments
String aud = 'https://test.salesforce.com';
// A user that belongs to one of the pre-authorized profiles for your connected app.
// Setup -> Create -> Apps
// Connected apps are at the bottom of the page (at time of writing, API v41.0)
// Go into your target connected app and click the "manage" button
// Click "Manage Profiles" near the bottom of the page, and add/remove profiles as needed.
String sub = 'derek@myCompany.com';
// Expiration time of the JWT itself
// Best to make this a long instead of an int
// Adding 5 minutes is arbitrary, anything less than a minute is fairly dangerous,
// and anything more is fine (could be minutes, days, months, or years in the future)
Long exp = DateTime.now().addMinutes(5).getTime();
// Start constructing the header and claims
// The "alg" will pretty much always be "RS256" with Salesforce
String jwtHeader = '{"typ":"JWT","alg":"RS256"}';
String jwtClaims = '{"iss":"' + iss + '","sub":"' + sub + '","aud":"' + aud + '","exp":' + exp + '}';
// Now we have to start Base64 encoding things.
// For JWT, Base64 is not good enough, there are 2 characters that are not URL-safe
// which we need to deal with.
// '+' needs to be replaced with '-', and '/' needs to be replaced with '_'
// This variant of Base64 is called Base64Url, and Salesforce doesn't provide us
// with a method to do that automatically.
// This step takes the JWT header and claims, separately Base64Url encodes them, and
// concatenates them with a period/full stop
String jwtRequest = System.encodingUtil.base64Encode(Blob.valueOf(jwtHeader)).replace('+', '-').replace('/', '_') + '.' + System.encodingUtil.base64Encode(Blob.valueOf(jwtClaims)).replace('+', '-').replace('/', '_');
// Here is what the certificate is used for.
// We sign the Base64Url-encoded JWT request with the private key of the certificate
// If you have the certificate stored in Salesforce (Setup, quick find box, type
// certificate and key management), then you can use Crypto.signWithCertificate(),
// providing the unique name of the certificate you want to use
String signature = System.encodingUtil.base64Encode(Crypto.signWithCertificate('RSA-SHA256', Blob.valueOf(jwtRequest), 'My_Cert')).replace('+', '-').replace('/', '_');
// Otherwise, if you have the private key of the certificate, you can use Crypto.sign()
//String signature = System.encodingUtil.base64Encode(Crypto.sign('RSA-SHA256', Blob.valueOf(jwtRequest), '<long string, the private key of the cert>')).replace('+', '-').replace('/', '_');
// One of the final steps, append the jwt request and the signature of that request
// (again with a period/full stop between them)
String signedJwtRequest = jwtRequest + '.' + signature;
// The JWT is fully constructed, now it's time to make the call to get the access token.
String payload = 'grant_type=' + System.EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8');
payload += '&assertion=' + signedJwtRequest;
Http httpObj = new Http();
HttpRequest req = new HttpRequest();
HttpResponse res;
// My sandbox is on cs52 for the moment
// You'll need to replace this with your sandbox pod (or production pod, or your custom
// domain if you've embraced Lightning Experience)
// Having a "My Domain" set up for production is very helpful, as Salesforce can
// eventually migrate your production org to a different pod.
// If you're doing this in anonymous apex (or in Apex in general), don't forget to
// add this domain to your remote site settings.
// No matter what environment you're using, the tail end of the endpoint you'll
// be using to submit the JWT is '/services/oauth2/token'
req.setEndpoint('https://cs52.salesforce.com/services/oauth2/token');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody(payload);
res = httpObj.send(req);
// If everything goes well, res.getBody() should contain JSON with "access_token"
Of course, if you're going to be doing this from within Salesforce (the JWT flow is an excellent choice for making an integration between two orgs), there is an easier way, the JWS, JWT, and JWTBearerTokenExchange classes in the Auth namespace
// 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.setAud(aud);
jwt.setIss(iss);
jwt.setSub(sub);
jwt.setValidityLength(Integer.valueof(exp));
// Storing the certificate in Salesforce is a requirement for using the JWS class
Auth.JWS jws = new Auth.JWS(jwt, 'My_Cert_Name');
String tokenEndpoint = 'https://cs52.salesforce.com/services/oauth2/token';
Auth.JWTBearerTokenExchange bearer = new Auth.JWTBearerTokenExchange(tokenEndpoint, jws);
String accessToken = bearer.getAccessToken();
Best Answer
You might be able to add something to the headers or the page to be able to access the actual photo url, but I'd avoid the whole thing & provide the photo in another form, either in base64, or as a publicly linked (you can set up "private" links as well) ContentVersion.
Base64
Downsides: Spotty support (never had a problem unless its an email), large amount of text, may need to return per request depending on how you set your page up (best used as a saved database entry instead of getting a value with each request)
ContentVersion
Downsides: Link is likely public to anyone with a copy of it, Setting this up to happen automatically is a pain, since you cant update a setup object & a non-setup object in the same context, Lots of extra objects involved to get the data to be shareable (ContentDocument, ContentVersion, User, etc)
Base64 Sample Code (Should run in dev console):
You may need to store the base64 result either in a long/rich text field or in a json object, depending on how your server handles the data.
ContentVersion Demo
Creates a contentVersion using the blob from the users FullPhotoUrl, set to "publicly available", which creates a ContentDistribution, which you can query & get the ContentDownloadUrl from, which you can use in an img tag or store somewhere. Could be useful to create base64 or binary files on a target server.