Request Validation

Ship It Signature

The Auth0 token provided in the X-User-Token header can be used to confirm that the user is authenticated with Auth0, but in order to verify that the billing headers described above originated from Ship It, you must verify the Ship It signature header. This can be done using the public key visible on the sites configuration page in the Ship It dashboard.

JavaScript / TypeScript

Please use the @ship-it-app/validate package to easily validate headers from Ship It.

Install

npm i @ship-it-app/validate

Usage

import { validate } from '@ship-it-app/validate';

async function fetch(request, env) {
    const isValid = await validate(
		env.REQUEST_PUBLIC_KEY,
		request.headers.get('X-Proxy-Signature'),
		request.headers.get('X-Proxy-Timestamp'),
		request.headers.get('X-User-Sub'),
	);
    if (!isValid) {
        return new Response(
            "unauthorized",
            {
                status: 401
            }
        );
    }
}

Python

Please use the ship-it-validate package to easily validate headers from Ship It.

Install

pip install ship-it-validate

Usage

For example, validate headers before every request in a Flask app:

from ship_it_validate import validate
from flask import request

@app.before_request
def before_request():
    try:
        validate(
            request.headers.get('X-PROXY-SIGNATURE'),
            request.headers.get('X-USER-SUB'),
            request.headers.get('X-PROXY-TIMESTAMP'),
        )
    except ValueError as e:
        app.logger.warning('Invalid Ship It signature: %s', e)
        return "Unauthorized", 401

Ruby

Coming Soon

Rust

Coming Soon

Other Languages

If you need to implement verification in a new language, it can be done as follows:

  1. Base64 decode the public key and parse it as JSON to obtain the P-256 ECDSA JSON Web Key (JWK) which can be used with many JSON Web Token (JWT) libraries.
  2. Use the X-Proxy-Sub and X-Proxy-Timestamp headers to form the string "SUB@TIMESTAMP".
  3. Base64 decode the signature, and use a cryptography library to verify the signature for the string from step 2 using the JWK from step 1.

Here is a reference implementation in TypeScript:

function byteStringToUint8Array(byteString: string): Uint8Array {
	const ui = new Uint8Array(byteString.length);
	for (let i = 0; i < byteString.length; ++i) {
		ui[i] = byteString.charCodeAt(i);
	}
	return ui;
}

export async function verify_request(
	env: Env,
	receivedMacBase64: string | null,
	sub: string | null,
	timestamp: string | null
): Promise<boolean> {
	const jwk: JsonWebKey = JSON.parse(atob(env.REQUEST_PUBLIC_KEY));

	if (!receivedMacBase64 || !sub || !timestamp) {
		return false;
	}

	const key = await crypto.subtle.importKey(
		"jwk",
		jwk,
		{
			name: "ECDSA",
			namedCurve: "P-256",
		},
		false,
		["verify"]
	);
	const encoder = new TextEncoder();
	const payload = `${sub}@${timestamp}`;

	const receivedMac = byteStringToUint8Array(atob(receivedMacBase64));

	const verified = await crypto.subtle.verify(
		{ name: "ECDSA", hash: "SHA-256" },
		key,
		receivedMac,
		encoder.encode(payload)
	);

	return verified;
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		const receivedMacBase64 = request.headers.get('X-Proxy-Signature');
		const timestamp = request.headers.get('X-Proxy-Timestamp');
		const sub = request.headers.get('X-User-Sub');
		const isValid = await verify_request(env, receivedMacBase64, sub, timestamp);
		if (!isValid) {
			return new Response('Unauthorized', { status: 401 });
		}
		
		// Handle valid request.
	},
};

Auth0 JWT

Ship It provides you with the user's access_token obtained from Auth0. If you wish to confirm that a request is legitimate and that it originates from a user that has authenticated with Auth0, you can validate this token, and also use it to fetch information about the user from Auth0.