top of page

Tutorials and Code Examples

Securing Your Site: Keeping Your Custom API Secure

Wed Apr 20 2022

2.gif
Blank_Canvas_on_Transparent_Background.p

Share

  • Twitter
  • Black LinkedIn Icon
  • Facebook

HTTP Functions are one of a few ways to use Velo to extend the functionality of the backend portion of your Wix site. Some others are...

Updated: Apr 25, 2022

HTTP Functions are one of a few ways to use Velo to extend the functionality of the backend portion of your Wix site. Some others are data hooks, job schedulers, and web modules, all of which have their own uses.


HTTP Functions in particular are used to create Application Programming Interfaces (API), which allow third-party sites to use the backend of your site in a clean, modular way. Where a human user might primarily interact with the frontend of your website, APIs make the backend of your website accessible to third-party clients in code, so that these sites can read data from your database collection, write to that collection, submit order information, and do a variety of other tasks supported by Velo and JavaScript. They can be a powerful tool in your web development toolkit, and their power requires that they be properly secured.


Secure Communication Between Hosts and Consumers


APIs are intended for internal cross-site communication, and leaving them open to the entire world can be a recipe for disaster. We certainly don’t want any random person reading or writing data to our database collection, which can negatively impact our website and ruin the experience for the rest of our users. We want to ensure that only parties we trust not to misuse data are able to access our database collection. To do this, we’ll be leveraging the HMAC Authentication Velo package. Hash-based message authentication code (HMAC) allows you to use a shared secret to authenticate requests between two websites.


For our example, we’ll call one website the Host site, which will contain our API endpoint.We’ll call the second website our Consumer site. This site will call the API endpoint and, in this case, read data from the Host site.


It’s worth noting that this 1 Host -> 1 Consumer topology is the simplest example we can create. In more complex instances, you may encounter 1 Host -> many Consumers, or even websites that function as Hosts in some contexts and Consumers in others. These topologies won’t be discussed in-depth here, but it’s important to understand that when we say Host and Consumer, we are talking about a relationship between two sites, rather than inherent properties of the sites themselves.


Setting Up the Host and Consumer


In the next steps, we’ll focus on Host and Consumer-specific code in isolation. However, they will both need to use the same HMAC Authentication package, and share the same secret phrase, so we’ll do a couple things on both of our sites:


ree

  1. Open up your Package Manager, click on “Built by Wix,” type “hmac” into the search bar, and click “Install”. Then do the same on your Consumer site.

  2. Create a secret string, this can be done in a few different ways:

    1. Create a long random string yourself

    2. Use a website like RandomKeygen or Random.org

    3. On Mac or Linux, you can use the command: `head -n 4096 /dev/urandom | openssl sha256`

    4. On Windows, you can use the command: `Get-FileHash -InputStream ([System.IO.MemoryStream]::New([System.Text.Encoding]::ASCII.GetBytes(-join ((1..256) | %{Get-Random -minimum 0 -maximum 255 }))))`

  3. Store this secret in your Secret Manager on both the Host and Consumer sites. You can find this on each site’s Dashboard, under Developer Tools > Secrets Manager > Store Secret

    1. Name it `hmac-authentication-secret-key`. This will allow us to retrieve the value of this secret using its name later on in our code.

    2. Paste the secret key we generated in step 2 into the value box and click Save.


ree


Setting Up Your Host Site to Validate Requests


Now we’ll create an http-functions.js file on the backend of our Host site. In this case, our Host site will have data (user phone numbers) that our Consumer site wants, but that we don’t want to expose to the rest of the world.

ree

We’ll want to use the specific name `http-functions.js`, so that Velo knows that this file has HTTP/API functions defined in it. HTTP functions also have specific naming conventions, which you can read about in the wix-http-functions documentation along with other considerations.


The full code that we’ll be writing for our Host site is:


import { ok, badRequest } from'wix-http-functions';
import { validateAuth } from'@velo/wix-http-functions-hmac-authentication-backend';
import wixData from'wix-data';

// getDefaultResponse and defaultInvalidAuthResponse are helper functions
// helps keep our code clean and simple
functiondefaultResponse() {
return {
"headers": {
"Content-Type": "application/json"
       }
   };
}

functiondefaultInvalidAuthResponse() {
let response = defaultResponse();
   response.body = {
error: "Invalid authentication token"
   }
return badRequest(response);
}

asyncfunctioncheckAuth(request) {
try {
// This is the one line of code that does the validation on the Host site
// It returns an error when it fails which is why we have it in a try/catch
await validateAuth(request, { secretName: 'hmac-authentication-secret-key' });
returntrue;
   } catch (err) {
// We will catch the error, log it, and not pass it on to the Consumer
// If we reveal the full error message to a malicious user
// it might give them information about how our backend works.
// Logging helps us with debugging; we can see the logs in the Site Events dashboard
console.log(err, request);
returnfalse;
   }
}

