├── .gitignore ├── LICENSE ├── README.md ├── example ├── app.js ├── package.json └── views │ ├── account.ejs │ ├── index.ejs │ └── layout.ejs ├── lib └── passport-tqq │ ├── index.js │ └── strategy.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Node.js 5 | node_modules 6 | npm-debug.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011-2013 Heroic Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passport-TQQ 2 | 3 | 适用于[Passport](http://passportjs.org/)的[Tencent QQ](http://www.qq.com/)登录认证策略。 4 | 5 | ## Install 6 | 7 | $ npm install passport-tqq 8 | 9 | ## Usage 10 | 11 | #### Configure Strategy 12 | ``` 13 | passport.use(new TqqStrategy({ 14 | clientID: QQ_APP_ID, 15 | clientSecret: QQ_APP_KEY, 16 | callbackURL: "http://localhost:3000/auth/qq/callback" 17 | }, 18 | function(accessToken, refreshToken, profile, done) { 19 | User.findOrCreate({ qqId: profile.id }, function (err, user) { 20 | return done(err, user); 21 | }); 22 | } 23 | )); 24 | ``` 25 | #### Authenticate Requests 26 | ``` 27 | // QQ登录认证时 `state` 为必填参数 28 | // 系client端的状态值,用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回 29 | app.get('/auth/qq', function (req, res, next) { 30 | req.session = req.session || {}; 31 | req.session.authState = crypto.createHash('sha1') 32 | .update(-(new Date()) + '') 33 | .digest('hex'); 34 | passport 35 | .authenticate('qq', { 36 | state: req.session.authState 37 | })(req, res, next); 38 | }); 39 | 40 | app.get('/auth/qq/callback', function (req, res, next) { 41 | // 通过比较认证返回的`state`状态值与服务器端`session`中的`state`状态值 42 | // 决定是否继续本次授权 43 | if(req.session && req.session.authState 44 | && req.session.authState === req.query.state) { 45 | passport 46 | .authenticate('qq', { 47 | failureRedirect: '/login' 48 | })(req, res, next); 49 | } else { 50 | return next(new Error('Auth State Mismatch')); 51 | } 52 | }, 53 | function(req, res) { 54 | res.redirect('/'); 55 | }); 56 | ``` 57 | #### Extended Permissions 58 | 59 | 可以配置用户授权时向用户显示的可进行授权的列表。 60 | 61 | ``` 62 | app.get('/auth/qq', 63 | passport.authenticate('qq', { 64 | state: 'random state value', 65 | scope: ['get_user_info', 'list_album'] 66 | })); 67 | ``` 68 | 69 | ## Examples 70 | 71 | 见 [https://github.com/heroicyang/passport-tqq/tree/master/example](https://github.com/heroicyang/passport-tqq/tree/master/example) 72 | 73 | ## Credits 74 | 75 | - [Heroic Yang](http://github.com/heroicyang) 76 | 77 | ## License 78 | 79 | [The MIT License](http://opensource.org/licenses/MIT) 80 | 81 | Copyright (c) 2011-2013 Heroic Yang <[http://heroicyang.com/](http://heroicyang.com/)> -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , passport = require('passport') 3 | , util = require('util') 4 | , crypto = require('crypto') 5 | , TqqStrategy = require('../lib/passport-tqq/').Strategy; 6 | 7 | var QQ_APP_ID = 'blabla...' 8 | var QQ_APP_KEY = 'blabla...'; 9 | 10 | passport.serializeUser(function(user, done) { 11 | done(null, user); 12 | }); 13 | 14 | passport.deserializeUser(function(obj, done) { 15 | done(null, obj); 16 | }); 17 | 18 | passport.use(new TqqStrategy({ 19 | clientID: QQ_APP_ID, 20 | clientSecret: QQ_APP_KEY, 21 | callbackURL: 'http://127.0.0.1:3000/auth/qq/callback' 22 | }, 23 | function(accessToken, refreshToken, profile, done) { 24 | // asynchronous verification, for effect... 25 | process.nextTick(function () { 26 | return done(null, profile); 27 | }); 28 | } 29 | )); 30 | 31 | var app = express(); 32 | 33 | // configure Express 34 | app.configure(function() { 35 | app.set('views', __dirname + '/views'); 36 | app.set('view engine', 'ejs'); 37 | app.use(express.logger()); 38 | app.use(express.cookieParser()); 39 | app.use(express.bodyParser()); 40 | app.use(express.methodOverride()); 41 | app.use(express.session({ secret: 'keyboard cat' })); 42 | 43 | app.use(passport.initialize()); 44 | app.use(passport.session()); 45 | app.use(app.router); 46 | }); 47 | 48 | app.get('/', function(req, res){ 49 | res.render('index', { user: req.user }); 50 | }); 51 | 52 | app.get('/account', ensureAuthenticated, function(req, res){ 53 | res.render('account', { user: req.user }); 54 | }); 55 | 56 | // GET /auth/qq 57 | // QQ登录认证时 `state` 为必填参数 58 | // 系client端的状态值,用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回 59 | app.get('/auth/qq', function (req, res, next) { 60 | req.session = req.session || {}; 61 | req.session.authState = crypto.createHash('sha1') 62 | .update(-(new Date()) + '') 63 | .digest('hex'); 64 | passport.authenticate('qq', { 65 | state: req.session.authState 66 | })(req, res, next); 67 | }); 68 | 69 | // GET /auth/qq/callback 70 | // 通过比较认证返回的`state`状态值与服务器端`session`中的`state`状态值 71 | // 决定是否继续本次授权 72 | app.get('/auth/qq/callback', function (req, res, next) { 73 | if(req.session && req.session.authState 74 | && req.session.authState === req.query.state) { 75 | passport 76 | .authenticate('qq', { 77 | failureRedirect: '/' 78 | })(req, res, next); 79 | } else { 80 | return next(new Error('Auth State Mismatch')); 81 | } 82 | }, 83 | function(req, res) { 84 | res.redirect('/'); 85 | }); 86 | 87 | app.get('/logout', function(req, res){ 88 | req.logout(); 89 | res.redirect('/'); 90 | }); 91 | 92 | require('http').createServer(app).listen(3000); 93 | 94 | function ensureAuthenticated(req, res, next) { 95 | if (req.isAuthenticated()) { return next(); } 96 | res.redirect('/'); 97 | } 98 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-tqq-example", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "express": "~3.2.x", 6 | "ejs": "~0.8.x", 7 | "passport": "~0.1.x" 8 | }, 9 | "engines" : { "node" : ">=0.6", "npm" : ">=1.0" } 10 | } -------------------------------------------------------------------------------- /example/views/account.ejs: -------------------------------------------------------------------------------- 1 | <% include layout %> 2 |
OpenID: <%= user.id %>
3 |Nickname: <%= user.nickname %>
4 |