Develop Functions
Before you deploy an API Gateway deployment, you'll need to develop and deploy your functions.
About 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 or perhaps performing some aggregation. The business logic function is the code that will be called when needed by Oracle Cloud Infrastructure API Gateway.
In this example architecture, the API Gateway calls an Oracle Function, which in turn queries some data from Oracle Fusion Applications Cloud Service via the REST API, manipulates it, and returns it to the user. When writing the function itself you can use any suitable framework you desire, but you need 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 easy 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, because these objects will be discarded on each call and therefore slow down the function’s execution.
About Getting Usernames and Roles
When a function is called by Oracle Cloud Infrastructure 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 (excerpted from the class
                JWTUtils in the example code provided with this solution playbook),
            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<string> 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 Oracle Identity Cloud Service using REST APIs.
When Oracle API Gateway calls the function, a number of useful headers are also sent. These headers can be read using the function's API. Useful headers available from the Functions Developer Kit include:
| Fn-Http-Method | The method used to call the function (GET/POST/PUT etc.). | 
| Fn-Http-Request-Url | The URL used to call the function; this includes the query parameters. | 
| Fn-Http-H-User-Agent | The client details such as operating system, vendor,and version. | 
For example, 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 (such as 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.
public OutputEvent handleRequest(InputEvent rawInput, HTTPGatewayContext hctx) throws JsonProcessingException {
  String httpMethod = hctx.getMethod();
  String httpRequestURI = hctx.getRequestURL();
   // etc
}   About Calling Oracle Fusion Applications Cloud Service Using a JWT Assertion Access Token
You need to implement the JWT Assertion from Oracle Functions by using the subject in the incoming Authorization header.
The sample Function provided with this solution playbook can perform this security
                process by using a helper library named idcsOAuthAsserter. The
                helper library performs the full OAuth Assertion flow by performing an exchange of
                bearer tokens before invoking Oracle Fusion Applications Cloud
                                Service. This library is integrated with the sample Function.
                     
The Function requires the private key and the public certificate in order to build the user and client assertions used to invoke Oracle Identity Cloud Service to create a new bearer access token using OAuth JWT Assertion.
The idcsOAuthAsserter library requires some properties
                that you can define in the Function Configuration. All of the variables in the
                following table are mandatory:
                     
| Config name | Description | Example | 
|---|---|---|
| IDCS_URL | Your Oracle Identity Cloud Service instance URL | https://<your identity cloud hostname.identity.oraclecloud.com> | 
| CLIENT_ID | Your Oracle Identity Cloud Service Application Client ID associated with Oracle Functions and Oracle API Gateway | 1a2b3c4d5e6f7g8h9i01j2k3l4m5o6p7 | 
| KEY_ID | Alias of the certificates imported to the Trusted Oracle Identity Cloud Service Application | fnassertionkey | 
                              
| SCOPE | This scope should match with the target OAuth resource, which is the Oracle Identity Cloud Service Application associated with your Oracle Fusion Applications Cloud Service | urn:opc:resource:fa:instanceid=xxxxxxxxxurn:opc:resource:consumer::all https://my_fusion_hostname:443/ | 
| AUDIENCE | Audiences for the Assertion process. Separate multiple values with commas. | myhostname.identity.oraclecloud.com, https://myfusionservice.dc1.oraclecloud.com | 
| IDDOMAIN | Name of the Oracle Fusion Applications Cloud Service Instance tenant | mytenant | 
The function will also require configuration properties to access
                secrets for assertion related to idcsOAuthAsserter. The JWT
                Assertion requires a certificate and private key to generate the client and user
                assertions. The function retrieves the keystore with the OCID specified in
                    V_KEYSTORE. The alias to retrieve that information should match
                with KEY_ID value in the configuration. The passphrase for both
                keystore and privatekey should be retrieved from the Oracle Cloud Infrastructure Vault Secrets Service using the OCIDs specified in V_KS_PASS and
                    V_PK_PASS.
                     
| Config Name | Description | Example | 
|---|---|---|
| V_KEYSTORE | Secret that contains the secure stored content of the keystore | ocid1.vaultsecret.oc1.dc1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 
| V_KS_PASS | Secret that contains the secure stored password for the keystore | ocid1.vaultsecret.oc1.dc1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 
| V_PK_PASS | Secret that contains the secure stored password for the private key | ocid1.vaultsecret.oc1.dc1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 
An additional supported configuration value is the property
                    USE_CACHE_TOKEN, which is set to True by
                default. This property allows you to store the generated Oracle Identity Cloud
                                Service assertion token to be reused in future invocations of Oracle Fusion Applications Cloud
                                Service, for as long as the token remains valid. The cached token is validated before use
                by comparing the Subject with the incoming token and verifying the expiry time to
                check if it is still valid. If not, a new access token is requested using the OAuth
                Assertion. This feature can be disabled by setting USE_CACHE_TOKEN
                to False.
                     
The Function to be implemented can use SecurityHelper object from
                the idcsOAuthAsserter library to extract the subject from the
                    access-token header, generate a new Bearer access token with
                OAuth JWT Assertion, and send a request to Oracle Fusion Applications Cloud
                                Service using the new access token as Authorization header.
                     
The Function saasopportunitiesfn in the source code sample is
                already integrated with the idcsOAuthAsserter library. The
                following code snippet is available in the handleRequest metdhod of
                the sample Function, which shows how to initialize the objects of
                    idcsOAuthAsserter and perform an exhange of tokens using
                assertion with Oracle Identity Cloud
                                Service:
                     
// Full Oauth scenario Perform exchange of tokens
if(fullOAauth) {
    LOGGER.log(Level.INFO, "Full Oauth Assertion scenario - Perform exchange of tokens");
    SecurityHelper idcsSecurityHelper = new SecurityHelper(context)                   // Initialize SecurityHelper with RuntimeContext
                                        .setOciRegion(Region.US_PHOENIX_1)            // Specify the OCI region, used to retrieve Secrets.
                                        .extractSubFromJwtTokenHeader(rawInput);      // Extracts the subject from Token in Fn-Http-H-Authorization.
 
    // Get OAuth Access token with JWT Assertion using the principal extracted from Fn-Http-H-Access-Token Header
    jwttoken = idcsSecurityHelper.getAssertedAccessToken();
    LOGGER.log(Level.INFO, "Successfully token retrived with IDCS Assertion");
    LOGGER.log(Level.FINEST, "Access Token from assertion [" + jwttoken + "]");
}
                     Notice in this snippet that the object SecurityHelper is initialized
                with a context object of type RuntimeContext. This will be used to
                initialize the SecurityHelper with the configuration for
                    idcsOAuthAsserter Helper Library.
                     
The example saasopportunitiesfn Function is set to use the
                    US_PHOENIX_1 region, so you should change the region in the
                method setOciRegion shown in the snippet to point to your
                region.
                     
SecurityHelper also has the method
                    extractSubFromJwtTokenHeader that takes the
                    InputEvent object from the handleRequest
                Function method to extract the Bearer token that comes in the API Gateway
                    Authorization header. Then you should be able to retrieve an
                Access Token as result of Oracle Identity Cloud
                                Service Assertion.
                     
For more information about the idcsOAuthAsserter usage and
                integration with a Function, review the README file for
                    idcsOAuthAsserter in the code repository with the downloadable
                code sample accompanying this solution playbook.
                     
Set Configuration Parameters
The Oracle Functions environment provides a very useful piece of functionality where you can define some parameters within the Oracle Cloud Infrastructure environment and then reference them from your code.
In this use case you will set the Fusion and an OverrideJWT token URLs,
                and a flag named full_oauth, as parameters which are used in your
                code. To add parameters:
                     
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");
        fullOAauth = Boolean.parseBoolean(ctx.getConfigurationByKey("full_oauth").orElse("false"));
        LOGGER.info("Configuration read : jwt=" + jwtoverride + " saasurl=" + fusionHostname);
    }
                     In addition, since this solution uses the idcsOAuthAsserter helper
                library, you need to provide in the Function Configuration the specific variables
                described in the previous section to use the Oracle Identity Cloud
                                Service OAuth assertion to exchange access tokens. Because this process requires several
                mandatory configurations, we suggest that you set your Function configuration using
                the yaml file approach. For example:
                     
