Keystone-classic: middleware (node-oauth2-server) can't return it's string

Created on 8 Feb 2015  ·  21Comments  ·  Source: keystonejs/keystone-classic

Hi, I am implementing oauth2 like I did in the expressjs project.

I put the following codes in routes/index.js

app.all('/oauth/token', oauth.grant());

It is triggered and token is generated in console.log.

in saveAccessToken (token: 4d82d4a819a6814459dde40e2e8f9d5f956d6028, clientId: myclientid, userId: 12345566666666, expires: Sun Feb 08 2015 11:15:51 GMT+0800 (HKT))

However, there is no return and Timeout.

In my expressjs, the oauth.grant() will return a token string. Is there anything I need to override in Keystonejs in order to let the middleware run the response?

Most helpful comment

Has anyone made a starter template for oauth login with Keystone.js? It would be awesome to have and I'd be down to collaborate on one!

All 21 comments

Looks like you are calling the function instead of passing it.
Try app.all('/oauth/token', oauth.grant);

@snowkeeper, according to the docs for node-oauth2-server app.all('/oauth/token', oauth.grant()); is correct.

Hi @snowkeeper

I tried to remove the () but it doesn't trigger any thing.

and in https://github.com/thomseddon/node-oauth2-server

The app.all('/oauth/token', oauth.grant()); seems should be used.

I have no clue how to make the token oauth2.grant() return the token

Thanks @JohnnyEstilles

And here is my routes/index.js

var keystone = require('keystone'),
    middleware = require('./middleware'),
    importRoutes = keystone.importer(__dirname);
var oauthserver = require('oauth2-server');

// Common Middleware
keystone.pre('routes', middleware.initLocals);
keystone.pre('render', middleware.flashMessages);

// Import Route Controllers
var routes = {
    views: importRoutes('./views'),
  api: importRoutes('./api')
};

// Setup Route Bindings
exports = module.exports = function(app) {
  // Oauth2
  var oauth = oauthserver({
     model: require('../models/Client'),
    grants: ['password', 'refresh_token'],
    debug: true
  });


    // Views
    app.get('/', routes.views.index);
    app.get('/blog/:category?', routes.views.blog);
    app.get('/blog/post/:post', routes.views.post);
    app.all('/contact', routes.views.contact);

  // Apis
  app.get('/api/post/list', keystone.initAPI, routes.api.posts.list);
    app.all('/api/post/create', keystone.initAPI, routes.api.posts.create);
    app.get('/api/post/:id', keystone.initAPI, routes.api.posts.get);
    app.all('/api/post/:id/update', keystone.initAPI, routes.api.posts.update);
    app.get('/api/post/:id/remove', keystone.initAPI, routes.api.posts.remove);


  // oauth
    // NOTE: To protect a route so that only admins can see it, use the requireUser middleware:
    // app.get('/protected', middleware.requireUser, routes.views.protected);

  app.all('/oauth/token', oauth.grant());
  app.get('/secret', oauth.authorise(), function (req, res) {
    res.send('Secret area');
  });

};

@arthurtalkgoal, are you using password or refresh_token mode? I assume you have version 2.3.0 or oauth2-server installed???

Just for debugging ... try adding app.use(oauth.errorHandler()); after your /secret route. Then start Keystone and open http://localhost:3000/secret and see if it at least returns an error message.

I actually using 'password' grant_type and wanna implement the refresh_token later.

and in my package.json, here is the oauth2-server dependencies:

    "oauth2-server": "^2.3.0",

Okay ... good. password type makes it easier to debug for now.

Thanks much @JohnnyEstilles

I appended the app.use(oauth.errorHandler()); after secret and changed the grand type as

    //grants: ['password', 'refresh_token'],
    grants: ['password'],

when i opened the /secret in browser it returned

{
  "code": 400,
  "error": "invalid_request",
  "error_description": "The access token was not found"
}

however, if I append the access_token, it got timeout

/secret?access_token=mytoken

It time out like the grant() did, console is triggered, client got no return:

