The Cloud Native Approach to Extending your SaaS Applications

December 20, 2019 | 12 minute read
Angelo Santagata
Architect
Text Size 100%:

Introduction

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.

Solution

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:

  • Entire solution infrastructure must be Oracle managed
    • SaaS Customers love Oracle Managed ?
  • Must implement REST Verbs (GET/PUT/POST etc)
    • VBCS primarily uses REST Services
  • Must support identity propagation to the target SaaS system
    • Within the middle tier our code may need to change the business logic based on the user calling it
  • API Endpoints must be secured 
    • We need to ensure that the endpoints are secured
  • Performance is important, the overhead of Functions cannot be more than 200ms
  • Must implement CORS (Cross Origin Resource Sharing)
    • This is required so that we can issue REST calls direct to the REST Server and not be forced go via the origin server where the HTML / VBCS was downloaded from.

Final Design

Alas the previous architecture diagram is not sufficient to implement what we need. Oracle Functions by itself will not be able to fulfil our requirements because of the following.
  1. Oracle Functions are not REST enabled, they have no concept of REST verbs like GET/PUT/POST etc
  2. Oracle Functions are not protected by IDCS, and have no concept of users, roles etc. 
To resolve this, we needed to add a couple of services to our diagram.
  1. IDCS: A global Identity store
  2. Oracle OCI API Gateway (OCI API GW):  This is an API gateway which will handle the REST to Functions mapping and enforce security.

 

How does the overall flow work?

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.

Oracle IDCS

For authentication to work properly we need to be using Oracle IDCS and this should be the same Oracle IDCS that Oracle Fusion and Oracle VBCS (or your HTML5 app) are using. Additionally within IDCS we need to create a confidential application which we can authenticate our client (eventually VBCS) against.

Within IDCS Creatre a confidential application and ensure the client configuration is setup so that it enables the grants Client Credentials and JWT Assertion

  • Within Client Configuration "Token Issuance Policy Section" you need to add resources from Oracle Fusion Applications. This can be done by clicking the "+ Add Scope" button and then selecting the resources within the Oracle Applications Cloud (Fusion) section.

  • No other changes are required however after saving ensure the application is “Activated” by pressing the activate button.

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

Oracle OCI API Gateway 

OCI API Gateway in the architecture acts as our HTTP front end to incoming requests.  The gateway provides a number of useful features for managing APIs, for our use case we are going to use the following features:
  • 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:

  1. Lines 2-9: A authentication request policy, this is the definition which is effectively saying “for any request in this deployment, first call this function (functionId), passing the tokenHeader and if it returns “true” then all good, otherwise respond with a HTTP unauthorized.
  2. Lines 10-31: Two URL entry points/VERBs to individual functions in FaaS. 
  {
    "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.

The “Authentication Function”

Oracle API gateway does not natively support IDCS as a native authentication provider (This is a feature our development organisation is looking to implement OOTB in the future). The gateway does however allow the definition of a “function” that it can call, passing it the incoming authorization header, if the function returns true then the function call is allowed, conversely if it returns false then the request is rejected with a HTTP 401 error code. 
The function is provided in source code format and is deployed within Functions, which is then referenced in the OCI API GATEWAY deployment file via its OCID. You can discover the OCID by navigating to the deployed function in the console and expanding its OCID column.
Before the function can be deployed the developer needs to edit one file (ResourceServerConfig.java), this defines how the function connects to IDCS and which IDCS oAuth application is used. 
 

 

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. 

Our Functions Business Logic

The crux of the implementation is to put some code into a function. This code could be as complex as needed, calling multiple endpoints perhaps performing some aggregation and this is the code we want to be called by OCI API Gateway.

 

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. 
 

How does one get hold of the current user username and roles?

When a function is called by OCI API Gateway the gateway injects some metadata into the call using HTTP Headers, these headers are accessible by using the functions SDK. 
For example, in the following snippet of Java code, we extract out the authentication token, decode it and then return it as part of the body to the caller.
public OutputEvent handleRequest(InputEvent rawInput) {
        Optional optionalToken=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”.

What about configuration parameters?

The Oracle Functions environment provides a very useful piece of functionality where you can define some parameters within the OCI environment and then reference it from your code. In our use case we are going to set the Fusion URL and an OverrideJWT token URL as parameters which we will use in our code.

 

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);
    }

How do I execute a REST Call against Fusion using the same user’s credential as the one who executed the OCI API GW request?

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();

Calling the API Gateway from Oracle VBCS

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.
 

Conclusion

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:

  • How to expose an Oracle Functions as a Service function as REST end point
  • How to secure the endpoint using IDCS as the authentication provider
  • How to implement business logic using metadata in the code (user, role, URI path, HTTP Method etc)
  • How to call an external REST service from the java function using a popular java library

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 

 

Angelo Santagata

Architect

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

Continuous deployments with Skaffold on Oracle Cloud Infrastructure Container Engine for Kubernetes (OKE)

Roland Koenn | 9 min read

Next Post


Deploying Oracle Data Integrator Marketplace in a Public Subnet with Autonomous Database

Dayne Carley | 11 min read