Permalink
Browse files

Add client authentication (xAuth) for trusted clients (#28).

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...
1 parent eebd416 commit 074f9a8bc9e42e0a4f667e87ba6eca52ee03b1e2 @ammmir committed Jan 22, 2013
Showing with 96 additions and 20 deletions.
  1. +13 −9 README.md
  2. +11 −0 examples/simple_express3.js
  3. +72 −11 index.js
View
@@ -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
@@ -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
@@ -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());
View
@@ -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.');
@@ -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.