Update session / token documentation

This commit is contained in:
Ritchie Martori 2013-11-14 15:27:36 -08:00
parent 64d8ff986b
commit 1bb95607b9
6 changed files with 123 additions and 50 deletions

View File

@ -211,6 +211,46 @@ app.use(loopback.rest());
View generated REST documentation by visiting: [http://localhost:3000/_docs](http://localhost:3000/_docs). View generated REST documentation by visiting: [http://localhost:3000/_docs](http://localhost:3000/_docs).
### Middleware
LoopBack comes bundled with several `connect` / `express` style middleware.
#### loopback.token(options)
**Options**
- `cookies` - An `Array` of cookie names
- `headers` - An `Array` of header names
- `params` - An `Array` of param names
Each array is used to add additional keys to find an `accessToken` for a `request`.
The following example illustrates how to check for an `accessToken` in a custom cookie, query string parameter
and header called `foo-auth`.
```js
app.use(loopback.token({
cookies: ['foo-auth'],
headers: ['foo-auth', 'X-Foo-Auth'],
cookies: ['foo-auth', 'foo_auth']
}));
```
**Defaults**
By default the following names will be checked. These names are appended to any optional names. They will always
be checked, but any names specified will be checked first.
```js
params.push('access_token');
headers.push('X-Access-Token');
headers.push('authorization');
cookies.push('access_token');
cookies.push('authorization');
```
> **NOTE:** The `loopback.token()` middleware will only check for [signed cookies](http://expressjs.com/api.html#req.signedCookies).
### Model ### Model
A Loopback `Model` is a vanilla JavaScript class constructor with an attached set of properties and options. A `Model` instance is created by passing a data object containing properties to the `Model` constructor. A `Model` constructor will clean the object passed to it and only set the values matching the properties you define. A Loopback `Model` is a vanilla JavaScript class constructor with an attached set of properties and options. A `Model` instance is created by passing a data object containing properties to the `Model` constructor. A `Model` constructor will clean the object passed to it and only set the values matching the properties you define.
@ -565,8 +605,8 @@ User.login = function (username, password, fn) {
} else if(!user) { } else if(!user) {
fn(failErr); fn(failErr);
} else if(user.password === passwordHash) { } else if(user.password === passwordHash) {
MySessionModel.create({userId: user.id}, function (err, session) { MyAccessTokenModel.create({userId: user.id}, function (err, accessToken) {
fn(null, session.id); fn(null, accessToken.id);
}); });
} else { } else {
fn(failErr); fn(failErr);
@ -585,7 +625,7 @@ loopback.remoteMethod(
{arg: 'username', type: 'string', required: true}, {arg: 'username', type: 'string', required: true},
{arg: 'password', type: 'string', required: true} {arg: 'password', type: 'string', required: true}
], ],
returns: {arg: 'sessionId', type: 'any'}, returns: {arg: 'accessTokenId', type: 'any'},
http: {path: '/sign-in'} http: {path: '/sign-in'}
} }
); );
@ -637,7 +677,7 @@ Define an instance method.
```js ```js
User.prototype.logout = function (fn) { User.prototype.logout = function (fn) {
MySessionModel.destroyAll({userId: this.id}, fn); MyAccessTokenModel.destroyAll({userId: this.id}, fn);
} }
``` ```

View File