in getAccessToken (bearerToken: mytoken)

Sorry bout that @arthurtalkgoal. Thanks @JohnnyEstilles , I obviously didn't account for a function returned.

@snowkeeper ... no worries! We're all here to help each other out. :-)

@arthurtalkgoal ... good ... at least we know the oauth middleware is responding.

I'm curious, what access_token are you appending? Since you're using password grant type, you should be requesting your access token by submitting a POST with a URL encoded form (with the username and password) to /oauth/token. Unless, of course, you already generated a code and stored it and you're using it just for testing purposes.

There are, of course, two pieces of the puzzle which we haven't seen: (1) your model and (2) your client-side code requesting the access token. Can you share them?

Hi @JohnnyEstilles
Sure, first off, thanks much and you are very helpful.

I change the existing working oauth2 expressjs project to keystone.

Namely, User and Client are registered in KeystoneJS (which I really want to as the AdminUI can manage my models).

On the other hand, the OAuthAccess OAuthRefreshTokens are stored in mongodb via mongoose as my expressJs.

All are defined here:

/models/Client.js

var keystone = require('keystone'),
    Types = keystone.Field.Types;
var mongoose = require('mongoose'),
  Schema = mongoose.Schema,
  model = module.exports;


  var OAuthAccessTokensSchema = new Schema({
  accessToken: { type: String },
  client_id: { type: String },
  userId: { type: String },
  expires: { type: Date }
});

var OAuthRefreshTokensSchema = new Schema({
  refreshToken: { type: String },
  client_id: { type: String },
  userId: { type: String },
  expires: { type: Date }
});

mongoose.model('OAuthAccessTokens', OAuthAccessTokensSchema);
mongoose.model('OAuthRefreshTokens', OAuthRefreshTokensSchema);

var OAuthAccessTokensModel = mongoose.model('OAuthAccessTokens'),
  OAuthRefreshTokensModel = mongoose.model('OAuthRefreshTokens');
//
// oauth2-server callbacks
//
model.getAccessToken = function (bearerToken, callback) {
  console.log('in getAccessToken (bearerToken: ' + bearerToken + ')');

  OAuthAccessTokensModel.findOne({ accessToken: bearerToken }, callback);
};

model.getClient = function (clientId, clientSecret, callback) {
  console.log('in getClient (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ')');
  if (clientSecret === null) {
    // return OAuthClientsModel.findOne({ client_id: clientId }, callback);
    return  Client.model.find()
      .where('client_id', clientId).limit(1).exec(callback);
  }
  //OAuthClientsModel.findOne({ client_id: clientId, client_secret: clientSecret }, callback);
  return  Client.model.find()
      .where('client_id', clientId).where('client_secret', clientSecret).limit(1).exec(callback);
}; 
// This will very much depend on your setup, I wouldn't advise doing anything exactly like this but
// it gives an example of how to use the method to resrict certain grant types
var authorizedClientIds = ['abc123', 'toto'];
model.grantTypeAllowed = function (clientId, grantType, callback) {
  console.log('in grantTypeAllowed (clientId: ' + clientId + ', grantType: ' + grantType + ')');
console.log("authorizedClientIds is hard coded, please change ******************");
  if (grantType === 'password') {
    return callback(false, authorizedClientIds.indexOf(clientId) >= 0);
  }

  callback(false, true);
};

model.saveAccessToken = function (token, clientId, expires, userId, callback) {
  console.log('in saveAccessToken (token: ' + token + ', clientId: ' + clientId + ', userId: ' + userId + ', expires: ' + expires + ')');

  var accessToken = new OAuthAccessTokensModel({
    accessToken: token,
    clientId: clientId,
    userId: userId,
    expires: expires
  });

  accessToken.save(callback);
};



/*
 * Required to support password grant type
 */
