Last active
November 16, 2022 15:24
-
-
Save talk2MeGooseman/ff6e13629b48ef2ac603341c33d56f8c to your computer and use it in GitHub Desktop.
Firebase Cloud Function 3rd Party Oauth Flow For The Web
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'); | |
var serviceAccount = require("./service-account.json"); | |
const APP_NAME = "twitch-playground"; | |
admin.initializeApp({ | |
credential: admin.credential.cert(serviceAccount), | |
databaseURL: "https://twitch-playground.firebaseio.com" | |
}); | |
const OAUTH_REDIRECT_URI = `https://localhost:5000/popup.html`; | |
const OAUTH_SCOPES = 'user_read'; | |
/** | |
* 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://api.twitch.tv/kraken/', | |
tokenPath: '/oauth2/token' | |
} | |
}; | |
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.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, secure: true, httpOnly: true}); | |
const redirectUri = oauth2.authorizationCode.authorizeURL({ | |
redirect_uri: OAUTH_REDIRECT_URI, | |
scope: OAUTH_SCOPES, | |
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.token = functions.https.onRequest((req, res) => { | |
const oauth2 = twitchOAuth2Client(); | |
try { | |
cookieParser()(req, res, () => { | |
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); | |
oauth2.authorizationCode.getToken({ | |
code: req.query.code, | |
redirect_uri: OAUTH_REDIRECT_URI | |
}).then(results => { | |
console.log('Auth code exchange result received:', results); | |
// We have an Twitch access token and the user identity now. | |
const accessToken = results.access_token; | |
const twitchUserID = 1; | |
const profilePic = 'www.google.com'; | |
const userName = 'need to do'; | |
// Create a Firebase account and get the Custom Auth Token. | |
createFirebaseAccount(twitchUserID, userName, profilePic, accessToken).then(firebaseToken => { | |
// Serve an HTML page that signs the user in and updates the user profile. | |
res.jsonp({token: firebaseToken}); | |
}); | |
}); | |
}); | |
} 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. | |
* Also saves the accessToken to the datastore at /twitchAccessToken/$uid | |
* | |
* @returns {Promise<string>} The Firebase custom auth token in a promise. | |
*/ | |
function createFirebaseAccount(twitchID, displayName, photoURL, accessToken) { | |
// The UID we'll assign to the user. | |
const uid = `twitch:${twitchID}`; | |
// Save the access token tot he Firebase Realtime Database. | |
const databaseTask = admin.database().ref(`/twitchAccessToken/${uid}`) | |
.set(accessToken); | |
// Create or update the user account. | |
const userCreationTask = admin.auth().updateUser(uid, { | |
displayName: displayName, | |
photoURL: photoURL | |
}).catch(error => { | |
// If user does not exists we create it. | |
if (error.code === 'auth/user-not-found') { | |
return admin.auth().createUser({ | |
uid: uid, | |
displayName: displayName, | |
photoURL: photoURL | |
}); | |
} | |
throw error; | |
}); | |
// Wait for all async task to complete then generate and return a custom auth token. | |
return Promise.all([userCreationTask, databaseTask]).then(() => { | |
// Create a Firebase custom auth token. | |
return admin.auth().createCustomToken(uid).then((token) => { | |
console.log('Created Custom token for UID "', uid, '" Token:', token); | |
return token; | |
}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment