AccessTokenService - 6.3

Talend ESB Service Developer Guide

EnrichVersion
6.3
EnrichProdName
Talend Data Fabric
Talend Data Services Platform
Talend ESB
Talend MDM Platform
Talend Open Studio for ESB
Talend Real-Time Big Data Platform
task
Design and Development
Installation and Upgrade
EnrichPlatform
Talend ESB

The role of AccessTokenService is to exchange a token grant for a new access token which will be used by the client to access the end user's resources. Here is an example request log:

Address: http://localhost:8080/services/oauth/token
Http-Method: POST

Headers: {
Accept=[application/json], 
Authorization=[Basic MTIzNDU2Nzg5Ojk4NzY1NDMyMQ==], 
Content-Type=[application/x-www-form-urlencoded]
}
Payload: 

grant_type=authorization_code&code=5c993144b910bccd5977131f7d2629ab
   &redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fservices%2Freservations
   %2Freserve%2Fcomplete

This request contains a client_id and client_secret (Authorization header), the grant_type, the grant value (code) plus the redirect URI the authorization grant was returned to which is needed for the additional validation. Note that the alternative client authentication methods are also possible, in this case the token service will expect a mapping between the client credentials and the client_id representing the client registration available.

After validating the request, the service will find a matching AccessTokenGrantHandler and request to create a ServerAccessToken which is a server-side representation of the access token. The grant handlers, such as AuthorizationCodeGrantHandler may delegate the creation of the actual access token to data providers, which may create Bearer or MAC tokens with the help of utility classes shipped with CXF or depend on other 3rd party token libraries.

The data providers are not strictly required to persist the data such as access tokens, instead the token key may act as an encrypted bag capturing all the relevant information.

Now that the token has been created, it is mapped by the service to a client representation and is returned back as a JSON payload:

Response-Code: 200
Content-Type: application/json
Headers: {
 Cache-Control=[no-store], 
 Pragma=[no-cache], 
 Date=[Thu, 12 Apr 2012 14:36:29 GMT]
}

Payload: 

{"access_token":"5b5c8e677413277c4bb8b740d522b378", "token_type":"bearer"}

The client will use this access token to access the current user's resources in order to complete the original user's request, for example, the request to access a user's calendar may look like this:

Address: http://localhost:8080/services/thirdPartyAccess/calendar
Http-Method: GET
Headers: 
{
  Authorization=[Bearer 5b5c8e677413277c4bb8b740d522b378], 
  Accept=[application/xml]
}

Note that the access token key is passed as the Bearer scheme value. Other token types such as MAC ones, etc, can be represented differently.

Access Token Types

As mentioned above, AccessTokenService can work with whatever token is created by a given data provider. This section provides more information on how CXF may help with supporting Bearer and MAC tokens.

Bearer

The following code fragment shows how a BearerAccessToken utility class can be used to create Bearer tokens:

import org.apache.cxf.rs.security.oauth2.common.AccessTokenRegistration; 
import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; 
import org.apache.cxf.rs.security.oauth2.tokens.bearer.BearerAccessToken; 

public class CustomOAuthDataProvider implements AuthorizationCodeDataProvider { 

   public ServerAccessToken createAccessToken(AccessTokenRegistration reg) 
      throws OAuthServiceException { 

      ServerAccessToken token = new BearerAccessToken(reg.getClient(), 3600L); 

      List<String> scope = reg.getApprovedScope().isEmpty() ? 
         reg.getRequestedScope() : reg.getApprovedScope(); 
      token.setScopes(convertScopeToPermissions(reg.getClient(), scope)); 
      token.setSubject(reg.getSubject()); 
      token.setGrantType(reg.getGrantType()); 

      // persist as needed and then return 

      return token; 
   } 
   // other methods not shown
}

CustomOAuthDataProvider will also be asked by OAuthRequestFilter to validate the incoming Bearer tokens given that they typically act as database key or key alias, if no Bearer token validator is registered.

MAC

CXF 2.6.2 supports MAC tokens as specified in the latest MAC Access Authentication draft. MAC tokens offer an option for clients to demonstrate they 'hold' the token secret issued to them by AccessTokenService. It is recommended that AccessTokenService endpoint issuing MAC tokens enforces a two-way TLS for an extra protection of the MAC token data returned to clients.

