passport plugin for egg, base on passportjs.
$ npm i egg-passport
// config/plugin.js
exports.passport = {
enable: true,
package: 'egg-passport',
};
// config/config.default.js
exports.passportGithub = {
key: 'my oauth2 clientID',
secret: 'my oauth2 clientSecret',
};
exports.passportTwitter: {
key: 'my oauth1 consumerKey',
secret: 'my oauth1 consumerSecret',
};
Use app.passport.mount(strategy[, options])
, specifying the 'github'
and 'twitter'
strategy, to authenticate requests.
// app/router.js
module.exports = app => {
app.get('/', 'home.index');
// authenticates routers
app.passport.mount('github');
// this is a passport router helper, it's equal to the below codes
//
// const github = app.passport.authenticate('github');
// app.get('/passport/github', github);
// app.get('/passport/github/callback', github);
// custom options.login url and options.successRedirect
app.passport.mount('twitter', {
loginURL: '/account/twitter',
// auth success redirect to /
successRedirect: '/',
});
};
Use app.passport.verify(async (ctx, user) => {})
hook:
// app.js
module.exports = app => {
app.passport.verify(async (ctx, user) => {
// check user
assert(user.provider, 'user.provider should exists');
assert(user.id, 'user.id should exists');
// find user from database
//
// Authorization Table
// column | desc
// --- | --
// provider | provider name, like github, twitter, facebook, weibo and so on
// uid | provider unique id
// user_id | current application user id
const auth = await ctx.model.Authorization.findOne({
uid: user.id,
provider: user.provider,
});
const existsUser = await ctx.model.User.findOne({ id: auth.user_id });
if (existsUser) {
return existsUser;
}
// call user service to register a new user
const newUser = await ctx.service.user.register(user);
return newUser;
});
};
See example: egg-passport-twitter.
- Plugin dependencies on egg-passport to use
app.passport
APIs.
// package.json
{
"eggPlugin": {
"name": "passportTwitter",
"dependencies": [
"passport"
]
},
}
- Define config and set default values
Must use key
and secret
instead of consumerKey|clientID
and consumerSecret|clientSecret
.
// config/config.default.js
exports.passportTwitter: {
key: '',
secret: '',
callbackURL: '/passport/twitter/callback',
};
- Init
Strategy
inapp.js
and format user inverify callback
// app.js
const debug = require('debug')('egg-passport-twitter');
const assert = require('assert');
const Strategy = require('passport-twitter').Strategy;
module.exports = app => {
const config = app.config.passportTwitter;
// must set passReqToCallback to true
config.passReqToCallback = true;
assert(config.key, '[egg-passport-twitter] config.passportTwitter.key required');
assert(config.secret, '[egg-passport-twitter] config.passportTwitter.secret required');
// convert to consumerKey and consumerSecret
config.consumerKey = config.key;
config.consumerSecret = config.secret;
// register twitter strategy into `app.passport`
// must require `req` params
app.passport.use('twitter', new Strategy(config, (req, token, tokenSecret, params, profile, done) => {
// format user
const user = {
provider: 'twitter',
id: profile.id,
name: profile.username,
displayName: profile.displayName,
photo: profile.photos && profile.photos[0] && profile.photos[0].value,
token,
tokenSecret,
params,
profile,
};
debug('%s %s get user: %j', req.method, req.url, user);
// let passport do verify and call verify hook
app.passport.doVerify(req, user, done);
}));
};
- That's all!
app.passport.mount(strategy, options)
: Mount the login and the login callback routers to use the givenstrategy
.app.passport.authenticate(strategy, options)
: Create a middleware that will authorize a third-party account using the givenstrategy
name, with optionaloptions
.app.passport.verify(handler)
: Verify authenticated userapp.passport.serializeUser(handler)
: Serialize user before store into sessionapp.passport.deserializeUser(handler)
: Deserialize user after restore from session
ctx.user
: get the current authenticated userctx.isAuthenticated()
: Test if request is authenticated* ctx.login(user[, options])
: Initiate a login session foruser
.ctx.logout()
: Terminate an existing login session
This plugin has includes some mock methods to helper you writing unit tests more conveniently.
const mm = require('egg-mock');
describe('mock user demo', () => {
let app;
before(() => {
app = mm.app();
return app.ready();
});
after(() => app.close());
afterEach(mm.restore);
it('should show authenticated user info', () => {
app.mockUser();
return request(app.callback())
.get('/')
.expect(/user name: mock_name/)
.expect(200);
});
});
it('should get authenticated user and call service', async () => {
const ctx = app.mockUserContext();
const result = await ctx.service.findUser({ id: ctx.user.id });
assert(result.user.id === ctx.user.id);
});
Please open an issue here.