Taking some REST (part 2)
Gepubliceerd: Auteur: Milco Numan Categorie: Oracle[Deze blog is alleen in het Engels beschikbaar]
In my previous post I have described the basic setup for my Proof of Concept project, discussed the basics of REST and the OAuth 2.0 variant I have selected.
As explained, in a regular OAuth 2.0 scenario, this would usually start with an application- or web-based interaction, where the user would be directed by the requesting application to a consent screen of the “resource owner” (that’s Google in this case, this is the entity that owns the task resources I want to be authorized to access on behalf of the user).
So the services described in this part of the blog start after this consent in the form of an “authorization code” has been provided.
Data Structures
As Frederick Brooks wrote in his classic “The Mythical Man Month” – Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowchart; it’ll be obvious. – I will start here with the definitions of the data structures.
External Service Interfaces
Next, there is an operation defined for getting hold of the token:
Next, there is an operation defined for getting hold of the token:
Caching the tokens
As the token information is usually accessed a number of times in short succession, it makes sense to store the token temporarily in a cache. Luckily, SOA Suite 12c comes with a Coherence Adapter, enabling you to easily store XML objects (or POJOs – Plain Old Java Objects) in a designated memory store for fast and easy access.
Ideally, I would store the refresh token obtained from redeeming the authorization code in a persistent data store (like a database) and the transient access token, usually expiring within an hour of obtaining it, in the Coherence cache. However, for this POC it does not matter too much and I decided for the sake of simplicity to store the token as a whole inside the Coherence cache, using the concatenated Issuer (who issue the token – Google) and the owner as the key.
The XML object stored for the token is structured like in the diagram below:
Business Services
The data structures for the invoked Google OAuth 2.0 APIs (the business services) are shown below; these structures are NXSDs, XML Schema representation of – in this case – the JSON payloads for the services:
Accessing the APIs
Invoking external services in Service Bus is done by means of a Business Service; in this case, the business service will be created by defining a REST adapter, since the service I’d like to invoke is a REST service:
One of the things to keep in mind when defining a REST adapter is that Oracle will only allow you to use the same HTTP method once per unique REST resource, otherwise it will respond with an error message – that’s why I have created separate business services for redeeming and refreshing, since both use a POST operation against the resource /oauth2/v4/token.
One of the things to keep in mind when defining a REST adapter is that Oracle will only allow you to use the same HTTP method once per unique REST resource, otherwise it will respond with an error message – that’s why I have created separate business services for redeeming and refreshing, since both use a POST operation against the resource /oauth2/v4/token.
Redeeming the Authorization Code
When redeeming the authorization code, Google’s OAuth API expects to receive a URL-encoded POST request message (without payload) and returns a JSON response object; the data structures map onto those described for the business services:
Mapping the inbound request XML to URL-encoded parameter for the outbound request for redeeming the AuthorizationCode for tokens
The outbound request (that is the request to the Google API is sent as an HTTP POST, where the parameters are passed on the URL – there is no payload). For the response however, a JSON payload is received back from the API and this needs to be mapped to an XML data structure to be processed by Service Bus:
Mapping the inbound JSON reply to an XML structure by simply specifying the reply element
The mapping for refreshing the access token follows the same pattern, where the outbound request is mapped onto URL-encoded parameters and the inbound response is mapped from JSON to an XML data structure.
TokenManagement Flows
The finished TokenManagement project has the following structure:
As you can see in the picture above, the project has a single (exposed) endpoint proxy service, exposing three operations to:
- Redeem the initial authorization code (into an access token)
- Refresh an existing (cached) access token
- Retrieve an existing access token
Validating the requests
It is a good practice to reject invalid messages as early as possible, so usually one of the first steps in a properly implemented service bus flow is validating the actual request message against the XML Schema Definition:
Validating the requests
It is a good practice to reject invalid messages as early as possible, so usually one of the first steps in a properly implemented service bus flow is validating the actual request message against the XML Schema Definition:
As you can see, I have not used an if-then-elsif construct to statically validate the different possible request messages for the three operations, but I preferred to hide this behind an XQuery to implement dynamic validation; the XQuery needs to return a prescribed XML structure holding the schema to use for validation, the namespace for the element and the actual element name itself:
xquery version "1.0" encoding "utf-8"; (:: OracleAnnotationVersion "1.0" ::) declare variable $operation as xs:string external; declare function local:func($operation as xs:string) { <validate xmlns="http://www.bea.com/wli/sb/context"> <schema>TokenManagement/Schemas/Authorization</schema> <schemaElement> <namespaceURI>http://xmlns.qualogy.com/blog/mnuman/OAuth</namespaceURI> <localname>{ if ($operation = 'RedeemAuthorizationCode') then 'RedeemAuthorizationTokenRequest' else if ($operation = 'RefreshAccessToken') then 'RefreshAccessTokenRequest' else if ($operation = 'GetAccessToken') then 'GetAccessTokenRequest' else 'CANNOTBEPROCESSED' } </localname> </schemaElement> </validate> }; local:func($operation)
After validating the request (and only if the validations completes successfully – otherwise an error will be raised, resulting in a SOAP Fault being returned to the caller), the message will be dispatched using an operation branch (yes, I could have used dynamic routing here as well – but I wanted to show how cluttered the flow can become by these parallel options):
Redeeming the AuthorizationCode
Redeeming the authorization code simply works by performing the callout to the Google OAuth API; the inbound request is transformed for the business service by using an XQuery function, where an XQuery library module provides the common values for all requests, like the client_id and the client_secret.
After successfully obtaining the access tokens, a cache token is created from it and this is stored in-memory using the Coherence Adapter, specifying a manual key composed of the concatenation of Issuer and Owner fields (from the request). The service itself only returns the access token to the caller.
Refreshing the Access Token
Since the access tokens usually expire after some time (for Google this is one hour), the tokens need to be refreshed occasionally. The operation for refreshing the access token first retrieves the currently cached token from memory, as this contains the special token needed for obtaining a new, refreshed token. If this token is not found in the cache, a SOAP Fault is returned. If the token is found, this is used to build an outbound request for the Google API, again leveraging the XQuery Module inside another XQuery for building the request. After a successfull request, the renewed access token is stored in the in-memory cache and the access token itself will be returned by the service.
Getting the Access Token
The implementation for getting the access token first retrieves the access token from the in-memory cache; this operation can have three different outcomes:
- the Cache token cannot be found: this situation will trigger a SOAP Fault to be returned
- the Cache token is found, but the system date is beyond the expiration date: in this cache, the access token will be returned from the refresh access token that is called in this location
- the Cache token is found and still valid: this is the simple scenario where the access token will be extracted and returned.
Summary
In this blog post I have shown how to do some basic token management for Google’s OAuth 2.0 implementation, caching the tokens in-memory for performance (robustness requires a more hybrid approach). The operations are exposed internally, in our service landscape, as SOAP Services accessible over HTTP.
In the third and final part of this series, I will show how to actually use these OAuth 2.0 tokens to invoke some secured resources from Google. You can read more about this here.