├── 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}}
{{/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 |
--------------------------------------------------------------------------------