├── .gitignore ├── routes ├── main.js ├── logout.js ├── profile.js ├── authSuccess.js └── index.js ├── templates ├── common │ ├── footer.ejs │ └── head.ejs ├── error.ejs ├── profile.ejs └── main.ejs ├── libs ├── sessionStore.js ├── mongoose.js ├── config.js └── passport.js ├── common.js ├── package.json ├── models └── users.js ├── app.js └── public └── main.css /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .idea/ 3 | node_modules/ -------------------------------------------------------------------------------- /routes/main.js: -------------------------------------------------------------------------------- 1 | 2 | exports.get = async (req, res, next) => { 3 | res.locals.title = 'Welcome to Passport demo by #BlondieCode'; 4 | res.render('./main'); 5 | }; -------------------------------------------------------------------------------- /templates/common/footer.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /routes/logout.js: -------------------------------------------------------------------------------- 1 | 2 | exports.get = (req, res, next) => { 3 | 4 | req.session.destroy(err => { 5 | if (err) return next(err); 6 | res.redirect('/'); 7 | }); 8 | }; -------------------------------------------------------------------------------- /templates/common/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | <%=title%> 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /routes/profile.js: -------------------------------------------------------------------------------- 1 | 2 | exports.get = async (req, res, next) => { 3 | 4 | if (req.session.userId){ 5 | res.locals.title = 'My profile'; 6 | res.render('./profile'); 7 | } else { 8 | res.redirect('/'); 9 | } 10 | }; -------------------------------------------------------------------------------- /libs/sessionStore.js: -------------------------------------------------------------------------------- 1 | 2 | const session = require('express-session'); 3 | const mongoose = require('./mongoose'); 4 | const mongoStore = require('connect-mongo')(session); 5 | 6 | const sessionStore = new mongoStore({mongooseConnection: mongoose.connection}); 7 | 8 | module.exports = sessionStore; -------------------------------------------------------------------------------- /templates/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ("./common/head.ejs") %> 4 | 5 |
6 |
7 |

<%=errorMsg%>

