Skip to content

Instantly share code, notes, and snippets.

@thenetimp
Forked from dionysio/index.js
Last active March 14, 2021 00:28
Show Gist options
  • Save thenetimp/0369a603829d811ab335f23c4f511bb7 to your computer and use it in GitHub Desktop.
Save thenetimp/0369a603829d811ab335f23c4f511bb7 to your computer and use it in GitHub Desktop.
Firebase Cloud Function 3rd Party Oauth Flow For Twitch
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