├── .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 |
--------------------------------------------------------------------------------