Functions: Validate an API Key with API Gateway

In this tutorial, you use Oracle Functions to validate an API key passed from Oracle API Gateway. An API key is a simple method for securing an API by requiring the client to pass a specific token. The gateway can use the token as a custom authorizer to validate requests. You create a Python function that validates the token and returns an authenticated JSON response.

Key tasks include how to:

  • Gather required information.
  • Create an application for your function.
  • Create a "Hello World!" function.
  • Convert your function to validate the API key.
  • Deploy and test your function.
  • Create an API Gateway for your function
  • Call your function from the internet using your API Gateway.
The images shows OCI components used to run Oracle functions.

For additional information, see:

Before You Begin

To successfully perform this tutorial, you must have the following:

OCI Account Requirements
Software Requirements

Oracle CLI

Oracle Cloud Shell

  • If you use Cloud Shell, the preceding list of software is already installed.

1. Gather Required Information

Collect all the information you need to complete the tutorial. Copy the following information into your notepad.

Get Compartment Information

To create a compartment see Create a compartment. After your compartment is created, save the compartment OCID and name.

To get the compartment OCID from an existing compartment:

  1. Open the navigation menu and click Identity & Security. Under Identity, click Compartments.
  2. Select your compartment.
  3. Click the Copy link for the OCID field.

Save the compartment OCID and name.

Collected Information

Ensure you have the following information written down for the tutorial.

  • Compartment Name: <your-compartment-name>

    Example: my-compartment

  • Compartment ID: <your-compartment-OCID>

    Example: ocid1.compartment.oc1.aaaaaaa...

  • VCN Name: <your-vcn-name>

    Example: my-vcn

    Open the navigation menu and click Networking, and then click Virtual Cloud Networks.

  • VCN Public Subnet Name: <Public-Subnet-your-vcn-name>

    Example: Public-Subnet-my-vcn

    Open the navigation menu and click Networking, and then click Virtual Cloud Networks. Click on the VCN you created.

2. Perform Required Configuration

Perform all the configuration you need for the tutorial.

Create Functions Application

To create application, follow these steps.

  1. Open the navigation menu and click Developer Services. Under Functions, click Applications.
  2. Select your compartment from the Compartment drop-down.
  3. Click Create Application.
  4. Fill in the form data.
    • Name: <your-app-name>
    • VCN: <your-vcn-name>
    • Subnets: <Public-Subnet-your-vcn-name>
  5. Click Create.

Your app is created.

Setup Ingress Rule for HTTPS
  1. Open the navigation menu and click Networking, and then click Virtual Cloud Networks.
  2. Click the name of the VCN you used to for your Oracle Functions application.
  3. With your new VCN displayed, click your Public subnet link.

    The public subnet information is displayed with the Security Lists at the bottom of the page.

  4. Click the Default Security List link or appropriate security list link.

    The default Ingress Rules for your VCN are displayed.

  5. Click Add Ingress Rules.

    An Add Ingress Rules dialog is displayed.

  6. Fill in the ingress rule with the following information. After all the data is entered, click Add Ingress Rules

    Fill in the ingress rule as follows:

    • Stateless: Checked
    • Source Type: CIDR
    • Source CIDR: 0.0.0.0/0
    • IP Protocol: TCP
    • Source Port range: (leave-blank)
    • Destination Port Range: 443
    • Description: VCN for applications

    After you click Add Ingress Rule, HTTPS connections are allowed to your public subnet.

Setup Policy for API Gateway Access to Functions

Next, setup a policy which allows API Gateway to invoke functions.

First, create a Dynamic Group for API Gateway.

  1. Open the navigation menu and click Identity & Security. Under Identity, click Dynamic Groups.
  2. Click Create Dynamic Group.
  3. Fill in the following information to define your dynamic group.
    • Name: <name-for-your-dynamic-group>
    • Under Matching Rules use Rule 1: <the-rule-text>

    Here is sample name and the rule you need to fill out.

    • Name: api-gtw-func-dynamic-group
    • Under Matching Rules use Rule 1: ALL {resource.type = 'ApiGateway', resource.compartment.id = 'ocid1.compartment.<your-compartment-OCID>'}
  4. Click Create.