config:
  AUDIENCE: <AUDIENCE_VALUES>
  CLIENT_ID: <YOUR_CLIENT_ID>
  IDCS_URL: <YOUR_IDCS_URL>
  IDDOMAIN: <YOUR_FA_TENANT_NAME>
  KEY_ID: <YOUR_IDCS_URL>
  SCOPE: <FA_RESOURCE_SCOPE>
  V_KEYSTORE: <YOUR_KS_OCID>
  V_KS_PASS: <YOUR_KSPASS_OCID>
  V_PK_PASS: <YOUR_PKPASS_OCID>
  fusionhost: <value>
  overridejwt: <value>
  full_oauth: <value>
                     Pass User Authentication Token to Fusion Applications
Your function needs to handle the user authentication token for secure REST API interactions.
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 application's identity server which is the same identity server that your Oracle Cloud Infrastructure API gateway function is using to validate the request.
The approach described in this solution playbook encorages you to use the Oracle Identity Cloud
                                Service OAuth JWT Assertion to perform the exchange of tokens using the
                idcsOAuthAsserter helper library. This provides an additional
            security layer for the invocation from Oracle Functions to Oracle Fusion Applications Cloud
                                Service. As shown in the architecture diagram, the Function with OAuth JWT Assertion
            queries for the token that comes in the inbound call from API Gateway header and uses it
            to be exhanged for another token during the Assertion process with Oracle Identity Cloud
                                Service. This new token will be used in the outbound call to your destination server  (Oracle Fusion Applications Cloud
                                Service) and it (Oracle Fusion Applications Cloud
                                Service) will execute the call as the user in Oracle Visual Builder.
                  
In the provided sample function (saasopportunitiesfn), there is a
            configuration named full_oauth, which by default is set to
                True and the behavior will be the described above. 
                  
Optionally, you can set  full_oauth to False. In this
            case, the Function queries the token that comes in the inbound call from API Gateway
            header and reuses it in the outbound call to Oracle Fusion Applications Cloud
                                Service. No second token is generated.
                  
The following code snippet uses the Apache HTTP library and calls the Oracle Fusion Applications Cloud Service REST API using the authentication token passed in.
String fusionURL=”<yourfusionresturl>”;
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();Compile and Deploy Your Function
In Oracle Functions, create an application for your function, and then deploy the function.
Optionally Define an Authentication Function in Oracle Cloud Infrastructure
Oracle Cloud Infrastructure API gateway natively supports IDCS as an authentication provider. However, the gateway also allows the definition of a function that it can call. Optionally, you can create a custom authentication function using this feature.
The custom authentication function receives a call from the gateway, 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 you
                need to edit the file ResourceServerConfig.java; this defines
                how the function connects to Oracle Identity Cloud
                                Service and which Oracle Identity Cloud
                                Service OAuth application is used. 
                     
The function example below avoids hardcoding sensitive values such as
                the client secret. In the four lines beneath the comment // KMS Key for IDCS
                    Client Secret, the code decrypts the OAuth secret using the Oracle Cloud
                                Infrastructure key management feature, Oracle Cloud Infrastructure Vault. You enter these values in the Oracle Cloud
                                Infrastructure console before deploying the function.