-
-
Save thenetimp/0369a603829d811ab335f23c4f511bb7 to your computer and use it in GitHub Desktop.
Firebase Cloud Function 3rd Party Oauth Flow For Twitch
This file contains 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 functions = require('firebase-functions'); | |
var admin = require("firebase-admin"); | |
const cookieParser = require('cookie-parser'); | |
const crypto = require('crypto'); | |
const axios = require('axios'); | |
admin.initializeApp({}); | |
/** | |
* Creates a configured simple-oauth2 client for Twitch. | |
*/ | |
function twitchOAuth2Client() { | |
// Twitch OAuth 2 setup | |
// TODO: Configure the `twitch.client_id` and `twitch.client_secret` Google Cloud environment variables. | |
const credentials = { | |
client: { | |
id: functions.config().twitch.client_id, | |
secret: functions.config().twitch.client_secret | |
}, | |
auth: { | |
tokenHost: 'https://id.twitch.tv/', | |
tokenPath: '/oauth2/token', | |
authorizePath: '/oauth2/authorize' | |
}, | |
options: { | |
bodyFormat: 'json', | |
authorizationMethod: 'body' | |
} | |
}; | |
return require('simple-oauth2').create(credentials); | |
} | |
/** | |
* Redirects the User to the Twitch authentication consent screen. Also the 'state' cookie is set for later state | |
* verification. | |
*/ | |
exports.twitch_redirect = functions.https.onRequest((req, res) => { | |
const oauth2 = twitchOAuth2Client(); | |
cookieParser()(req, res, () => { | |
const state = req.cookies.state || crypto.randomBytes(20).toString('hex'); | |
console.log('Setting verification state:', state); | |
res.cookie('state', state.toString(), {maxAge: 3600000, httpOnly: true}); | |
const redirectUri = oauth2.authorizationCode.authorizeURL({ | |
redirect_uri: functions.config().twitch.redirect_uri, | |
scope: 'user:read:email', | |
state: state | |
}); | |
console.log('Redirecting to:', redirectUri); | |
res.redirect(redirectUri); | |
}); | |
}); | |
/** | |
* Exchanges a given Twitch auth code passed in the 'code' URL query parameter for a Firebase auth token. | |
* The request also needs to specify a 'state' query parameter which will be checked against the 'state' cookie. | |
* The Firebase custom auth token, display name, photo URL and Twitch acces token are sent back in a JSONP callback | |
* function with function name defined by the 'callback' query parameter. | |
*/ | |
exports.twitch_token = functions.https.onRequest(async(req, res) => { | |
const oauth2 = twitchOAuth2Client(); | |
try { | |
cookieParser()(req, res, async () => { | |
console.log('Received verification state:', req.cookies.state); | |
console.log('Received state:', req.query.state); | |
if (!req.cookies.state) { | |
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.'); | |
} else if (req.cookies.state !== req.query.state) { | |
throw new Error('State validation failed'); | |
} | |
console.log('Received auth code:', req.query.code); | |
try { | |
const results = await oauth2.authorizationCode.getToken({ | |
code: req.query.code, | |
redirect_uri: functions.config().twitch.redirect_uri | |
}); | |
console.log('Auth code exchange result received'); | |
var twitchUser = await getTwitchUser(results.access_token); | |
twitchUser['access_token'] = results.access_token; | |
// Create a Firebase account and get the Custom Auth Token. | |
const firebaseToken = await createFirebaseAccount(twitchUser); | |
// Serve an HTML page that signs the user in and updates the user profile. | |
return res.jsonp({ token: firebaseToken}); | |
} catch (error) { | |
return res.jsonp({error: error.toString()}); | |
} | |
}); | |
} catch (error) { | |
return res.jsonp({error: error.toString()}); | |
} | |
}); | |
/** | |
* Creates a Firebase account with the given user profile and returns a custom auth token allowing | |
* signing-in this account. | |
* | |
* @returns {Promise<string>} The Firebase custom auth token in a promise. | |
*/ | |
async function createFirebaseAccount(twitchUser) { | |
// The UID we'll assign to the user. | |
const uid = `twitch:${twitchUser.id}`; | |
// Save the access token to the Firebase Database. | |
const db = admin.firestore() | |
const databaseTask = db.collection('users').doc(uid).set(twitchUser) | |
// Create or update the user account. | |
const userCreationTask = admin.auth().updateUser(uid, { | |
displayName: twitchUser['display_name'], | |
photoURL: twitchUser['profile_image_url'] | |
}).catch(error => { | |
// If user does not exists we create it. | |
if (error.code === 'auth/user-not-found') { | |
return admin.auth().createUser({ | |
uid: uid, | |
displayName: twitchUser['display_name'], | |
photoURL: twitchUser['profile_image_url'] | |
}); | |
} | |
throw error; | |
}); | |
// Wait for all async task to complete then generate and return a custom auth token. | |
await Promise.all([userCreationTask, databaseTask]); | |
// Create a Firebase custom auth token. | |
const token = await admin.auth().createCustomToken(uid); | |
console.log('Created Custom token for UID "', uid, '" Token:', token); | |
return token; | |
} | |
async function getTwitchUser(accessToken) { | |
try { | |
const response = await axios.get('https://api.twitch.tv/helix/users', {headers: {'Client-Id': functions.config().twitch.client_id, 'Authorization': 'Bearer ' + accessToken}}); | |
return response.data.data[0]; | |
} catch (error) { | |
console.error(error); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment