As businesses adopt more SaaS, the ability to extend to meet changing business requirements becomes more important. Before cloud-native, IT teams were faced with the realities of either selecting a set of vendors offered integration solutions, providing simplistic use cases, or setting up rather large stacks of infrastructures to support their more complex use-cases.
Cloud-native introduces capabilities such as containers, and serverless functions. By using these offerings, development teams can focus on completing their use-case while offloading the complexity of the infrastructure to their cloud provider. Oracle Cloud Native Services provides the capabilities that development teams need to extend their SaaS in a manner that is much easier to implement and maintain.
Oracle Functions is a new functions as a service (FAAS) offering from Oracle which allows developers the ability to execute code in the cloud and only pay for the processing power used, thus avoiding the need to run the service 24x7. The additional advantage is that the infrastructure needed to run the code is fully Oracle managed and does not require the customer/developer to maintain the runtime environment.
When using the new Oracle Cloud Infrastructure API Gateway service with Oracle Functions, development teams can create serverless APIs that unlock the power of serverless functions to applications such as SaaS that easily integrate with RESTful APIs
If you compare this to an Oracle Java Cloud/WebLogic cloud instance, where the customer is responsible for the maintenance and management of the software, the associated operating system hosting the WebLogic Server and often a database, this “Fully Oracle Managed approach” is very attractive.
One target audience could be our current JCS/SX fleet of customers. These customers often use JCSSX to host SaaS UI Extensions, which are often built using the Oracle ADF Framework. If these customers wished to move platform, then a migration to VBCS is the strategic migration route for these customers.
Another common use case for JCS/SX is to host some code (A REST entry point) which is either called by Oracle Fusion or another Application.
The rest of this article goes through a real-world customer scenario. This customer currently has their application deployed on Java Cloud SaaS Extensions and is using the Oracle ADF Faces framework. The customer wishes to migrate to a more modern platform and the platform must be Oracle managed – after all they are a SaaS customer and are used to things being managed for them.
They have therefore decided to use the Oracle Visual Builder Cloud Service (VBCS) framework and after some investigation they have determined that 90% of the network requests will go direct to Oracle SaaS, however 10% of the calls will need to go through a middle tier. This customer’s requirements are because they wish to do some complex business logic in the middle tier before calling Oracle SaaS, however other valid reasons include caching, aggregation of data, complex logic and perhaps protocol conversion (REST to SOAP).
For this customer we are proposing that they use Oracle Functions as the middle tier for their 3GL code. It has the advantage that it will only cost when it is actually being used and considering only 10% of the requests need to go via the middle tier this could mean the middle tier cost is quite small.
For this solution to work we will require the following functionality from the middle tier:
When a request comes into the OCI API Gateway Endpoint, the gateway first executes the authentication method, if this returns true then OCI API GW redirects the request to the business logic function associated with the route/HTTP Method, passing all the parameters/headers/method to the function as part of the method parameters.
Within IDCS Creatre a confidential application and ensure the client configuration is setup so that it enables the grants Client Credentials and JWT Assertion
If you have a tool like postman (http://postman.com), you can test this is all working by creating a dummy REST request and configuring it to be secured by oAuth 20 and then requesting a authentication token via the built in “get new access token” functionality of Postman. The grant type we will use within postman is "Password Credentials" , and you need to supply the username/password, but in real life we'd be using Client Credentials where the client will provide the user information.
The access token URL is obtained by talking the IDCS hostname and appending /oauth2/v1/token
e.g. https://<your IDCS Hostname.identity.oraclecloud.com>/oauth2/v1/token
Mapping of URLs, and REST VERBS, to Oracle Functions using the functions OCID
Enforcing Security by only allowing authenticated users the ability to execute functions
Implementing CORS headers
When creating the API gateway in the cloud console ensure it is created as a public gateway and associated with a VCN.
This is an example OCI API Gateway function definition, within this definition file we can see:
{ "requestPolicies": { "authentication": { "type": "CUSTOM_AUTHENTICATION", "isAnonymousAccessAllowed": true, "functionId": "OCID1.fnfunc.oc1.phx.xxxxxxxxxxxxxxx", "tokenHeader": "Authorization" } "cors":{ "allowedOrigins": [], "allowedMethods": [ ], "allowedHeaders": [ ], "exposedHeaders": [ ], "isAllowCredentialsEnabled": , "maxAgeInSeconds": } }, "routes": [ { "path": "/opportunities", "methods": [ "GET" ], "requestPolicies": {}, "backend": { "type": "ORACLE_FUNCTIONS_BACKEND", "functionId": "OCID1.fnfunc.oc1.phx.xxxxxxxxxxxxxxx" } }, { "path": "/opportunities/{optyid}", "methods": [ "PATCH" ], "requestPolicies": {}, "backend": { "type": "ORACLE_FUNCTIONS_BACKEND", "functionId": "OCID1.fnfunc.oc1.phx.xxxxxxxxxxxxxxx" } } ] }
Within OCI you can create the gateway but you cannot deploy the API deployment file (API Gateway defines its APIs using a JSON file called API Deployments) until you’ve deployed the function as you will need the functions OCID. This is only available after the function has been deployed and remains the same for redeployments.
public class ResourceServerConfig { //YOUR IDENTITY DOMAIN AND APPLICATION CREDENTIALS public static final String CLIENT_ID = "xxxx"; public static final String CLIENT_SECRET = "xxxxx"; public static final String IDCS_URL = "https://idcs-xxx.identity.oraclecloud.com"; //INFORMATION ABOUT THE TARGET APPLICATION public static final String SCOPE_AUD = "urn:opc:resource:fa:instanceid=501840138urn:opc:resource:consumer::all"; //INFORMATION ABOUT IDENTITY CLOUD SERVICES public static final String JWK_URL=IDCS_URL+"/admin/v1/SigningCert/jwk"; public static final String TOKEN_URL=IDCS_URL+"/oauth2/v1/token"; //PROXY public static final boolean HAS_PROXY = false; public static final String PROXY_HOST = "http://my.proxy.com"; public static final int PROXY_PORT = 80; }
E.g. fn deploy -app fnsaaspoc
You can find the functions OCID by navigating to the function in the OCI Console and viewing the OCID field.
In this example we are going to simply call an Oracle Fusion Sales Cloud REST API, query some data, manipulate it and then return it to the user. When writing the function itself you can use any suitable framework you desire but the developer needs to be aware of the impact of a framework for serverless functions. In this example we have used Java as the language and the Apache HttpClient library to connect to the REST Service. The Apache library was chosen because it was to use and easy to implement, however in Java 11 we now have the new http client which could also be used.
You should also avoid frameworks which instantiate a lot of in-memory objects when calling REST APIS, these objects will be discarded on each call and therefore slowing down the function’s execution.
public OutputEvent handleRequest(InputEvent rawInput) { OptionaloptionalToken=rawInput.getHeaders().get("Fn-Http-H-Authorization"); if (!optionalToken.isPresent()) { throw new Exception("No Authentication Bearer token found"); } String jwtToken=optionalToken.get().substring(TOKEN_BEARER_PREFIX.length()); String[] split_string = jwtToken.split("\\."); String base64EncodedHeader = split_string[0]; String base64EncodedBody = split_string[1]; String base64EncodedSignature = split_string[2]; byte[] decodedJWT = Base64.getDecoder().decode(base64EncodedBody); try { String JSONbody = new String(decodedJWT, "utf-8"); ObjectMapper mapper = new ObjectMapper(); JsonNode root=mapper.readTree(JSONbody); username=root.get("sub").asText(); System.out.println(“Username = “+username); } catch (Exception e) { Throw new Exception (e.getMessage()); } Return OutputEvent.fromBytes( username.getBytes(), // Data OutputEvent.Status.Success,// Any numeric HTTP status code can be used here "text/plain");
The username can be used within the function to implement business logic as required. Roles are not available in the Authentication token however they can be queried from IDCS using REST APIs. We will cover these apis in a future article with some sample code.
Other useful headers available using the Functions Developer kit include:
Fn-Http-Method | The method used to call the function GET/POST/PUT etc) |
Fn-Http-Request-Url | Fn-Http-Request-Url The URL used to call the function, this includes the query parameters |
Fn-Http-H-
|
All Headers are passed using the prefix Fn-Http-H- |
Note: Using a combination of Fn-Http-Method and Fn-Http-Request-Url you could implement a router within your code so that your function does different related things based on how it was called (e.g. PUT, PATCH etc). This approach is often called the “Serverless Service” pattern and has the advantage that the developer has to maintain “fewer” functions and each function has a little router which determines what it needs to do. This approach can be used but some developers view this approach as an “anti-pattern” as one school of thought is that one function should implement only implement one “unit of work”.
Within the code you can access these configuration variables, using the Functions SDK by using a special Java annotation ( @FnConfiguration) and accessing the parameters through the context variable.
private String jwtoverride = ""; private String fusionHostname = ""; private String fnURIBase ="/fnsaaspoc/opportunities"; /** * @param ctx : Runtime context passed in by Fn */ @FnConfiguration public void config(RuntimeContext ctx) { fusionHostname = ctx.getConfigurationByKey("fusionhost").orElse("NOTSET"); jwtoverride = ctx.getConfigurationByKey("overridejwt").orElse("NOTSET"); LOGGER.info("Configuration read : jwt=" + jwtoverride + " saasurl=" + fusionHostname); }
When the function is called it will also have been sent an “authorization” header variable which would contain an authentication token. This token is generated by the calling applications identity server which is the same identity server that our OCI API gateway function is using to validate the request. We query this token and reuse it in the outbound call to our destination server (Fusion Applications) and it (Fusion Applications) will execute the call as the user in VBCS.
The following code snippet uses the Apache HTTP library and calls the Fusion REST API using the authentication token passed in.
String fusionURL=””; HttpClient client = HttpClients.custom().build(); HttpUriRequest request = RequestBuilder.get().setUri(fusionURL). setHeader(HttpHeaders.CONTENT_TYPE, "application/json"). setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken). build(); HttpResponse response = client.execute(request); responseJson = EntityUtils.toString(response.getEntity()); status = response.getStatusLine().getStatusCode();
The last mile in our voyage is to call the API from a HTML5 client, in our case we are using the Oracle VBCS service. Within VBCS the service endpoint is configured just like any other oAuth endpoint. Based on our IDCS configuration we will be using the oAuth mechanism called “User Assertion oAuth 2.0”. This is because VBCS is a trusted client of IDCS, it will proxy the logged-on user of VBCS to IDCS, with the “clientid”, and “client secret”, as parameters. IDCS will then return an access token VBCS can then use to call the endpoint (our function, via the gateway). This token can then also be used to call a Oracle Fusion Applications REST URL as the authenticated user from VBCS.
Performance HINT: If you have enabled CORS in the API Gateway definition and you are using HTTPS then you can set the “token relay” flag to true. If this flag is true then VBCS will route all REST traffic direct to the REST service instead of going via the VBCS server - a great optimisation technique providing you meet the requirements.
Once this is done you can then build a VBCS application as per normal and enjoy all the lovely cloud native functions for the REST calls.
This article describes how a developer can use Oracle Functions as the backend implementation of a REST API which in turn is secured by IDCS. This is accomplished by using the newly released Oracle OCI API Gateway and Oracle IDCS as part of the solution. This article was written using a SaaS extensibility use case, but the same techniques can be used with or without SaaS in the picture.
The article has explored and explained the following topics:
Fully working sample code on which this article is based on is available in the Oracle official github repository here https://github.com/oracle/cloud-asset-fusion-serverless-vbcs-sample
25+ years of Oracle experience, specialising in Technical design and design authority of Oracle Technology solutions, specialising in integrating technology with Oracles SaaS products.
Extensive credible communication skills at all levels, from hard core developers to C-Level executives.
Specialities: Oracle Fusion Apps Integration, Oracle Cloud products, SaaS Integration architectures, Engaging with SIs & ISVs, Technical Enablement, Customer Design Reviews, advisory and project coaching.
TOGAF 9 Architect and Oracle Cloud Infrastructure Architect Certified
Previous Post
Next Post