Skip to content

Instantly share code, notes, and snippets.

@ritch
Last active June 6, 2018 10:19
Show Gist options
  • Save ritch/6675297 to your computer and use it in GitHub Desktop.
Save ritch/6675297 to your computer and use it in GitHub Desktop.
LoopBack example for accessing the current user.
/**
* 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"
});
}
@raymondfeng
Copy link

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

@bajtos
Copy link

bajtos commented Sep 24, 2013

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.

@ritch
Copy link
Author

ritch commented Sep 24, 2013

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