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?
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
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!