akamai-g2o
v2.0.0
Published
Connect/Express/Restify middleware implementing Akamai signature header authentication (G2O).
Downloads
8
Readme
Akamai G2O for Node JS
G2O (Ghost to Origin, aka Signature Header Authentication) is an Akamai protocol that provides request authentication between Akamai caching proxies and the origin.
This library provides a simple middleware function that provides compliant G2O validation for applications. The following NodeJS server frameworks are supported:
- vanilla NodeJS HTTP server
- Express 4
- Restify 4
How the protocol works
From the documentation:
This feature configures edge servers to include two special headers in requests to the origin server. One of these headers contains basic information about the specific request. The second header contains similar information encrypted with a shared secret. This allows the origin server to perform several levels of authentication to ensure that the request is coming directly from an edge server without tampering.
What this library does not support
The reference documentation specifies that the X-Akamai-G2O-Auth-Data
header should be stored to prevent replay attacks.
This is not supported (yet), because the library does not make assumptions about its environment and the storage engines
that may be available. It does however provide a way of extending the validation mechanism. See the extraCheck
option
for more details.
How to use this library
First, install it:
npm install --save akamai-g2o
It is assumed that you are familiar with G2O concepts, which aren't covered in depth here.
This simple example sets up an application to use G2O with default parameters.
const app = require("express")();
const g2o = require("akamai-g2o");
app.use(g2o({
key: {
"nonce1": "s3cr3tk3y",
"nonce2": "s3cr3tk3y2"
}
}));
// ... setup routes and listen
Requests that fail authentication will result in a 403 response.
Requests that pass authentication will be extended with a g2o
attribute with the following structure:
{
data: {}, // see the Data object description section for details
signature: "base64 encoded signature generated by the middleware",
authenticated: true|false,
message: "present only when authenticated is false"
}
See the options section for more advanced configuration.
Options
key
Mandatory; Object
or Function
.
When providing an Object
, it should map nonces to keys (see the official G2O documentation for more information about nonces).
It is also possible to provide a Function
for more flexibility. It should have the following signature:
function (req, data, callback)
req
is the request object, typically an instance of http.IncomingMessage
.
data
is an Object
representing the data in the dataString
. It is described in more detail in the Data object description.
callback
takes an Error
or null
as its first parameter and the key as its second.
strict
*** NO LONGER SUPPORTED ***
If you want to implement not strict logic see: onUnauthenticated.
dataHeader
Optional; String
(default: "X-Akamai-G2O-Auth-Data")
By default, Ghost sends the request data in the X-Akamai-G2O-Auth-Data
header. Setting this option instructs the middleware
to retrieve a different header.
signHeader
Optional; String
(default: "X-Akamai-G2O-Auth-Sign")
By default, Ghost sends the request signature in the X-Akamai-G2O-Auth-Sign
header. Setting this option instructs the middleware
to retrieve a different header.
signString
Optional; Function
(default: req => req.url
)
By default, Ghost signs the request URL as represented in the status line of the request (the URL path).
Ghost can be configured to use any part or combination of parts of the request, in which case you
should provide a Function
with the following signature:
function (req) => String
checkTime
Optional; Boolean
(default: true
)
If true
, the request will fail if the request time is more than 30s distant from the current server time.
timeWindow
Optional; Number
(default: 30)
Number of seconds before or after which the difference between the server and request times will trigger an
authentication error when checkTime
is true
.
extraCheck
Optional; Function
(default: null)
If provided, the function will be called after all checks have been completed. The signature should be:
function (req, callback)
An example implementation that prevents replay attacks might look like this:
var g2o = require("akamai-g2o");
var app = require("express")();
var previousAuthDataValues = {};
app.use(g2o({
key: {
"nonce1": "s3cr3tk3y",
"nonce2": "s3cr3tk3y2"
},
extraCheck: function (req, callback) {
if (req.g2o.data.raw in previousAuthDataValues) {
callback(new Error("replayed request"));
} else {
callback();
}
}
}));
onUnauthenticated
Optional; Function
(default:
function (req, res, next) {
var statusCode = 403;
if (typeof res.status === "function") {
// Express has a status() helper function
res.status(statusCode);
} else {
res.statusCode = statusCode;
}
res.end();
}
)
If provided, the function will be called after all checks have been completed and found to be unauthenticated. An unauthenticated g2o data will have a message property, that is the failure reason. The signature should be:
function (req, res, next)
An example implementation that:
- logs out the failed request
- uses a different status code
- only fails request if strict
var g2o = require("akamai-g2o");
var app = require("express")();
var strict = true; // strict controlled outside, but maybe some config.
app.use(g2o({
key: {
"nonce1": "s3cr3tk3y",
"nonce2": "s3cr3tk3y2",
},
onUnauthenticated: function (req, res, next) {
var g2oResponse = Object.assign(
{},
{
strict: strict,
clientIp: req.ip, // for if there are no g2o header fallback to server known ip.
forwardAddresses: req.forwardAddresses,
uri: req.originalUrl, // note this is for express 4.0, else it is req.url
},
req.g2o
);
console.log('g2o unauthenticated', g2oResponse); // logging
if (strict) { // only fail request if strict
res.status(407).end(); // different status code
} else {
next();
}
}
}));
Data object description
Ghost is required to send a header containing authentication information which is used to generate the signature. This library parses
the contents of the header and makes it available as an Object
with the following structure:
{
// the value of the raw header
raw: "1, 1.2.3.4, 2.3.4.5, 123456789, 42314563, nonce1",
// specifies the hashing function to use
version: int(1..5),
// edge server IP address
edgeIp: String,
// client IP address
clientIp: String,
// request time, as a Date object
time: Date,
// request unique identifier
uniqueId: String,
// nonce referencing the key to use
nonce: String
}
Contributing
Don't break the unit tests, be as clean as you can, be nice.
npm run -s test