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:
- 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. - Use the
X-Proxy-Sub
andX-Proxy-Timestamp
headers to form the string "SUB@TIMESTAMP". - 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.