@ClientCredentials(tokenUrl = "https://login.salesforce.com/services/oauth2/authorize")
public class SalesforceOAuthConnectionProvider<C> implements ConnectionProvider<SalesforceClient> {
@Parameter
@Optional(defaultValue = "34.0")
private Double apiVersion;
/**
* Tailors the login page to the user's device type.
*/
@OAuthParameter
private String display;
/**
* Avoid interacting with the user
*/
@OAuthParameter
@Optional(defaultValue = "false")
private boolean immediate;
/**
* Specifies how the authorization server prompts the user for reauthentication and reapproval
*/
@OAuthParameter
@Optional(defaultValue = "true")
private boolean prompt;
@OAuthCallbackValue(expression = "#[payload.instance_url]")
private String instanceId;
@OAuthCallbackValue(expression = "#[payload.id]")
private String userId;
private AuthorizationCodeState state;
@Override
public SalesforceClient connect() throws ConnectionException {
if (state.getAccessToken() == null) {
throw new SalesforceException(MessageFormat.format(COULD_NOT_EXTRACT_FIELD, "accessToken"));
}
if (instanceId == null) {
throw new SalesforceException(MessageFormat.format(COULD_NOT_EXTRACT_FIELD, "instanceId"));
}
return new SalesforceClient(state.getAccessToken(), instanceId, apiVersion);
}
public void disconnect(SalesforceClient connection) {
connection.close();
}
@Override
public ConnectionValidationResult validate(SalesforceClient connection) {
return success();
}
}
Client Credentials
Available since version 1.3.
Unlike the Authorization Code grant type, which focuses on authorizing an application that wants to act on a user’s behalf, the Client Credentials grant type focuses on authorizing an application to act on its own behalf. This grant type is better suited for machine-to-machine communication because it doesn’t require human intervention.
Develop a Client Credentials-Enabled Connector
You can add Client Credentials support at the ConnectionProvider
level through the @ClientCredentials
annotation.
Here is an oversimplified ConnectionProvider
example for Anypoint Connector for Salesforce (Salesforce Connector):
The class implements ConnectionProvider
, just like other connections in the SDK. Operations that are authenticated through the object operate over the SalesforceClient
object that the provider returns. This is an important design consideration because it permits you to decouple the operation from the authentication method that is used because the connector could define another ConnectionProvider
that uses basic authentication, and all the operations remain fully compatible.
@ClientCredentials
This annotation indicates that this connection provider requires an OAuth dance using the Client Credentials grant type. The annotation has the following attributes:
public @interface ClientCredentials {
/**
* @return The Url of the endpoint which provides the access tokens
*/
String tokenUrl();
/**
* @return Expression to be used on the response of {@link #tokenUrl()} to extract the access token
*/
String accessTokenExpr() default "#[payload.access_token]";
/**
* @return Expression to be used on the response of {@link #tokenUrl()} to extract the access token expiration
*/
String expirationExpr() default "#[payload.expires_in]";
/**
* @return The default set of scopes to be requested, as a comma separated list. Empty string means no default scopes.
*/
String defaultScopes() default "";
/**
* Allows to customize the placement that the client credentials will have in the request.
*
* @return the selected {@link CredentialsPlacement}. Defaults to {@link CredentialsPlacement#BASIC_AUTH_HEADER}
*/
CredentialsPlacement credentialsPlacement() default BASIC_AUTH_HEADER;
}
Connection Management Strategy
The provider in the @ClientCredentials
example does not implement any specialization of the ConnectionProvider interface, which means that the OAuth mechanism can be combined with the other connection management strategies. The connection objects can be pooled, cached, or created anew each time, depending on which interface is used (for example, PoolingConnectionProvider, CachedConnectionProvider, ConnectionProvider, and so on). For more information about connectivity management, see the connectivity reference.
Be aware of the semantics of using the unspecialized (for example, not PollingConnectionProvider, CachedConnectionProvider) ConnectionProvider interface in this scenario. In a regular, “non-OAuth” connection provider, using the unspecialized interface means that each time a component requires a connection, a new one is created, and it is destroyed when the component is finished. Although this will remain true for the OAuth case, it does not mean that the OAuth dance is performed again. New connection objects are created, but the same access token is reused as long as it remains valid.
Regular Parameters versus OAuth Parameters
This ConnectionProvider can have parameters, just like any other connection provider. However, you must distinguish regular parameters from the concept of @OAuthParameter
.
An OAuthParameter is included as a custom parameter while performing the OAuth dance. So, for example, while the apiVersion
parameter is something that the connection provider uses to create the SalesforceClient, the immediate parameter is actually sent on the OAuth request to the service provider.
From the module’s point of view, it is just another parameter for which the user provides a value. You can combine these parameters with @Optional
, @Expression
, and all the other annotations you can use with the traditional @Parameter
annotation. In the DSL, regular and OAuth parameters appear together. The module’s end user should not notice any difference.
requestAlias Parameter
Some custom OAuth parameters include characters that are not supported in Java, for example Api-Key
. Since you cannot use -
as part of a field name, the @OAuthParameter annotation has an optional parameter called requestAlias
:
@OAuthParameter(requestAlias = "api-key")
private String apiKey;
@OAuthCallbackValue Annotation
Callback values are extracted from the response that the service provider sends through the OAuth callback. Although most service providers return only standard items (such as access and refresh tokens, expiration information, and so on), others return additional items (such as user and instance IDs, in the case of Salesforce).
The annotation includes an expression that is applied to the response to extract the value. That value is then assigned to the field for the connection provider to use. When the connect(), validate(), or disconnect() methods are invoked, the fields are set and usable.
@ClientCredentialsState
Every ConnectionProvider annotated with @ClientCredentials
must contain one (and only one) field of type ClientCredentialsState.
It is a simple immutable POJO that contains information regarding the outcome of the OAuth dance. It contains the following information:
public interface ClientCredentialsState {
/**
* @return The obtained access token
*/
String getAccessToken();
/**
* @return The access token's expiration. The actual format of it depends on the OAuth provider
*/
Optional<String> getExpiresIn();
}
Through this object, the provider gains access to the accessToken and other standard information that was obtained during the OAuth dance. The original Salesforce example shows how the connect() method uses this POJO to create the client.
Configure a Client Credentials Connector
An important aspect of OAuth-enabled connectors or modules is how to use them. As much as the SDK does to hide the complexities of the OAuth protocol, things like the OAuth dance are unavoidable. The SDK’s approach to this problem is to standardize the experience of all OAuth modules to keep simplifying the user experience. This section discusses the steps a user must take to use the module.
Synthetic Parameters
In addition to everything explicitly defined in the ConnectionProvider, the SDK automatically adds some extra parameters and injects the proper behavior for them.
Parameter Name | Required | Expressions | Default Value | Description |
---|---|---|---|---|
|
YES |
SUPPORTED |
N.A. |
The OAuth |
|
YES |
NOT_SUPPORTED |
N.A. |
The OAuth |
|
NO |
SUPPORTED |
Value provided in the |
The service provider’s token endpoint URL. |
|
NO |
SUPPORTED |
The value provided in the |
The OAuth scopes to be requested during the dance. If not provided, this value defaults to those in the annotation. |
|
NO |
NOT_SUPPORTED |
N.A. |
A reference to the ObjectStore used for storing each resource owner ID’s data. If not specified, the runtime automatically provisions a default one. |
About the Use of Expressions
The synthetic parameters table identifies many of the synthetic parameters that accept expressions. Using expressions there has the same effect as using expressions in a regular parameter: it turns the configuration dynamic.
OAuth Connection DSL
The following example shows generated DSL:
<sfdc:config name="salesforce">
<sfdc:client-credentials-connection display="PAGE" immediate="FALSE" prompt="CONSENT">
<sfdc:oauth-client-credentials clientId="${sfdc.consumerkey}" clientSecret="${sfdc.consumersecret}" tokenUrl="http://..." />
<sfdc:oauth-store-config objectStore="oauthObjectStore" />
</sfdc:config>
Regular and OAuth parameters are all shown at the connection provider level, as they would be in any other provider. The parameters related to the Client Credentials grant type are placed on a child element called <oauth-client-credentials>. The parameters related to ObjectStore are placed in a child element called <oauth-store-config>.
Configuring a Custom ObjectStore
The obtained access tokens are stored in an Object Store. By default, the SDK stores them in the app’s default Object Store, but users can define their own custom Object Store, as shown in this example:
<os:object-store name="tokenStore"
entryTtl="1"
entryTtlUnit="HOURS"
maxEntries="100"
persistent="true"
expirationInterval="30"
expirationIntervalUnit="MINUTES" />
<sfdc:config name="salesforce">
<sfdc:client-credentials-connection display="PAGE" immediate="FALSE" prompt="CONSENT">
<sfdc:oauth-client-credentials clientId="${sfdc.consumerkey}" clientSecret="${sfdc.consumersecret}" tokenUrl="http://..." />
<sfdc:oauth-store-config objectStore="tokenStore" />
</sfdc:config>
Refresh an Expired Client Credentials Access Token
Most access tokens have a limited lifespan: typically about 30 to 60 minutes after being issued. This is why most providers also supply a refresh token, which eliminates the need to perform the OAuth dance again, although this is not a standard that is strictly enforced by providers.
There is not an enforced standard that the SDK automatically detects an expired token and replaces it. Because different APIs communicate that in different ways, the AccessTokenExpiredException
exception exists, shown below:
public void someOperation(@Connection SalesforceRestClient client) {
Response response = client.performSomeOperation();
if (response.getStatusCode() == 401) {
throw new AccessTokenExpiredException();
}
}
This example uses an imaginary REST client to perform an operation:
-
It assumes that this REST client was created through a ConnectionProvider annotated with
@ClientCredentials
. -
It performs the operation and receives a Response object that contains information about the HTTP call.
-
The example assumes that a status code of 401 (
UNAUTHORIZED
) means that the token is expired. -
It throws the AccessTokenExpiredException.
When the SDK detects this exception, it automatically performs the refresh dance and retries the operation with a new access token. As noted earlier, different APIs notify you of an access token expiration in different ways. Some give you clients that throw exceptions, while others give you custom messages. You should research the remote API to determine the best way to account for this.
Invalidating Access Tokens
Multitenancy implies the ability to invalidate the access token of a particular resourceOwnerId, causing the associated token information to be deleted.
To invalidate an access token, the SDK automatically adds an operation called unauthorize
to every OAuth-enabled connector or module. Given the following example, you might invalidate the token obtained like this:
<sfdc:unauthorize config-ref="salesforce"/>
Note that this operation does not invalidate the token on the service provider. It removes the service provider from Mule’s caches and Object Stores.