Now create the policy for API Gateway.

  1. Open the navigation menu and click Identity & Security. Under Identity, click Policies.
  2. Click Create Policy.
  3. To define your policy, fill in the following information.
    • Name: <name-for-your-policy>
    • Description: <description-for policy>
    • Compartment: <name-of-functions-compartment>

    For the Policy Builder section:

    • Click Show manual editor.
    • Enter your policy in the text box, for example:
      Allow dynamic-group  api-gtw-func-dynamic-group to use functions-family in compartment <your-compartment-name>
      Note

      The last parameter is the compartment name, not the compartment OCID.
  4. Click Create.

You have created a policy to allow API Gateway to use Functions.

Create "Hello World" Python Function
  1. Open a terminal.
  2. Create a directory to store your functions and change into that directory.
    mkdir my-dir-name
    cd my-dir-name                        
                         
  3. Create a Python "Hello World" function with Fn.
    fn init --runtime python my-func-name

    This command creates a directory named my-func-name with the function and configuration files in it.

  4. Change into the directory.
  5. Deploy the function.
    fn -v deploy --app your-app-name

    Various messages are displayed as the Docker images are built, pushed to OCIR, and eventually deployed to Oracle Functions.

  6. Invoke the function.
    fn invoke your-app-name my-func-name

    Returns: {"message": "Hello World"}

  7. Invoke the function with a parameter.
    echo -n '{"name":"Bob"}' | fn invoke your-app-name my-func-name

    Returns: {"message": "Hello Bob"}

3. Create an API Gateway

To call your function, create an API Gateway.

Create the API Gateway

To create an API Gateway:

  1. Open the navigation menu and click Developer Services. Under API Management, click Gateways.
  2. Select your compartment from the Compartment drop-down.
  3. Click Create Gateway
  4. Fill in the following information to define your API Gateway.
    • Name: <your-gateway-name>
    • Type: <Public>
    • Compartment: <your-compartment-name>
    • Virtual Cloud Network in <your-vcn-name>: <select-your-vcn>
    • Subnet in <your-compartment-name: <your-public-subnet-name>
  5. Click Create. Wait a few minutes for your API Gateway to e created.
Create an API Deployment for your Gateway

Next, create a deployment for your API Gateway.

  1. Click Deployments in Resources section on the left side of the screen.
  2. Click Create Deployment.
  3. Ensure that From Scratch is selected for the deployment type.
  4. To define your deployment, fill in the Basic Information section.
    • Name: <your-deployment-name>
    • Path Prefix (example): /tokens
    • Compartment: <your-compartment-name>
    • API Request Policies: Take default values
    • API Logging Policies: Take default value of Information
  5. Click Next. The Routes dialog appears with Route 1 selected.
  6. To define your route, fill in the Route 1 section.
    • Path: <your-route-path>

      Example: /val-token

    • Methods: GET POST
    • Type: Oracle Functions
    • Application in <your-compartment-name>: Select the Functions application you created.
    • Function Name: Select the function you created in the configuration section.
  7. Click Next. The Review dialog is displayed summarizing the choices you have made.
  8. Click Create. Your deployment is created.
  9. Click the Deployments link for your gateway. Copy the base end point for the deployment you created.

    For example: https://aaaaa.apigateway.us-ashburn-X.oci.customer-oic.com/tokens

Test your API Gateway

With your API Gateway and deployment created, you can now test you installation. Create a simple script for the curl command. To create the URL for curl, add your deployment path to your endpoint.

  1. Create the script file: touch gtw01.sh && chmod 755 gtw01.sh
  2. Add the command curl command to the script file:
    #!/bin/bash
    curl <your-api-gateway-endpoint>/val-token
  3. The command returns: {"message":"Hello World"}

You have connected your API Gateway to a boiler plate Python function. Next, you update your Python function to display information passed in an HTTP request.

4. Update Function to Validate API Key

Next, modify the boiler plate Python function to validate an API key.

Review Starting Python Code

If you look at the boiler plate function, your Python function looks something like this.

import io
import json
import logging

