Created
May 17, 2020 10:15
-
-
Save basiclines/b373eaec35303d7f8865644039a41006 to your computer and use it in GitHub Desktop.
Server side implementation for Google OAuth2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const crypto = require('crypto') | |
const url = require('url') | |
const fs = require('fs') | |
const http = require('http') | |
const express = require('express') | |
const {google} = require('googleapis') | |
const successView = require('./success_view') | |
const errorView = require('./error_view') | |
const googleCredentials = require('./google_client_id.json') | |
/* | |
Google OAuth configuration | |
*/ | |
// This is the public address of this server, which Google | |
// will redirect the user to after they authenticate | |
const callbackHost = 'https://mydomain.com/callback' | |
// For Google OAuth and APIs, the client ID can be registered at | |
// https://console.developers.google.com/apis/credentials | |
const oauth2Client = new google.auth.OAuth2( | |
googleCredentials.web.client_id, | |
googleCredentials.web.client_secret, | |
callbackHost | |
) | |
// API client we will consume with our oAuth2Client | |
const gApi = google.people({ | |
version: 'v1', | |
auth: oauth2Client | |
}) | |
// The resources we want to access with Google OAuth | |
const scopes = [ | |
'https://www.googleapis.com/auth/userinfo.profile', | |
'https://www.googleapis.com/auth/userinfo.email' | |
] | |
/* | |
Utilities | |
*/ | |
const readMap = new Map | |
const writeMap = new Map | |
// Handles generic json responses | |
function handleResponse(res, data) { | |
res.writeHead(200, { | |
'Access-Control-Allow-Origin': '*', | |
'Content-Type': 'application/json' | |
}).end(JSON.stringify(data)) | |
} | |
// Obtains the user profile from google | |
async function getMe(code) { | |
const {tokens} = await oauth2Client.getToken(code) | |
oauth2Client.setCredentials(tokens) | |
const me = await gApi.people.get({ | |
resourceName: 'people/me', | |
personFields: 'emailAddresses,names,photos' | |
}) | |
return { | |
email: me.data.emailAddresses[0].value, | |
name: me.data.names[0].displayName, | |
avatar: me.data.photos[0].url | |
} | |
} | |
/* | |
Server configuration and routing | |
*/ | |
const app = express() | |
const port = process.env.PORT || 8081 | |
const options = { } | |
// Test endpoint | |
app.get('/', (req, res) => handleResponse(res, { server: "ok" })) | |
// Generate a new read/write keypair | |
app.get('/keys', (req, res) => { | |
crypto.randomBytes(64, (err, buf) => { | |
if (err) throw err | |
const read_key = buf.slice(0, buf.length >> 1).toString('base64') | |
const write_key = buf.slice(buf.length >> 1).toString('base64') | |
const obj = { read_key, write_key, user: { me: null } } | |
readMap.set(read_key, obj) | |
writeMap.set(write_key, obj) | |
// Store created read/write pairs for 5 min. | |
setTimeout(() => { | |
readMap.delete(read_key) | |
writeMap.delete(write_key) | |
}, 5 * 60 * 1000) | |
handleResponse(res, { read_key, write_key }) | |
}) | |
}) | |
// Redirect user to Google OAuth with the obtained write_key as state | |
app.get('/start', (req, res) => { | |
const write_key = req.query.write_key | |
if (write_key) { | |
const oauthUrl = oauth2Client.generateAuthUrl({ | |
access_type: 'online', | |
state: write_key, | |
scope: scopes | |
}) | |
res.writeHead(302, { 'Location': oauthUrl }).end() | |
} else { | |
res.writeHead(401).end('Unauthorized: Obtain a valid write_key from /keys') | |
} | |
}) | |
// Callback to receive authorization from Google OAuth | |
app.get('/callback', (req, res) => { | |
const code = req.query.code | |
const write_key = req.query.state.replace(/ /g, '+') // Black magic that replaces `+` with `%20` in callback url | |
const credentials = writeMap.get(write_key) | |
let content = '' | |
if (write_key && credentials) { | |
(async () => { | |
let html = '' | |
try { | |
const me = await getMe(code) | |
credentials.user = { me: me } | |
writeMap.delete(write_key) | |
content = { me: me } | |
} catch (e) { | |
content = { error: 'error' } | |
} | |
if (content.error) { | |
html = errorView.render() | |
} else { | |
html = successView.render(content) | |
} | |
// Show success page | |
res.writeHead(200, { 'Content-Type': 'text/html' }).end(html) | |
})() | |
} else { | |
res.writeHead(301).end('Unauthorized: Obtain a valid write_key from /keys') | |
} | |
}) | |
// Finish Google OAuth | |
app.get('/finish', (req, res) => { | |
const read_key = req.query.read_key.replace(/ /g, '+');// Black magic that replaces `+` with `%20` in callback url | |
const readCredentials = readMap.get(read_key) | |
if (read_key && readCredentials) { | |
let user = readCredentials.user | |
if (user.me !== null) readMap.delete(read_key) | |
handleResponse(res, user) | |
} else { | |
res.writeHead(401).end('Unauthorized: Obtain a valid read_key from /keys') | |
} | |
}) | |
app.use(function (req, res, next) { | |
res.status(404).end("404: endpoint does not exist") | |
}) | |
// Run server | |
let server = app.listen(port, () => console.log(`Auth server listening on ${server.address().address}${server.address().port}`)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment