Last active
June 6, 2018 10:19
-
-
Save ritch/6675297 to your computer and use it in GitHub Desktop.
LoopBack example for accessing the current user.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Dependencies | |
*/ | |
var request = require('request'); | |
var loopback = require('loopback'); | |
var app = loopback(); | |
var memoryDataSource = loopback.memory(); | |
var HEADER_NAME = 'x-access-token'; // subject to change | |
/** | |
* Define our own User model | |
*/ | |
var MyUser = loopback.User.extend('user'); | |
var MySessions = MyUser.session; | |
/** | |
* Attach it to a data source, we'll use memory | |
* for testing... but we could use a database | |
*/ | |
MyUser.attachTo(memoryDataSource); | |
MySessions.attachTo(memoryDataSource); | |
/** | |
* Expose the Model we've defined over REST | |
*/ | |
app.use(auth); | |
app.use(loopback.rest()); | |
app.model(MyUser); | |
app.listen(3000, exampleUsage); | |
/** | |
* Create a user (using the Node.js API) for testing the REST API | |
*/ | |
MyUser.create({ | |
email: '[email protected]', | |
username: 'joe', | |
password: '123456', | |
emailVerified: true // skip verification | |
}); | |
/** | |
* Create a custom middleware that attaches the current user (if one exists) | |
* to the request using a provided header. | |
*/ | |
function auth(req, res, next) { | |
// retrieve the token from the http request | |
var token = req.headers[HEADER_NAME]; | |
// try to authenticate if a token is provided | |
if(token) { | |
MyUser.current(token, function(err, user) { | |
if(err) { | |
return next(err); | |
} else if(user) { | |
req.user = user; | |
} | |
next(); | |
}); | |
} else { | |
// otherwise continue as normal | |
next(); | |
} | |
} | |
/** | |
* A method for getting the current user. | |
*/ | |
MyUser.current = function(token, callback) { | |
var MyUser = this; | |
this.session.findById(token, function(err, sess) { | |
if(err) { | |
return callback(err); | |
} else if(sess && sess.uid) { | |
MyUser.findById(sess.uid, callback); | |
} else { | |
callback(); | |
} | |
}); | |
} | |
// add the remoting meta data to expose over REST / strong-remoting | |
MyUser.current.shared = true; | |
MyUser.current.accepts = [{arg: 'token', type: 'string', http: function(ctx) { | |
return ctx.req.headers['x-access-token']; | |
}}]; | |
MyUser.current.returns = [{arg: 'user'}]; | |
// a method that only logged in users should be able to call | |
MyUser.getSecret = function(callback) { | |
callback(null, 'the cake is a lie'); | |
} | |
// remoting metadata | |
MyUser.getSecret.shared = true; | |
MyUser.getSecret.http = {path: '/secret'}; | |
MyUser.getSecret.returns = [{arg: 'secret'}]; | |
// protect the secret method | |
MyUser.beforeRemote('getSecret', function(ctx, model, next) { | |
if(ctx.req.user) { | |
next(); | |
} else { | |
var err = new Error('unauthorized'); | |
err.statusCode = 401; | |
return next(err); | |
} | |
}); | |
/** | |
* A basic example showing how to interact with the API over REST. | |
*/ | |
function exampleUsage() { | |
var BASE = 'http://localhost:3000'; | |
// login the user over http | |
request.post(BASE + '/users/login', { | |
body: { | |
username: 'joe', | |
password: '123456' | |
}, | |
json: true | |
}, function(err, res, body) { | |
// the session id / token | |
var token = body.id; | |
var headers = {}; | |
var options = { | |
json: true, | |
headers: headers | |
}; | |
// include the token in the header | |
headers[HEADER_NAME] = token; | |
// get the current user | |
request.get(BASE + '/users/current', options, function(err, res, body) { | |
console.log(body.user.username); // => "joe" | |
}); | |
// get the secret | |
request(BASE + '/users/secret', options, function(err, res, body) { | |
console.log(body.secret); // => "the cake is a lie" | |
}); | |
}); | |
// an un-authed request | |
request(BASE + '/users/secret', {json: true}, function(err, res, body) { | |
console.log(res.statusCode); // => 401 | |
console.log(body.error); // => "unauthorized" | |
}); | |
} |
LGTM too.
We could provide a shorthand to mark certain methods as available to authenticated users only. (Possibly the other way around with authorisation being required by default.)
Alternative 1:
// remoting metadata
MyUser.getSecret.shared = true;
MyUser.getSecret.http = {path: '/secret'};
MyUser.getSecret.returns = [{arg: 'secret'}];
MyUser.getSecret.allowAnonymous = false;
Alternative 2:
// protect the secret method
MyUser.beforeRemote('getSecret', loopback.requireRegisteredUser);
// or
MyUser.requireRegisteredUserFor('getSecret', 'getAnotherSecret');
// or even
MyUser.allowAnonymousAccess(/* white-list of publicly available methods */)
Anyway, that's an idea for one of the next increments. Ritchie's proposal is good on it's own.
Background
The code above is just a starting point for our auth implementation. It doesn't include anything about access control.
@bajtos - These are interesting. I think ACLs (access control lists) are going to cover that functionality. How they are implemented and exposed is up in the air. Keep in mind that everything in loopback should be driven by data. You should be able to do as much as possible with configuration.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
LGTM.
For the endpoint to get current user, we can generalize it as:
/users/me or /users/self (me or self is a special literal for the current logged in user id)
To access other user owned resources such as albums, we can do:
/users/me/albums
A lot of sites use a similar convention for REST apis, for example:
https://www.facebook.com/me/albums