Update session / token documentation
This commit is contained in:
parent
64d8ff986b
commit
1bb95607b9
48
docs/api.md
48
docs/api.md
|
@ -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);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue