Authentication

#HMAC Auth Policy

Custom Policy Example

Zuplo is extensible, so we don't have a built-in policy for HMAC Auth, instead we have a template here that shows you how you can use your superpower (code) to achieve your goals. To learn more about custom policies see the documentation.

This example policy demonstrates how to use a shared secret to create an HMAC signature to sign a payload (in this case the body). When the request is sent, the signature is sent in the request header. The policy can then verify that the signature matches the payload - thus ensuring that the sender had the same shared secret.

This policy is configured with the value of the secret. Normally, you would store this as an environment variable secret. Additionally, the policy option headerName is used to set the header that will be used by the client to send the signature.

import { HttpProblems, ZuploContext, ZuploRequest } from "@zuplo/runtime";

interface PolicyOptions {
  secret: string;
  headerName: string;
}

export default async function (
  request: ZuploRequest,
  context: ZuploContext,
  options: PolicyOptions,
  policyName: string,
) {
  // Validate the policy options
  if (typeof options.secret !== "string") {
    throw new Error(
      `The option 'secret' on policy '${policyName}' must be a string. Received ${typeof options.secret}.`,
    );
  }
  if (typeof options.headerName !== "string") {
    throw new Error(
      `The option 'headerName' on policy '${policyName}' must be a string. Received ${typeof options.headerName}.`,
    );
  }

  // Get the authorization header
  const token = request.headers.get(options.headerName);

  // No auth header, unauthorized
  if (!token) {
    return HttpProblems.unauthorized(request, context);
  }

  // Convert the hex encoded token to an Uint8Array
  const tokenData = new Uint8Array(
    token.match(/../g)!.map((h) => parseInt(h, 16)),
  );

  // Get the data to verify
  // This could be anything (headers, query parameter, etc.)
  // For this example, we will just verify the entire body value
  const data = await request.text();

  // Create a crypto key from a secret stored as an environment variable
  const encoder = new TextEncoder();
  const encodedSecret = encoder.encode(options.secret);
  const key = await crypto.subtle.importKey(
    "raw",
    encodedSecret,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["verify"],
  );

  // Verify that the data
  const verified = await crypto.subtle.verify(
    "HMAC",
    key,
    tokenData,
    encoder.encode(data),
  );

  // Check if the data is verified, if not return unauthorized
  if (!verified) {
    return HttpProblems.unauthorized(request, context);
  }

  // Request is authorized, continue
  return request;
}
ts

#Configuration

The example below shows how to configure a custom code policy in the 'policies.json' document that utilizes the above example policy code.

{
  "name": "my-hmac-auth-inbound-policy",
  "policyType": "hmac-auth-inbound",
  "handler": {
    "_name": "basic",
    "export": "default",
    "module": "$import(./modules/YOUR_MODULE)",
    "options": {
      "secret": "$env(MY_SECRET)",
      "headerName": "signed-request"
    }
  }
}
json

#Policy Configuration

  • name <string> - The name of your policy instance. This is used as a reference in your routes.
  • policyType <string> - The identifier of the policy. This is used by the Zuplo UI. Value should be hmac-auth-inbound.
  • handler.export <string> - The name of the exported type. Value should be default.
  • handler.module <string> - The module containing the policy. Value should be $import(./modules/YOUR_MODULE).
  • handler.options <object> - The options for this policy. See Policy Options below.

#Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

  • secret (required) <string> - The secret to use for HMAC authentication
  • headerName (required) <string> - The header where the HMAC signature is send

#Using the Policy

The example below demonstrates how you could sign a value in order to create an HMAC signature for use with this policy.

const token = await sign("my data", environment.MY_SECRET);

async function sign(
  key: string | ArrayBuffer,
  val: string,
): Promise<ArrayBuffer> {
  const encoder = new TextEncoder();
  const cryptoKey = await crypto.subtle.importKey(
    "raw",
    typeof key === "string" ? encoder.encode(key) : key,
    { name: "HMAC", hash: { name: "SHA-256" } },
    false,
    ["sign"],
  );
  const token = await crypto.subtle.sign(
    "HMAC",
    cryptoKey,
    encoder.encode(val),
  );
  return Array.prototype.map
    .call(new Uint8Array(token), function (x) {
      return ("0" + x.toString(16)).slice(-2);
    })
    .join("");
}
ts

Read more about how policies work