8 |
9 | <%- include ("./common/footer.ejs") %> 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /libs/mongoose.js: -------------------------------------------------------------------------------- 1 | 2 | const mongoose = require('mongoose'); 3 | const Config = require('./config'); 4 | 5 | mongoose.Promise = Promise; 6 | const mongooseLogs = (process.env.NODE_ENV === 'development'); 7 | mongoose.set('debug', mongooseLogs); 8 | mongoose.set('useUnifiedTopology', true); 9 | 10 | mongoose.connect(Config.mongoose.uri, Config.mongoose.options); 11 | 12 | module.exports = mongoose; -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | 2 | const Users = require('./models/users').Users; 3 | 4 | module.exports.commonMW = async (req, res, next) => { 5 | 6 | res.locals.title = 'Simple Passport.js demo'; 7 | res.locals.fullYear = (new Date()).getFullYear(); 8 | res.locals.userProfile = ''; 9 | 10 | if (req.session.userId) { 11 | res.locals.userProfile = await Users.findOne({_id: req.session.userId}); 12 | } 13 | next(); 14 | }; -------------------------------------------------------------------------------- /templates/profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ("./common/head.ejs") %> 4 | 5 |
6 |
7 | 14 |
15 | <%- include ("./common/footer.ejs") %> 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-demo-blondiecode", 3 | "version": "1.0.0", 4 | "description": "Passport authentification Express.js architecture", 5 | "main": "app.js", 6 | "author": "Aida Drogan #BlondieCode", 7 | "scripts": { 8 | "dev": "NODE_ENV=development node app.js", 9 | "pro": "NODE_ENV=production node app.js" 10 | }, 11 | "license": "ISC", 12 | "dependencies": { 13 | "connect-mongo": "^3.2.0", 14 | "ejs": "^3.1.6", 15 | "express": "^4.17.1", 16 | "express-session": "^1.17.2", 17 | "helmet": "^4.6.0", 18 | "mongoose": "^5.9.18", 19 | "passport": "^0.4.1", 20 | "passport-facebook": "^3.0.0", 21 | "passport-google": "^0.3.0", 22 | "passport-google-oauth": "^2.0.0", 23 | "passport-vkontakte": "^0.3.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/main.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ("./common/head.ejs") %> 4 | 5 |
6 |
7 | 21 |
22 | <%- include ("./common/footer.ejs") %> 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /libs/config.js: -------------------------------------------------------------------------------- 1 | 2 | const config = {}; 3 | 4 | config.port = 3000; 5 | 6 | config.mongoose = { 7 | "uri": "mongodb://localhost:27017/passport-demo", 8 | "options": { 9 | "keepAlive": 1, 10 | "autoIndex": false, 11 | "useNewUrlParser": true, 12 | "poolSize" : 10 13 | } 14 | }; 15 | 16 | config.session = { 17 | "secret": "BlondieCode", 18 | "key": "sid", 19 | "cookie": { 20 | "path": "/", 21 | "httpOnly": true, 22 | "maxAge": null 23 | } 24 | }; 25 | 26 | config.oauth = { 27 | 'facebookAuth' : { 28 | 'clientID': 'your_client_id', 29 | 'clientSecret': 'your_client_secret', 30 | 'callbackURL': '/registration/facebook/callback' 31 | }, 32 | 'googleAuth' : { 33 | 'clientID': 'your_client_id', 34 | 'clientSecret': 'your_client_secret', 35 | 'callbackURL': '/registration/google/callback' 36 | }, 37 | 'vkontakteAuth' : { 38 | 'clientID': 'your_client_id', 39 | 'clientSecret': 'your_client_secret', 40 | 'callbackURL': '/registration/vkontakte/callback' 41 | } 42 | }; 43 | 44 | module.exports = config; -------------------------------------------------------------------------------- /routes/authSuccess.js: -------------------------------------------------------------------------------- 1 | 2 | const Users = require('../models/users').Users; 3 | 4 | exports.get = async (req, res, next) => { 5 | 6 | try { 7 | if (req.session?.passport?.user) { 8 | 9 | console.log('PASSPORT USER:',req.session.passport.user); 10 | 11 | const userObj = {}; 12 | userObj.email = req.session.passport.user.emails[0].value; 13 | userObj.firstName = req.session.passport.user?.name?.givenName || ''; 14 | userObj.lastName = req.session.passport.user?.name?.familyName || ''; 15 | userObj.avatar = req.session.passport.user?.photos[0]?.value || ''; 16 | userObj.socialId = req.session.passport.user.id; 17 | userObj.provider = req.session.passport.user.provider; 18 | 19 | req.session.userId = await Users.findOne({socialId: userObj.socialId}, {_id: 1}).lean(); 20 | req.session.userId = req.session.userId || (await Users.fullSave(userObj))._id; 21 | 22 | res.redirect('/profile'); 23 | } else { 24 | next('Authentication error'); 25 | } 26 | } catch (e) { 27 | next(e); 28 | } 29 | }; -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (app, passport) => { 3 | 4 | const mainRoute = require('./main'); 5 | app.get('/', mainRoute.get); 6 | 7 | const profileRoute = require('./profile'); 8 | app.get('/profile', profileRoute.get); 9 | 10 | //================= Social Auth 11 | const networks = ['google', 'facebook', 'vkontakte']; 12 | networks.forEach(network => { 13 | 14 | app.get(`/registration/${network}`, (request, response) => { 15 | passport.authenticate(network, { 16 | scope:'email' 17 | })(request, response); 18 | }); 19 | 20 | app.get(`/registration/${network}/callback`, (request, response) => { 21 | passport.authenticate(network, { 22 | successRedirect: '/auth-success', 23 | failureRedirect: '/auth-error' 24 | })(request, response); 25 | }); 26 | }); 27 | 28 | const authSuccess = require('./authSuccess'); 29 | app.get('/auth-success', authSuccess.get); 30 | 31 | //================= logout 32 | const logoutRoute = require('./logout'); 33 | app.get('/logout', logoutRoute.get); 34 | }; 35 | -------------------------------------------------------------------------------- /models/users.js: -------------------------------------------------------------------------------- 1 | 2 | const mongoose = require('../libs/mongoose'); 3 | const Schema = mongoose.Schema; 4 | 5 | const schema = new Schema({ 6 | email: { 7 | type: String, 8 | unique: true, 9 | required: true 10 | }, 11 | firstName: { 12 | type: String 13 | }, 14 | lastName: { 15 | type: String 16 | }, 17 | avatar: { 18 | type: String 19 | }, 20 | socialId: { 21 | type: String 22 | }, 23 | provider: { 24 | type: String 25 | }, 26 | created: { 27 | type: Date, 28 | default: Date.now 29 | } 30 | }); 31 | 32 | schema.index( 33 | {email: 1}, {unique: true, dropDups: true} 34 | ); 35 | 36 | schema.statics = { 37 | fullSave: async function(data) { 38 | const Item = this; 39 | const items = new Item(data); 40 | return await items.save(); 41 | }, 42 | updateItem: async function(id, params) { 43 | const Item = this; 44 | return await Item.updateOne( 45 | {_id: id}, 46 | {$set: params} 47 | ); 48 | }, 49 | removeItem: async function(id) { 50 | const Item = this; 51 | return await Item.deleteOne({_id: id}); 52 | } 53 | }; 54 | 55 | exports.Users = mongoose.model('Users', schema); -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const session = require('express-session'); 4 | const path = require('path'); 5 | const helmet = require('helmet'); 6 | 7 | const Config = require('./libs/config'); 8 | const common = require('./common'); 9 | 10 | const app = express(); 11 | 12 | app.use(helmet()); 13 | //================= Templates 14 | app.set('views', __dirname + '/templates'); 15 | app.set('view engine', 'ejs'); 16 | app.set('view options', {compileDebug: false, self: true, cache: true}); 17 | app.use(express.static(path.join(__dirname, 'public'))); 18 | 19 | //================= Session 20 | const sessionStore = require('./libs/sessionStore'); 21 | app.use(session({ 22 | secret: Config.session.secret, 23 | key: Config.session.key, 24 | cookie: Config.session.cookie, 25 | store: sessionStore, 26 | resave: false, 27 | saveUninitialized: true 28 | })); 29 | 30 | //================= Social Auth 31 | const passport = require('passport'); 32 | require('./libs/passport')(passport); 33 | 34 | app.use(passport.initialize()); 35 | app.use(passport.session()); 36 | 37 | //================= Common middleware 38 | app.use(common.commonMW); 39 | 40 | //================= Routing 41 | require('./routes')(app, passport); 42 | 43 | //================= Error handling 44 | app.use((err, req, res, next) => { 45 | console.error('Error:', err.stack); 46 | res.status(502); 47 | res.render('./error', {errorMsg: 'Server Error'}); 48 | }); 49 | app.use((req, res) => { 50 | res.status(404); 51 | res.render('./error', {errorMsg: 'Not Found'}); 52 | }); 53 | 54 | app.listen(Config.port, () => { 55 | console.log(`Listening on port ${Config.port}!`); 56 | }); -------------------------------------------------------------------------------- /public/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | min-height: 100vh; 3 | margin: 0; 4 | padding: 0; 5 | overflow: auto; 6 | } 7 | body { 8 | display: flex; 9 | flex-direction: column; 10 | text-align: center; 11 | box-sizing: border-box; 12 | font-family: Arial, "Helvetica Neue", Helvetica, serif; 13 | background-color: #89a1b4; 14 | background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23ffffff' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 15 | } 16 | div { 17 | box-sizing: border-box; 18 | } 19 | main { 20 | flex-grow: 1; 21 | } 22 | footer { 23 | text-align: center; 24 | padding: 20px; 25 | flex-grow: 0; 26 | } 27 | a { 28 | color: #00706f; 29 | } 30 | a:hover { 31 | color: #00a2a1; 32 | } 33 | a.button { 34 | background: #00706f; 35 | color: #ffffff; 36 | font-size: 18px; 37 | text-decoration: none; 38 | padding: 20px; 39 | border-radius: 20px; 40 | display: inline-block; 41 | margin: 10px; 42 | } 43 | a.button:hover { 44 | background: #00a2a1; 45 | color: #ffffff; 46 | } 47 | h1 { 48 | font-size: 26px; 49 | margin: 20px auto 40px; 50 | } 51 | h2 { 52 | font-size: 22px; 53 | margin: 40px auto; 54 | } 55 | .flex { 56 | display: flex; 57 | } 58 | .flex.centered { 59 | justify-content: center; 60 | } 61 | .flex.row { 62 | flex-direction: row; 63 | } 64 | .flex.col { 65 | flex-direction: column; 66 | } 67 | .holder { 68 | width: 100%; 69 | max-width: 1000px; 70 | margin: 0 auto; 71 | background: rgba(255,255,255,.75); 72 | height: 100vh; 73 | flex: 1; 74 | } 75 | .login-form { 76 | margin: auto; 77 | } 78 | .login-form img { 79 | max-width: 100px; 80 | border-radius: 50%; 81 | } -------------------------------------------------------------------------------- /libs/passport.js: -------------------------------------------------------------------------------- 1 | 2 | const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 3 | const FacebookStrategy = require('passport-facebook').Strategy; 4 | const VKontakteStrategy = require('passport-vkontakte').Strategy; 5 | const Config = require('./config'); 6 | 7 | module.exports = passport => { 8 | 9 | passport.serializeUser((user, done) => { 10 | done(null, user); 11 | }); 12 | 13 | passport.deserializeUser((obj, done) => { 14 | done(null, obj.id); 15 | }); 16 | 17 | //============ GOOGLE 18 | passport.use('google', new GoogleStrategy({ 19 | clientID: Config.oauth.googleAuth.clientID, 20 | clientSecret: Config.oauth.googleAuth.clientSecret, 21 | callbackURL: Config.oauth.googleAuth.callbackURL 22 | }, 23 | function(request, accessToken, refreshToken, profile, done) { 24 | process.nextTick(() => { 25 | done(null, profile); 26 | }); 27 | })); 28 | 29 | //============ FACEBOOK 30 | passport.use('facebook', new FacebookStrategy({ 31 | clientID: Config.oauth.facebookAuth.clientID, 32 | clientSecret: Config.oauth.facebookAuth.clientSecret, 33 | callbackURL: Config.oauth.facebookAuth.callbackURL, 34 | profileFields: ['id', 'displayName', 'name', 'gender', "emails", 'photos'] 35 | }, 36 | function(accessToken, refreshToken, profile, done) { 37 | process.nextTick(() => { 38 | done(null, profile); 39 | }); 40 | })); 41 | 42 | //============ VK 43 | passport.use('vkontakte', new VKontakteStrategy({ 44 | clientID: Config.oauth.vkontakteAuth.clientID, 45 | clientSecret: Config.oauth.vkontakteAuth.clientSecret, 46 | callbackURL: Config.oauth.vkontakteAuth.callbackURL 47 | }, 48 | function(accessToken, refreshToken, params, profile, done) { 49 | const _profile = JSON.parse(JSON.stringify(profile)); 50 | _profile.emails = [{value: params.email}]; 51 | process.nextTick(() => { 52 | done(null, _profile); 53 | }); 54 | })); 55 | }; 56 | 57 | --------------------------------------------------------------------------------