from fdk import response


def handler(ctx, data: io.BytesIO = None):
    name = "World"
    try:
        body = json.loads(data.getvalue())
        name = body.get("name")
    except (Exception, ValueError) as ex:
        logging.getLogger().info('error parsing json payload: ' + str(ex))
    
    logging.getLogger().info("Inside Python Hello World function")
    return response.Response(
       ctx, response_data=json.dumps(
           {"message": "Hello {0}".format(name)}),
       headers={"Content-Type": "application/json"}
    )

Using this code as a starting point, the sections that follow convert the function into a Python function that validates an API key and returns a token.

Create Functions Configuration Variable

Oracle Functions allows you to store configuration data in your context that is available in your request. Configuration data can be stored in an application or a function. The following command stores the API key in your app config.

  • fn config app <your-app-name> FN_API_KEY ABCD

The "ABCD" string is a sample key value.

For more information about setting function configuration values, see Fn Project's tutorial on runtime context.

Update Required Packages and Handler Function

First, update the func.py for required packages.

  1. Update the import statements in func.py for required packages validation:
    import io
    import json
    import logging
    import datetime
    
    from datetime import timedelta
    from fdk import response
                    

    The datetime package is used to set an expiration time for the returned token.

  2. Next, remove the main body of the function. The response method and related code are added back as we progress.
    import io
    import json
    import logging
    import datetime
    
    from datetime import timedelta
    from fdk import response
                            
    
    def handler(ctx, data: io.BytesIO = None):                
    

Now you are ready to update the function with the validation code.

Add Validation Code to Function

Next add code to validate the API key and return a token in the response. Here is the code with comments following.

import io
import json
import logging
import datetime

from datetime import timedelta
from fdk import response

def handler(ctx, data: io.BytesIO = None):
    auth_token = "invalid"
    token = "invalid"
    apiKey = "invalid"
    expiresAt = (datetime.datetime.utcnow() + timedelta(seconds=60)).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
    
    try:
        auth_token = json.loads(data.getvalue())
        token = auth_token.get("token")
        
        app_context = dict(ctx.Config())
        apiKey = app_context['FN_API_KEY']
        
        if token == apiKey:
            return response.Response(
                ctx, 
                status_code=200, 
                response_data=json.dumps({"active": True, "principal": "foo", "scope": "bar", "clientId": "1234", "expiresAt": expiresAt, "context": {"username": "wally"}})
            )
    
    except (Exception, ValueError) as ex:
        logging.getLogger().info('error parsing json payload: ' + str(ex))
        pass
    
    return response.Response(
        ctx, 
        status_code=401, 
        response_data=json.dumps({"active": False, "wwwAuthenticate": "API-key"})
    )
  • The handler function receives system information about the current request through the ctx and data parameters.
  • The function assumes that FN_API_KEY is set in the app configuration. Then the function compares the configuration value to the token value sent from the curl POST request: -d '{"token":"ABCD"}'.
  • If the values match, a validation JSON message is returned. If they do not match, a 401 code is returned with JSON error data.

The function code is complete. You are now ready to test the function.

Test your Function
  1. Redeploy the updated function.
  2. Invoke the function to ensure that the function is working.
  3. Update the gtw01.sh script to pass the POST data to the script.
    /bin/bash
    curl -X POST -d '{"token":"ABCD"}' https://aaaaa.apigateway.us-ashburn-X.oci.customer-oic.com/tokens/val-token
  4. Run the script: gtw01.sh | jq
  5. If the API keys match, the output from the script looks similar to:
    {
        "active": true,
        "principal": "foo",
        "scope": "bar",
        "clientId": "1234",
        "expiresAt": "2020-12-16T22:48:50+00:00",
        "context": {
        "username": "wally"
        }
    }

    If the API keys do not match, an error message is returned.

    {
        "active": false,
        "wwwAuthenticate": "API-key"
    }                        
                        

    You can download the complete source code for the function from the Oracle Function Samples site here.

Congratulations, you have converted the boiler plate Python function into a new function that validates an API key. The function demonstrates how data can be passed to API Gateway and processed in a function.