It's not possible to have a custom payload on failed authentication
In some situations it would be very useful to have a custom payload returned when authentication fails. However, it seems that this isn't possible to achieve.
The obvious way of doing this based on the current documentation would be something like:
percolator.route('/api/authentication/internal', {
basicAuthenticate: function (username, password, req, res, cb) {
if (username === 'username' && password === 'password') {
cb(null, {username: username, password: password});
} else {
res.object({'hello': 'world'}).send();
cb(true);
}
},
POST : function (req, res) {
res.object({auth: req.authenticated}).send();
}
});
However, if the basicAuthenticate function does a res.object().send() then you get an "Error: Can't set headers after they are sent" logged, and whilst the custom object is sent, the HTTP Status code is still set to 200. If the basicAuthenticate does a res.object() but doesn't do a send() then the custom object is never sent.
It seems that a cleaner way of making this work would be to use the first parameter to the callback to specify a custom payload that can be sent. Either by specifically passing an object to the callback and having it do a 401 and sending that object, or else by supporting some mechanism by which the first parameter contains the status and payload to use.
Alternatively, it would seem that if some object data has been sent to the response object in the authentication callback then it should be honoured and sent down as part of the payload in the event that the callback is called with true as a parameter.
Actually, you've got the solution for getting custom payloads working... just don't call cb(true); after. That callback is the default handler, so if you provide your own handling, you don't need it.
I can't work out how to actually send a custom payload on an unauthenticated status either though. If I do
res.status.unauthenticated({'hello': 'world'});
then the output is not simply the object
{
"hello": "world"
}
but instead you get
{
"error": {
"type": 401,
"message": "Unauthenticated",
"detail": {
"hello": "world"
}
}
}
That's not useful if you need to change the top level object whilst at the same time using a different status code - such as if I want to implement some other API specification (e.g. OAuth2)
I can see that I can do it manually by setting headers and so on, and that works, but it seems to be a bit of a hole.
The purpose of that res.status interface is for when you're happy with the defaults. If you want to do something else, plain node is still pretty simple:
res.statusCode = 401;
res.object({'hello': 'world'}).send();