Skip to content

Commit

Permalink
Add client authentication (xAuth) for trusted clients (#28).
Browse files Browse the repository at this point in the history
This adds an optional `client_auth` event that is emitted whenever an
access token request is made with grant_type=password. It is meant to be
used only for client-side applications that can be trusted to handle a
user's credentials directly.

For example, this will generate an access token in one shot:

$ curl -XPOST "http://1:1secret@localhost:8081/oauth/access_token" \
       -d "grant_type=password&username=guest&password=leet"

In addition, access token requests may now include client_id and
client_secret as the username and password, respectively, in the HTTP
Authorization header using Basic authentication.
  • Loading branch information
ammmir committed Jan 22, 2013
1 parent eebd416 commit 074f9a8
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 20 deletions.
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,9 @@ support both cookie-authenticated and OAuth access to protected URLs, you
could populate `req.session.user` so that individual URLs don't need to
care about which type of authentication was used.

## Running tests

Install dev dependencies:

$ npm install -d

Run the tests:

$ make test
To support client authentication (sometimes known as xAuth) for trusted
clients, handle the `client_auth` event to exchange a username and password
for an access token. See `examples/simple_express3.js`.

## Example

Expand All @@ -58,3 +52,13 @@ Visit <http://localhost:8081/login> to gain access to

- code: <http://localhost:8081/oauth/authorize?client_id=1&redirect_uri=http://myapp.foo/>
- token: <http://localhost:8081/oauth/authorize?client_id=1&redirect_uri=http://myapp.foo/&response_type=token>

## Running tests

Install dev dependencies:

$ npm install -d

Run the tests:

$ make test
11 changes: 11 additions & 0 deletions examples/simple_express3.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ myOAP.on('access_token', function(req, token, next) {
next();
});

// (optional) client authentication (xAuth) for trusted clients
myOAP.on('client_auth', function(client_id, client_secret, username, password, next) {
if(client_id == '1' && username == 'guest') {
var user_id = '1337';

return next(null, user_id);
}

return next(new Error('client authentication denied'));
});

app.use(express.logger());
app.use(express.bodyParser());
app.use(express.query());
Expand Down
83 changes: 72 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ var EventEmitter = require('events').EventEmitter,
querystring = require('querystring'),
serializer = require('serializer');

function parse_authorization(authorization) {
if(!authorization)
return null;

var parts = authorization.split(' ');

if(parts.length != 2 || parts[0] != 'Basic')
return null;

var creds = new Buffer(parts[1], 'base64').toString(),
i = creds.indexOf(':');

if(i == -1)
return null;

var username = creds.slice(0, i);
password = creds.slice(i + 1);

return(username, password);
}

function OAuth2Provider(options) {
if(arguments.length != 1) {
console.warn('OAuth2Provider(crypt_key, sign_key) constructor has been deprecated, yo.');
Expand Down Expand Up @@ -169,30 +190,70 @@ OAuth2Provider.prototype.oauth = function() {
redirect_uri = req.body.redirect_uri,
code = req.body.code;

self.emit('lookup_grant', client_id, client_secret, code, function(err, user_id) {
if(err) {
if(!client_id || !client_secret) {
var authorization = parse_authorization(req.headers.authorization);

if(!authorization) {
res.writeHead(400);
return res.end(err.message);
return res.end('client_id and client_secret required');
}

res.writeHead(200, {'Content-type': 'application/json'});
client_id = authorization[0];
client_secret = authorization[1];
}

self.emit('create_access_token', user_id, client_id, function(extra_data) {
var atok = self.generateAccessToken(user_id, client_id, extra_data);
if('password' == req.body.grant_type) {
if(self.listeners('client_auth').length == 0) {
res.writeHead(401);
return res.end('client authentication not supported');
}

if(self.listeners('save_access_token').length > 0)
self.emit('save_access_token', user_id, client_id, atok);
self.emit('client_auth', client_id, client_secret, req.body.username, req.body.password, function(err, user_id) {
if(err) {
res.writeHead(401);
return res.end(err.message);
}

res.writeHead(200, {'Content-type': 'application/json'});

res.end(JSON.stringify(atok));
self._createAccessToken(user_id, client_id, function(atok) {
res.end(JSON.stringify(atok));
});
});
} else {
self.emit('lookup_grant', client_id, client_secret, code, function(err, user_id) {
if(err) {
res.writeHead(400);
return res.end(err.message);
}

self.emit('remove_grant', user_id, client_id, code);
});
res.writeHead(200, {'Content-type': 'application/json'});

self._createAccessToken(user_id, client_id, function(atok) {
self.emit('remove_grant', user_id, client_id, code);

res.end(JSON.stringify(atok));
});
});
}

} else {
return next();
}
};
};

OAuth2Provider.prototype._createAccessToken = function(user_id, client_id, cb) {
var self = this;

this.emit('create_access_token', user_id, client_id, function(extra_data) {
var atok = self.generateAccessToken(user_id, client_id, extra_data);

if(self.listeners('save_access_token').length > 0)
self.emit('save_access_token', user_id, client_id, atok);

return cb(atok);
});
};

exports.OAuth2Provider = OAuth2Provider;

0 comments on commit 074f9a8

Please sign in to comment.