@ -25,16 +25,16 @@ var User = loopback.User.extend('user');
// attach to the memory connector // attach to the memory connector
User.attachTo(memory); User.attachTo(memory);
// also attach the session model to a data source // also attach the accessToken model to a data source
User.session.attachTo(memory); User.accessToken.attachTo(memory);
// expose over the app's api // expose over the app's api
app.model(User); app.model(User);
``` ```
**Note:** By default the `loopback.User` model uses the `loopback.Session` model to persist sessions. You can change this by setting the `session` property. **Note:** By default the `loopback.User` model uses the `loopback.AccessToken` model to persist access tokens. You can change this by setting the `accessToken` property.
**Note:** You must attach both the `User` and `User.session` model's to a data source! **Note:** You must attach both the `User` and `User.accessToken` model's to a data source!
#### User Creation #### User Creation
@ -49,13 +49,13 @@ User.create({email: 'foo@bar.com', password: 'bar'}, function(err, user) {
#### Login a User #### Login a User
Create a session for a user using the local auth strategy. Create an `accessToken` for a user using the local auth strategy.
**Node.js** **Node.js**
```js ```js
User.login({username: 'foo', password: 'bar'}, function(err, session) { User.login({username: 'foo', password: 'bar'}, function(err, accessToken) {
console.log(session); console.log(accessToken);
}); });
``` ```
@ -88,8 +88,8 @@ POST
```js ```js
// login a user and logout // login a user and logout
User.login({"email": "foo@bar.com", "password": "bar"}, function(err, session) { User.login({"email": "foo@bar.com", "password": "bar"}, function(err, accessToken) {
User.logout(session.id, function(err) { User.logout(accessToken.id, function(err) {
// user logged out // user logged out
}); });
}); });
@ -106,7 +106,7 @@ User.findOne({email: 'foo@bar.com'}, function(err, user) {
POST /users/logout POST /users/logout
... ...
{ {
"sid": "<session id from user login>" "sid": "<accessToken id from user login>"
} }
``` ```
@ -175,23 +175,23 @@ User.confirmReset(token, function(err) {
}); });
``` ```
### Session Model ### AccessToken Model
Identify users by creating sessions when they connect to your loopback app. By default the `loopback.User` model uses the `loopback.Session` model to persist sessions. You can change this by setting the `session` property. Identify users by creating accessTokens when they connect to your loopback app. By default the `loopback.User` model uses the `loopback.AccessToken` model to persist accessTokens. You can change this by setting the `accessToken` property.
```js ```js
// define a custom session model // define a custom accessToken model
var MySession = loopback.Session.extend('my-session'); var MyAccessToken = loopback.AccessToken.extend('MyAccessToken');
// define a custom User model // define a custom User model
var User = loopback.User.extend('user'); var User = loopback.User.extend('user');
// use the custom session model // use the custom accessToken model
User.session = MySession; User.accessToken = MyAccessToken;
// attach both Session and User to a data source // attach both AccessToken and User to a data source
User.attachTo(loopback.memory()); User.attachTo(loopback.memory());
MySession.attachTo(loopback.memory()); MyAccessToken.attachTo(loopback.memory());
``` ```
### Email Model ### Email Model

View File

@ -10,7 +10,7 @@ A LoopBack model consists of:
Apps use the model API to display information to the user or trigger actions Apps use the model API to display information to the user or trigger actions
on the models to interact with backend systems. LoopBack supports both "dynamic" schema-less models and "static", schema-driven models. on the models to interact with backend systems. LoopBack supports both "dynamic" schema-less models and "static", schema-driven models.
_Dynamic models_ require only a name. The format of the data are specified completely and flexibly by the client application. Well-suited for data that originates on the client, dynamic models enable you to persist data both between sessions and between devices without involving a schema. _Dynamic models_ require only a name. The format of the data are specified completely and flexibly by the client application. Well-suited for data that originates on the client, dynamic models enable you to persist data both between accessTokens and between devices without involving a schema.
_Static models_ require more code up front, with the format of the data specified completely in JSON. Well-suited to both existing data and large, intricate datasets, static models provide structure and _Static models_ require more code up front, with the format of the data specified completely in JSON. Well-suited to both existing data and large, intricate datasets, static models provide structure and
consistency to their data, preventing bugs that can result from unexpected data in the database. consistency to their data, preventing bugs that can result from unexpected data in the database.

View File

@ -11,9 +11,8 @@ var RemoteObjects = require('strong-remoting');
module.exports = token; module.exports = token;
function token(app, options) { function token(options) {
options = options || {}; options = options || {};
var tokenModelName = options.tokenModelName || 'AccessToken';
var TokenModel = options.model; var TokenModel = options.model;
assert(TokenModel, 'loopback.token() middleware requires a AccessToken model'); assert(TokenModel, 'loopback.token() middleware requires a AccessToken model');

View File

@ -5,7 +5,8 @@
var Model = require('../loopback').Model var Model = require('../loopback').Model
, loopback = require('../loopback') , loopback = require('../loopback')
, crypto = require('crypto') , crypto = require('crypto')
, uid = require('uid2'); , uid = require('uid2')
, DEFAULT_TOKEN_LEN = 64;
/** /**
* Default AccessToken properties. * Default AccessToken properties.
@ -30,11 +31,11 @@ var AccessToken = module.exports = Model.extend('AccessToken', properties);
*/ */
AccessToken.createAccessTokenId = function (fn) { AccessToken.createAccessTokenId = function (fn) {
uid(this.settings.accessTokenIdLength || 64, function(err, buf) { uid(this.settings.accessTokenIdLength || DEFAULT_TOKEN_LEN, function(err, guid) {
if(err) { if(err) {
fn(err); fn(err);
} else { } else {
fn(null, buf.toString('base64')); fn(null, guid);
} }
}); });
} }
@ -98,16 +99,16 @@ function tokenIdForRequest(req, options) {
} }
} }
for(length = headers.length; i < length; i++) { for(i = 0, length = headers.length; i < length; i++) {
id = req.header(params[i]); id = req.header(headers[i]);
if(typeof id === 'string') { if(typeof id === 'string') {
return id; return id;
} }
} }
for(length = headers.length; i < length; i++) { for(i = 0, length = headers.length; i < length; i++) {
id = req.signedCookies(cookies[i]); id = req.signedCookies[cookies[i]];
if(typeof id === 'string') { if(typeof id === 'string') {
return id; return id;

View File

@ -4,30 +4,36 @@ var Token = loopback.AccessToken.extend('MyToken');
// attach Token to testing memory ds // attach Token to testing memory ds
Token.attachTo(loopback.memory()); Token.attachTo(loopback.memory());
describe('loopback.token(app, options)', function() { describe('loopback.token(options)', function() {
beforeEach(createTestingToken); beforeEach(createTestingToken);
it('should populate req.token from the query string', function (done) { it('should populate req.token from the query string', function (done) {
var app = loopback(); createTestAppAndRequest(this.token, done)
var options = {};
options.model = Token;
var testToken = this.token;
app.use(loopback.token(app, options));
app.get('/', function (req, res) {
try {
assert(req.accessToken, 'req should have accessToken');
assert(req.accessToken.id === testToken.id);
} catch(e) {
return done(e);
}
res.send('ok');
});
request(app)
.get('/?access_token=' + this.token.id) .get('/?access_token=' + this.token.id)
.expect(200) .expect(200)
.end(done); .end(done);
}); });
it('should populate req.token from a header', function (done) {
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', this.token.id)
.expect(200)
.end(done);
});
it('should populate req.token from a secure cookie', function (done) {
var app = createTestApp(this.token, done);
request(app)
.get('/token')
.end(function(err, res) {
request(app)
.get('/')
.set('Cookie', res.header['set-cookie'])
.end(done);
});
});
}); });
function createTestingToken(done) { function createTestingToken(done) {
@ -38,3 +44,30 @@ function createTestingToken(done) {
done(); done();
}); });
} }
function createTestAppAndRequest(testToken, done) {
var app = createTestApp(testToken, done);
return request(app);
}
function createTestApp(testToken, done) {
var app = loopback();
app.use(loopback.cookieParser('secret'));
app.use(loopback.token({model: Token}));
app.get('/token', function(req, res) {
res.cookie('authorization', testToken.id, {signed: true});
res.end();
});
app.get('/', function (req, res) {
try {
assert(req.accessToken, 'req should have accessToken');
assert(req.accessToken.id === testToken.id);
} catch(e) {
return done(e);
}
res.send('ok');
});
return app;
}