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.