Skip to content

Instantly share code, notes, and snippets.

@dionysio
Forked from talk2MeGooseman/index.js
Last active March 14, 2021 03:47
Show Gist options
  • Save dionysio/844f27425b012036923020b119afe1ab to your computer and use it in GitHub Desktop.
Save dionysio/844f27425b012036923020b119afe1ab to your computer and use it in GitHub Desktop.
Firebase Cloud Function 3rd Party Oauth Flow For The Web
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);
}
}
@dionysio
Copy link
Author

to set up:

  1. obtain twitch client ID/secret by setting up oauth app here
  2. run firebase functions:config:set twitch.client_id="XXX" twitch.client_secret="XXX" twitch.redirect_uri="XXX"

@thenetimp
Copy link

thenetimp commented Mar 14, 2021

This doesn't seem to work.

TypeError: require(...).create is not a function

[Edit]: Current version of simple-oauth2 no longer uses this function. Last version to use it is 2.5.2, forcing 2.5.2 in packages.json resolves the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment