├── views ├── login.hbs ├── error.hbs ├── layout.hbs └── index.hbs ├── db ├── knex.js └── api.js ├── knexfile.js ├── migrations └── 20160624103756_users.js ├── public └── style.css ├── .gitignore ├── package.json ├── auth.js ├── routes └── index.js ├── app.js ├── bin └── www └── README.md /views/login.hbs: -------------------------------------------------------------------------------- 1 | Login with Google 2 | -------------------------------------------------------------------------------- /views/error.hbs: -------------------------------------------------------------------------------- 1 |

{{message}}

2 |

{{error.status}}

3 |
{{error.stack}}
4 | -------------------------------------------------------------------------------- /db/knex.js: -------------------------------------------------------------------------------- 1 | var environment = process.env.NODE_ENV || 'development'; 2 | var config = require('../knexfile.js')[environment]; 3 | var knex = require('knex')(config); 4 | 5 | module.exports = knex; 6 | -------------------------------------------------------------------------------- /views/layout.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Google OAuth2.0 Template 6 | 7 | 8 | 9 | {{{ body }}} 10 | 11 | 12 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | module.exports = { 4 | development: { 5 | client: 'pg', 6 | connection: 'postgres://localhost/' + 'your-database-name' 7 | }, 8 | 9 | production: { 10 | client: 'pg', 11 | connection: process.env.DATABASE_URL + '?ssl=true' 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20160624103756_users.js: -------------------------------------------------------------------------------- 1 | 2 | exports.up = function(knex, Promise) { 3 | return knex.schema.createTable('users', function(table) { 4 | table.increments(); 5 | table.string('googleId'); 6 | }); 7 | }; 8 | 9 | exports.down = function(knex, Promise) { 10 | return knex.schema.dropTable('users'); 11 | }; 12 | -------------------------------------------------------------------------------- /db/api.js: -------------------------------------------------------------------------------- 1 | var knex = require('./knex'); 2 | 3 | module.exports = { 4 | findUserById: function(profileId) { 5 | return knex('users') 6 | .select() 7 | .where({ googleId: profileId }) 8 | .first(); 9 | }, 10 | 11 | createUser: function(profileId) { 12 | return knex('users') 13 | .insert({ googleId: profileId }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /views/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if user}} 2 |

Hello, {{ user.displayName }}.

3 | {{#each user.photos}}userPhoto{{/each}} 4 | 5 | Home | 6 | Log Out 7 | {{else}} 8 |

Welcome! Please log in.

9 | Home | 10 | Log In 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | background-color: #c7c7c7; 5 | } 6 | 7 | a { 8 | color: #00B7FF; 9 | } 10 | 11 | .button { 12 | background-color: #00a848; 13 | padding: .7rem; 14 | margin: .5rem; 15 | margin-top: 1rem; 16 | border-radius: 5px; 17 | color: #e9e9e9; 18 | text-decoration: none; 19 | box-shadow: 3px 2px 2px black; 20 | } 21 | 22 | .button:hover { 23 | background-color: #34d7c3; 24 | } 25 | 26 | img { 27 | height: 80px; 28 | width: 80px; 29 | border-radius: 5px; 30 | box-shadow: 3px 2px 2px black; 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Environment Variables 40 | .env 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-oauth2-template", 3 | "version": "1.1", 4 | "description": "Google OAuth2 Express Passport template", 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/bradford-hamilton/Google-OAuth2.0-Express-Passport-Template.git" 11 | }, 12 | "author": "Bradford Lamson-Scribner ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "body-parser": "1.12.4", 16 | "cookie-parser": "1.3.4", 17 | "dotenv": "2.0.0", 18 | "express": "4.12.4", 19 | "express-session": "1.11.2", 20 | "hbs": "3.0.1", 21 | "knex": "0.11.7", 22 | "morgan": "1.5.3", 23 | "passport": "0.2.1", 24 | "passport-google-oauth": "0.2.0", 25 | "pg": "6.0.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /auth.js: -------------------------------------------------------------------------------- 1 | var db = require('./db/api'); 2 | var passport = require('passport'); 3 | var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 4 | require('dotenv').config(); 5 | 6 | passport.serializeUser(function(user, done) { 7 | // done(null, user.id); 8 | done(null, user); 9 | }); 10 | 11 | passport.deserializeUser(function(obj, done) { 12 | // Users.findById(obj, done); 13 | done(null, obj); 14 | }); 15 | 16 | passport.use( 17 | new GoogleStrategy({ 18 | clientID: process.env.GOOGLE_CLIENT_ID, 19 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 20 | callbackURL: "http://127.0.0.1:1337/auth/google/callback" 21 | }, 22 | function(accessToken, refreshToken, profile, done) { 23 | console.log(profile); 24 | // Query the database to find user record associated with this 25 | // google profile, then pass that object to done callback 26 | db.findUserById(profile.id).then(function(id) { 27 | if (id) { 28 | return done(null, profile); 29 | } else { 30 | db.createUser(profile.id) 31 | .then(function(id) { 32 | return done(null, profile); 33 | }); 34 | } 35 | }); 36 | }) 37 | ); 38 | 39 | module.exports = { passport: passport }; 40 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var auth = require('../auth'); 4 | 5 | // GET root page 6 | router.get('/', function(request, response) { 7 | response.render('index', { user: request.user }); 8 | }); 9 | 10 | // GET login page 11 | router.get('/login', function(request, response) { 12 | response.render('login', { user: request.user }); 13 | }); 14 | 15 | // GET route for when you click on login - passport authenticates through google 16 | router.get('/auth/google', 17 | auth.passport.authenticate('google', { scope: ['openid email profile'] })); 18 | 19 | // If successful auth - redirects to home page, if not - redirects to /login 20 | router.get('/auth/google/callback', 21 | auth.passport.authenticate('google', { 22 | failureRedirect: '/login' 23 | }), 24 | function(request, response) { 25 | // Authenticated successfully 26 | response.redirect('/'); 27 | }); 28 | 29 | // GET logout route - will sign person out of session 30 | router.get('/logout', function(request, response) { 31 | request.logout(); 32 | response.redirect('/'); 33 | }); 34 | 35 | // Route middleware to ensure user is authenticated. 36 | function ensureAuthenticated(request, response, next) { 37 | if (request.isAuthenticated()) { 38 | return next(); 39 | } 40 | response.redirect('/login'); 41 | } 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | var express = require('express'); 3 | var app = express(); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var session = require('express-session'); 7 | var path = require('path'); 8 | var auth = require('./auth'); 9 | 10 | var routes = require('./routes/index'); 11 | 12 | app.set('view engine', 'hbs'); 13 | app.set('views', path.join(__dirname, 'views')); 14 | 15 | app.use(logger('dev')); 16 | app.use(cookieParser()); 17 | app.use(session({ 18 | secret: process.env.SESSION_KEY, 19 | resave: false, 20 | saveUninitialized: false 21 | })); 22 | 23 | // Passport/session initialization 24 | app.use(auth.passport.initialize()); 25 | app.use(auth.passport.session()); 26 | 27 | // Set static files to folder /public 28 | app.use(express.static(__dirname + '/public')); 29 | 30 | app.use('/', routes); 31 | 32 | // catch 404 and forward to error handler 33 | app.use(function(req, res, next) { 34 | var err = new Error('Not Found'); 35 | err.status = 404; 36 | next(err); 37 | }); 38 | 39 | /* error handlers */ 40 | 41 | // development error handler 42 | // will print stacktrace 43 | if (app.get('env') === 'development') { 44 | app.use(function(err, req, res, next) { 45 | res.status(err.status || 500); 46 | res.render('error', { 47 | message: err.message, 48 | error: err 49 | }); 50 | }); 51 | } 52 | 53 | // production error handler 54 | // no stacktraces leaked to user 55 | app.use(function(err, req, res, next) { 56 | res.status(err.status || 500); 57 | res.render('error', { 58 | message: err.message, 59 | error: {} 60 | }); 61 | }); 62 | 63 | module.exports = app; 64 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('passport-google-oauth2-example:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '1337'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This work is starting to become really dated and I don't have interest in supporting it, so it is now read-only. 2 | 3 | ## Google OAuth2.0 with Express, PostgreSQL & Passport Template 4 | 5 | This is a template for using Google OAuth2.0 with Express and Passport.js. 6 | 7 | 1. Clone the repo, cd into it, and run npm install. 8 | 9 | 2. Make sure you have PostgreSQL installed and run createdb in your command line. Name your db whatever you'd like 10 | 11 | 3. Open up knexfile.js and under development > connection replace your-database-name, replace with the name of the db you just created. 12 | 13 | 4. Run 'knex migrate:latest' in the command line 14 | 15 | 5. Go to [https://console.developers.google.com/](https://console.developers.google.com/) and create a new project then 'create credentials' for 'OAuth ClientId'. 16 | 17 | 6. Select 'Web Application' and give it a name. 18 | 19 | 7. Set 'Authorized JavaScript origins' to whatever local port you are running the app on (in this template it's set to http://127.0.0.1:1337) 20 | 21 | 8. Set Authorized redirect URIs' to http://127.0.0.1:1337/auth/google/callback 22 | 23 | 9. It will show you clientId and you can click okay. When you get back to the credentials page you can click on your app name and it will give you Client ID and Client secret which you can access at any time. Leave this page open, as we need to put it into our app in step 8. 24 | 25 | 10. Find the button to enable google apis > find the Google+ api > Enable that api. 26 | 27 | 11. Go back into editor and create a .env file. Add into that file 3 new environment variables: 28 | 29 | GOOGLE_CLIENT_ID= 30 | GOOGLE_CLIENT_SECRET= 31 | SESSION_KEY= 32 | 33 | 12. Make certain your .env file is included in your .gitignore - it should already be. 34 | 35 | 13. Run npm start or nodemon and visit http://localhost:1337 36 | 37 | 14. As is, the only thing saved into your database after logging in via Google is the user's googleId. I wanted to set up the db to a basic level. There are many Mongo examples, but not many others... I also left a console.log in on the auth.js file in the GoogleStrategy so that you can view what's coming back from Google and use the info however you'd like! 38 | 39 | 15. :) 40 | --------------------------------------------------------------------------------