Skip to content

Instantly share code, notes, and snippets.

@basiclines
Created May 17, 2020 10:15
Show Gist options
  • Save basiclines/b373eaec35303d7f8865644039a41006 to your computer and use it in GitHub Desktop.
Save basiclines/b373eaec35303d7f8865644039a41006 to your computer and use it in GitHub Desktop.
Server side implementation for Google OAuth2
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