model.getUser = function (username, password, callback) {
  console.log('in getUser (username: ' + username + ', password: ' + password + ')');
  //OAuthUsersModel.findOne({ username: username }, function(err, user) {
  keystone.mongoose.model('User').findOne({ email: username }, function(err, user) {
    //.where('email',username )
    //.where('password', password)
    //.exec(function(err, user) {

    if(err) return callback(err);
    // test a matching password
    if (user) {

      // callback(null, user._id);
console.log("password is --------" + password);      
      user._.password.compare(password, function(err, isMatch){
          if (err) throw err;
          if (isMatch){
            callback(null, user._id);
          } else {
            callback(false);
          }
      });
      /*user.comparePassword(password, function(err, isMatch) {
          if (err) throw err;
          if (isMatch){
            callback(null, user._id);
          } else {
            callback(false);
          }
      }); */
    } else {

      callback(false);
    }

  });
};

/*
 * Required to support refreshToken grant type
 */
model.saveRefreshToken = function (token, clientId, expires, userId, callback) {
  console.log('in saveRefreshToken (token: ' + token + ', clientId: ' + clientId +', userId: ' + userId + ', expires: ' + expires + ')');

  var refreshToken = new OAuthRefreshTokensModel({
    refreshToken: token,
    clientId: clientId,
    userId: userId,
    expires: expires
  });

  refreshToken.save(callback);
};

model.getRefreshToken = function (refreshToken, callback) {
  console.log('in getRefreshToken (refreshToken: ' + refreshToken + ')');

  OAuthRefreshTokensModel.findOne({ refreshToken: refreshToken }, callback);
};

/**
 * Client Model
 * ==========
 */

var Client = new keystone.List('Client');

Client.add({
    client_id: { type: String, required: true, index: true, default: "1234" },
    client_secret: { type: String, initial: true, required: true, default: "secret" },
  redirectUri: { type: String, initial: true }
}, 'Permissions', {
    isAdmin: { type: Boolean, label: 'Can access Keystone', index: true }
});




/**
 * Relationships
 */

// User.relationship({ ref: 'Post', path: 'posts', refPath: 'author' });


/**
 * Registration
 */

Client.defaultColumns = 'client_id, client_secret, redirectUri';
Client.register();

models/User.js

var keystone = require('keystone'),
    Types = keystone.Field.Types;

/**
 * User Model
 * ==========
 */

var User = new keystone.List('User');

User.add({
    name: { type: Types.Name, required: true, index: true },
    email: { type: Types.Email, initial: true, required: true, index: true },
    password: { type: Types.Password, initial: true, required: true }
}, 'Permissions', {
    isAdmin: { type: Boolean, label: 'Can access Keystone', index: true }
});

// Provide access to Keystone
User.schema.virtual('canAccessKeystone').get(function() {
    return this.isAdmin;
});


/**
 * Relationships
 */

User.relationship({ ref: 'Post', path: 'posts', refPath: 'author' });


/**
 * Registration
 */

User.defaultColumns = 'name, email, isAdmin';
User.register();

FYI,
All the oauth2 provider function like getAccessToken() and saveAccessToken() are triggerred correctly.

But all has no return in clients/browsers.

Re client side:
I modify the example in http://scottksmith.com/blog/2014/07/02/beer-locker-building-a-restful-api-with-node-oauth2-server/

which use postman to post a request to the server with the parameters

I'm trying to duplicate your problem on my end ... unfortunately I need to take a break. I will continue troubleshooting when I get back this afternoon.

Hi @JohnnyEstilles

Fixed! I found that the issue is the

var mongoose = require('mongoose');

which created a new mongoose and make my token not working and no error! (the data goes to other dbs I bet);

var mongoose = keystone.mongoose;

solved the problem!

That's awesome! :-)

This is much helpful for me, I met the same question. In mongo log, I found the saveaccessToken connected the mongo and not used the env mongo URI . Mongodb was not connected, so save function didn't call the callback, and the response was timeout. What was strange was that, the same code I runned on mac without problem, but issue occurred on Centos.

Has anyone made a starter template for oauth login with Keystone.js? It would be awesome to have and I'd be down to collaborate on one!

@cleechtech I believe this Sydjs Site repo has an implementation

Was this page helpful?
0 / 5 - 0 ratings