Kontent.ai
Copyright © 2023 Kontent.ai. All rights reserved.
  • Web
  • Privacy policy
  • Cookies policy
  • Consent settings
  • Security
  • GDPR
  • Documentation
  • API reference
  • Product updates
Kontent.ai Learn
  • Plan
  • Set up
  • Model
  • Develop
  • Create

Protect sensitive data in custom elements

Is this page helpful?
  • Sign in
  • Martin Makarsky
    17 minutes
    Custom elements give you the opportunity to expand your Kontent.ai project beyond its built-in features. You can make your custom elements reusable and configurable in your project by entering JSON parameter properties. However, data entered in this field, including sensitive data such as API keys or credentials, can be viewed by any of your contributors, including those who do not have the permission to view sensitive data.
    To manage our products, we will use Magento's API. Because Magento exposes your management API key for your products, we want to provide a content editor with only a read-only product selector. In short, we don't want to allow a content editor to edit, create or delete products.In addition, Magento's API requires an access token while requesting their API which is also sensitive data and should not be shown in your custom element's properties. In this tutorial, you'll learn how to use and protect your sensitive data needed for custom elements by creating a server-side proxy solution to handle your sensitive information.

    Server-side proxy

    As a proxy, we will configure a simple AWS Lambda function which will receive requests from our custom element, add an authentication token, copy headers and query strings, send the request to Magento API, and then resend the response back to our custom element. Since the authentication token is stored within the AWS Lambda function, it's not accessible to contributors on your project.
    In this tutorial you will:
    • Create an AWS Lambda serverless function with API Gateway.
    • Configure the proper host and authentication token.
    • Send test request to our proxy.

    Step 1: Creating an AWS Lambda function

    First, you need an Amazon Web Services account. Visit Amazon Web Services and if you don't have an account, sign up.  Once you are logged in, select the data center that best fits your location or purpose in the upper-right corner. Expand All services and click Lambda.
    On the next screen, click Create a function.
    To create the function:
    • Select Author from scratch 
    • Name your function requestRepeater.
    • Under Runtime, select Node.js. 
    • Click Create function.
    On the function's Configuration page:
    1. Click API Gateway trigger from the left-side panel. 
    2. Navigate to Configure triggers. 
    3. Under API, select Create a new API.
    4. Under Security, select Open.
    5. Click Add in the lower right of the screen.
    6. Click Save in the upper-right corner.
    Note: For demonstration purposes, this endpoint will be publicly available and can be invoked by all users.
    Be sure to copy the API endpoint to your clipboard because you will need to enter it in Step 3. This URL is the endpoint of our proxy server. Click the requestRepeater Lambda function.
    Paste the following code in Function code.
    Click Create function and then Save.

    Step 2: Configuring your Lambda function

    In the Environment variables section, enter the following keys and values:
    • BEARER_TOKEN – Your Magento access token
    • HOST – demo1-m2.mage.direct
    • PATH – /index.php/rest/V1/products

    Step 3: Testing the proxy

    To test the Magento API via your new proxy (which is accessible through the API Gateway), enter your requestRepeater API endpoint as {Gateway API URL} and then enter the URL in your browser address bar to try the GET request:
    In our example:
    If entered correctly, you will receive a JSON response from the Magento server which was delivered via your new Lambda function.
    To learn more about Magento API, see more about our Magento integration.

    Summarized

    When you need to enter sensitive data to your custom element configuration, use an external server-side service like AWS Lambda or Azure Function. After you complete the integration with the service, you get a server-side proxy to handle your sensitive data, request and response headers, and query parameters. You can use this approach with any custom element that works with sensitive information like API keys.
    • Develop with Kontent.ai
    • Hello World
    • Hello Web Spotlight
    • Run a sample app
    • Build apps
    • Decide navigation and URLs
    • Environments
    • Get Developer Certification

    See the diagram on https://viewer.diagrams.net?lightbox=1&nav=1#Uhttps%3A%2F%2Fraw.githubusercontent.com%2FKenticoDocs%2Fkontent-docs-diagrams%2Fmaster%2Fdevelopment%2Fpassing-sensitive-information-in-custom-elements.drawio

    AWS management console
    Creating a new AWS Lambda function
    Specifying function name and runtime.
    Adding API Gateway.
    • JavaScript
    • HTTP
    • HTTP
    Communication diagram
    AWS Management Console
    Creating a new AWS Lambda function
    Specify function name and runtime.
    Add API Gateway.
    API Gateway URL.
    AWS Lambda environment variables
    JSON response
    const https = require('https');
    const querystring = require('querystring');
    
    /* ========Config Section======== */
    const host = process.env.HOST;
    const path = process.env.PATH;
    const accessControlAllowOriginValue = process.env.ACCESS_CONTROL_ALLOW_ORIGIN;
    const accessControlAllowHeadersValue = process.env.ACCESS_CONTROL_ALLOW_HEADERS;
    
    // Bearer token authentization
    const bearerToken = process.env.BEARER_TOKEN;
    
    //  Basic authentication credentials   
    const username = process.env.USERNAME;
    const password = process.env.PASSWORD;
    /* ========Config Section======== */
    
    let authorizationHeaderValue;
    
    if (bearerToken || (username && password)) {
        authorizationHeaderValue = bearerToken ?
            `Bearer ${bearerToken}` :
            `Basic ${new Buffer(username + ":" + password).toString('base64')}`;
    }
    
    const request = (queryStringParameters, headers) => {
    
        const requestOptions = {
           host: host,
           path: path,
           port: 443,
           method: 'GET',
        };
    
        if (queryStringParameters) {
            requestOptions.path = `${requestOptions.path}?${querystring.stringify(queryStringParameters)}`;
        }
    
        if (authorizationHeaderValue) {
            headers['Authorization'] = authorizationHeaderValue;
        }
    
        headers['Accept'] = 'application/json';
        headers['accept-encoding'] = 'identity';
        headers['Host'] = host;
        
        requestOptions.headers = headers;
    
        return new Promise((resolve, reject) => {
            https.request(requestOptions, response => {
                let data = '';
                response.on('data', chunk => {
                    data += chunk;
                });
                response.on('end', () => {
                    const dataObject = JSON.parse(data);
                    response.data = dataObject;
                    resolve(response);
                });
            })
                .on('error', error => {
                    reject(error);
                })
                .end();
        });
    };
    
    exports.handler = (event, context, callback) => {
    
        const corsHeaders = {
            'Access-Control-Allow-Origin': accessControlAllowOriginValue,
            'Access-Control-Allow-Headers': accessControlAllowHeadersValue
        };
    
        const repeatResponse = (response) => {
            let multiValueHeaders = {};
    
            for (const headerName in response.headers) {
                if (Array.isArray(response.headers[headerName])) {
                    multiValueHeaders[headerName] = response.headers[headerName];
                    delete response.headers[headerName];
                }
            }
    
            callback(null, {
                statusCode: response.statusCode,
                body: JSON.stringify(response.data),
                headers: { ...response.headers, ...corsHeaders },
                multiValueHeaders: multiValueHeaders,
            });
        };
    
        const sendError = (error) => {
            callback(null, {
                statusCode: '400',
                body: JSON.stringify(error),
                headers: corsHeaders,
            });
        };
    
        switch (event.httpMethod) {
            case 'GET':
                request(event.queryStringParameters, event.headers)
                    .then((response) => {
                        repeatResponse(response);
                    })
                    .catch(error => {
                        sendError(error);
                    });
                break;
            default:
                sendError(new Error(`Unsupported method "${event.httpMethod}"`));
        }
    };
    {Gateway API URL}?searchCriteria[pageSize]=10&searchCriteria[filterGroups][0][filters][0][field]=name&searchCriteria[filterGroups][0][filters][0][conditionType]=like&searchCriteria[filterGroups][0][filters][0][value]=%25watch%25
    https://vpzvj1fspi.execute-api.eu-central-1.amazonaws.com/default/requestRepeater?searchCriteria[pageSize]=10&searchCriteria[filterGroups][0][filters][0][field]=name&searchCriteria[filterGroups][0][filters][0][conditionType]=like&searchCriteria[filterGroups][0][filters][0]
    • Overview
    • Integrate search
    • Translation management systems
    • Automate content updates with webhooks
    This code covers a scenario using GET requests returning a JSON response. If you are using a different endpoint, adjust the code to meet your needs. For example, if API is only able to serve XML content types, the Accept header would have to be changed to headers['Accept'] = 'application/xml';.
    • Zapier (automation)
    • Netlify (DevOps)
    • Sensitive data in custom elements
  • Server-side proxy
  • Step 1: Creating an AWS Lambda function
  • Step 2: Configuring your Lambda function
  • Step 3: Testing the proxy
  • Summarized