The following code fragment shows how a MacAccessToken utility class can be used to create MAC tokens:

import org.apache.cxf.rs.security.oauth2.common.AccessTokenRegistration; 
import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; 
import org.apache.cxf.rs.security.oauth2.tokens.mac.HmacAlgorithm; 
import org.apache.cxf.rs.security.oauth2.tokens.mac.MacAccessToken; 

public class CustomOAuthDataProvider implements AuthorizationCodeDataProvider { 

   public ServerAccessToken createAccessToken(AccessTokenRegistration reg) 
      throws OAuthServiceException { 

      // generate 
      ServerAccessToken token = new MacAccessToken(reg.getClient(), 
         HmacAlgorithm.HmacSHA1, 3600L); 

      // set other token fields as shown in the Bearer section 

      // persist as needed and then return 

      return token; 
   } 
   // other methods not shown 
}

One can expect the following response:

Response-Code: 200 
Content-Type: application/json 
Headers: { 
Cache-Control=[no-store], 
Pragma=[no-cache], 
Date=[Thu, 12 Apr 2012 14:36:29 GMT]
} 

Payload: 

{"access_token":"5b5c8e677413277c4bb8b740d522b378", "token_type":"mac",
"secret"="1234568", algorithm="hmac-sha-1"} 

Note that 'access_token' is the MAC key identifier, 'secret' - MAC key.

MacAccessTokenValidator has to be registered with OAuthRequestFilter for validating the incoming MAC tokens. This validator can get a reference to custom NonceVerifier with CXF possibly shipping a default implementation in the future.

The client can use CXF OAuthClientUtils to create Authorization MAC headers. All is needed is to provide references to ClientAccessToken representing the MAC token issued by AccessTokenService and HttpRequestProperties capturing the information about the current request URI:

String requestURI = "http://localhost:8080/calendar"; 
WebClient wc = WebClient.create(requestURI); 

// represents client registration 
OAuthClientUtils.Consumer consumer = getConsumer(); 
// the token issued by AccessTokenService 
ClientAccessToken token = getToken(); 

HttpRequestProperties httpProps = new HttpRequestProperties(wc, "GET"); 
String authHeader = OAuthClientUtils.createAuthorizationHeader(consumer, token, 
   httpProps); 
wc.header("Authorization", authHeader); 

Calendar calendar = wc.get(Calendar.class);

This code will result in something like:

GET /calendar HTTP/1.1 
Host: localhost 
Accept: application/xml 
Authorization: MAC id="5b5c8e677413277c4bb8b740d522b378", 
nonce="273156:di3hvdf8", 
mac="W7bdMZbv9UWOTadASIQHagZyirA=" 
ext="12345678"

where the 'ext' attribute is used to pass a timestamp value.

Writing OAuthDataProviders

Using CXF OAuth service implementations will help a lot with setting up an OAuth server. As you can see from the above sections, these services rely on a custom OAuthDataProvider implementation.

The main task of OAuthDataProvider is to persist and generate access tokens. Additionally, as noted above, AuthorizationCodeDataProvider needs to persist and remove the code grant registrations. The way it's done is really application-specific. Consider starting with a basic memory based implementation and then move on to keeping the data in some DB.

Note that OAuthDataProvider supports retrieving Client instances but it has no methods for creating or removing Clients. The reason for it is that the process of registering third-party clients is very specific to a particular OAuth2 application, so CXF does not offer a registration support service and hence OAuthDataProvider has no Client create/update methods. You will likely need to do something like this:

public class CustomOAuthProvider implements OAuthDataProvider {
   public Client registerClient(String applicationName, 
      String applicationURI, ...) {}
   public void removeClient(String cliendId) {}
   // ...
   // OAuthDataProvider methods
}

CustomOAuthProvider will also remove all tokens associated with a given Client in removeClient(String cliendId).

Finally OAuthDataProvider may need to convert opaque scope values such as "readCalendar" into a list of OAuthPermissions. AuthorizationCodeGrantService and OAuth2 security filters will depend on it (assuming scopes are used in the first place). In the former case AuthorizationCodeGrantService will use this list to populate OAuthAuthorizationData - the reason this bean only sees Permissions is that some of the properties OAuthPermission keeps are of no interest to OAuthAuthorizationData handlers.