exportasyncfunctionget_newestUsers(request) {

// Check Authentication before processing the rest of the function
// We want to do this first before any other code executes
let authResult = await checkAuth(request);

// If authentication failed we return our default response
if (!authResult) {
return defaultInvalidAuthResponse();
   }

// Now that we know authentication succeeded we'll do our actual work
// and send the results to the Consumer that called this function
let usersQuery = await wixData.query("UserPhoneNumbers")
       .limit(20)
       .find({ suppressAuth: true })

let response = defaultResponse();
   response.body = {
      users: usersQuery.items
   }

return ok(response);
}

Setting Up Some Helper Functions


The first two functions (`defaultResponse` and `defaultInvalidAuthResponse`) are helper functions that we could reuse if our program had more functionality. If we had multiple `get` functions like `get_newestUsers`, `get_newestOrders`, and `get_shoppingCart`, for example, we could reuse our `default*()` functions, rather than rewriting the same code multiple times. There’s nothing special or unique about these functions, we’re just following best practices.


Validating a Request’s Authentication


Next, we define another helper function `checkAuth`, which will call our `validateAuth` function with the request we received. `validateAuth` comes from our HMAC library and helps ensure that the request we receive from the Consumer site is properly authenticated. This function throws an error when it receives an invalid request, which is why we wrap it in a `try/catch` block. We also have the option to tell this function which secret we’re using in our Secret Manager. I’m using the default name to demonstrate (`{ secretName: 'hmac-authentication-secret-key' }`), but you can name it whatever you want in the Secret Manager as long as you make sure to specify it here.

After calling `validateAuth`, the `checkAuth` function will return either `true` (if the request’s authentication is valid) or it will return `false` and log an error with the request (if the request’s authentication is invalid). You can see these errors in your site logs with both Velo and Google Operations.


Putting It All Together


Lastly, we’ll put our function together. We’ll call the function `checkAuth` and set it up so that if the request is invalid, we’ll return a response back to the Consumer saying so. If our `checkAuth` function runs successfully, it will query our database collection (i.e. do the actual thing we want to do) and return the response just as it would without HMAC, now with added security from our `get_newestUsers` function. It’s worth noting that because we are performing the authentication first, there’s no need to waste time doing any heavy lifting (like a database queries) before we’re certain that the request is valid.


Implementing the Consumer Side


Now that our Host site is set up to validate requests, we’ll need to ensure that our Consumer site submits valid requests to the Host site. This is a good time to double-check to confirm that you did the first few steps of this article correctly on both your Host and Consumer sites (i.e. setting up the shared secret in your Secret Manager).


Once you’ve confirmed that you can use the following code to make a request from the Consumer site. This process is much shorter than setting up the Host site. We’ll want to put this code in a new file on our backend, which we will cleverly name `backend.js`. Note that we won’t actually be using this code for anything (it’ll never execute on its own), but you also have the option to set up a scheduled job, tie it into a data hook, or perform any other backend operation where you need data from the Host site.


import { invoke } from '@velo/wix-http-functions-hmac-authentication-backend'

export async function getNewestUsersFromHost() {
   // Where our API endpoint is on the Host site
   const endpointUrl = 'https://YOURSITE.wixsite.com/website/_functions/newestUsers';
   const fetchOptions = {
       method: 'get'
   }

   const hmacOptions = {
       secretName: 'hmac-authentication-secret-key'
   }

   let request = await invoke(endpointUrl, fetchOptions, hmacOptions);
   return request.json();
}

Next, we define our endpoint, the format of which is specified in the wix-http-functions documentation. You’ll need to adjust this to correspond to the specifics of your site. Then, we will define our `fetchOptions` function. Our example for this is admittedly rather simple, and we will specify in our function that we’re going to use the HTTP method `get`. You’ll notice that this matches the definition of our function on the Host site (i.e. `get_newestUsers`). Lastly, we define our `hmacOptions` variable to specify the `secretName` of the shared secret we stored in the Secret Manager earlier.


Once we’ve defined this function and variable, we can call the `invoke` function with our options passed in as a parameter and it will handle creating an authenticated request to the Host site. Now we can use the `request` function however we want In this case, I’ve chosen to return the `body` of the request in JavaScript Object Notation (JSON) by calling the `request.json()` method, which will provide us with the data we want from the request in a near-universal format that’s also easy to use.


Testing It Out


Lastly, we want to confirm that our code works before we use it in our application. To do this, we want to click on the triangle symbol () next to our function definition (ie. `getNewestUsersFromHost`). We can then click the “Run” button on the new tab that opens. On the right-hand side of the page, we’ll see our object returned from `request.json()`.


ree

And that’s all there is to it! If you have any questions about how HMAC or HTTP Functions work, make sure to visit the Velo API docs. If you hit any snags, feel free to post on the Velo forum!

Comments


Blank_Canvas_on_Transparent_Background.p

0

get certified.png

Related Posts

View All

1.png

CORVID LIBARY

Unified Database Management

Unified Database Management

Jun 18, 2018   8min

1.png

CORVID LIBARY

Unified Database Management

Unified Database Management

Jun 18, 2018   8min

1.png

CORVID LIBARY

Unified Database Management

Unified Database Management

Jun 18, 2018   8min

bottom of page