Closed wcousin closed 4 years ago
Here's my Firebase function:
// Securely pass JWT token so users can connect to Twilio call
exports.twilioTokenHandler = functions.https.onCall((data) => {
const AccessToken = Twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
const twilioAccountSid = functions.config().twilio_api.account_sid;
const twilioApiKey = functions.config().twilio_api.key;
const twilioApiSecret = functions.config().twilio_api.secret;
const token = new AccessToken(twilioAccountSid, twilioApiKey, twilioApiSecret);
token.identity = data.uid;
const videoGrant = new VideoGrant({ room: data.roomName});
token.addGrant(videoGrant);
console.log("Sending token: ", token);
return {
token: token.toJwt()
}
});
state > index.tsx
const getToken: StateContextType['getToken'] = (name, room) => {
var data = {
uid: name,
roomName: room
}
setIsFetching(true);
return functions.httpsCallable('twilioTokenHandler')(data).then((response: any) => {
setIsFetching(false);
return response.data.token
}).catch((error: any) => {
setIsFetching(false);
setError(error);
console.error("Error getting Twilio token: ")
console.error(error)
return Promise.reject(error);
});
};
Button to connect:
<button
className="s-btn-success ring-pulse"
onClick={() => getToken(props.userId, props.roomName).then(token => connect(token))}
disabled={isConnecting || isFetching}>
<i className="fas fa-phone" /> Connect with {props.userFirstName} now!
</button>
Thank you for your reply.
What file are you placing your button code?
I just put mine in the MenuBar.tsx
Thanks for the question @wcousin! In order to help out, I'll need some more information. It sounds like there may be a problem with your server. What are you deploying to firebase, and how are you deploying it? What is the error that you are seeing (can you look in the browser console for additions clues)? Can you provide some sample code?
The more information you can provide, the more helpful we can be 🙂
Basically, I deployed the repo to firebase hosting and it runs the single page app just fine. The issue is that it does not connect to the Twilio API since I don't know how to setup the environment variables in firebase. I'm attempting to learn Firebase functions for this, but it seems I would have to modify the app to make that work.
I also tried deploying to heroku where I know how to add the .env variables, but when I run the app I get an Invalid Headers error.
I'm fairly in-experienced deploying react apps so thank you for your help, @timmydoza
I just put mine in the MenuBar.tsx
I'm really confused with this. Can you share your repo that I could pull down?
I can't share my main repo, but I can share this repo where I converted the twilio-video-app-react repo to a different structure. I am calling my Firebase Function that I outlined above from it, so just reference that code snippet for the token grab.
For reference, you cannot use React environment variables from the .env file (ex: REACT_APP_FIREBASE_LIVE_API_KEY
) in the Firebase Functions because the difference is client vs server side. You need to use the functions config variables instead (ex: functions.config().twilio_api.account_sid
). Let me know if you need help setting the firebase function config variables.
I don't know, man...I'm pretty lost on this. I even tried deploying on heroku and I get an 'invalid host header' message.
Hey @wcousin - sorry to hear you're still having trouble on this. I'd like to help you get this deployed somewhere. Do you have a specific platform in mind (Firebase, Heroku, etc.) or can it be deployed on any platform? Also, are you trying to enable Firebase Authentication as well (with the REACT_APP_SET_AUTH=firebase
environment variable)?
Sounds like you are getting the web app deployed just fine, but it's having trouble talking to the backend to get a video access token. There is a server that comes with the app server.js
that can be used for these video access tokens when deployed somewhere.
We have an internal deployment of the app that is hosted on Google App Engine. If it doesn't matter where your app is deployed, then it should be fairly easy to get you set up there. Let me know what you think!
I would really love to deploy on Firebase, but if Heroku is easier, then I would be very happy with that too! Really, it can be deployed on any platform, but I want to be able to use the Firebase login.
I have enabled the Firebase environment variable. I can run the app and connect with both Firebase Authentication and Twilio APIs locally - everything works fine on my local machine. I can also deploy to Twilio using the passcode authentication, but I want to use Firebase authentication.
The app deploys perfectly fine on Firebase hosting, but it does not connect to the Twilio API because Firebase doesn't use the .env file.
It doesn't matter where the app is deployed, but I just want to be able to use Firebase Authentication and not the Twilio passcode.
BTW, thank you so much for your help! I didn't expect answers so fast.
@timmydoza Any other suggestions on this?
Thanks!
Hey @wcousin!
It can be a little tricky to set up the firebase authentication. It's important to keep in mind that two things muse be implemented here in order for the application to be secure: 1) Authentication, and 2) Authorization. Authentication is the act of validating that users are who they claim to be. Authorization is the process of giving the user permission to access the app.
It sounds like you already have Authentication working. This is what you see when you configure the app with the Firebase credentials and the REACT_APP_SET_AUTH=firebase
. This alone won't secure the app. It just requires users to verify their identity with Google.
Authorization would happen on the server, and it's up to you to determine which users you should grant access to. When REACT_APP_SET_AUTH=firebase
is set, a user's Firebase ID Token will be included in the Authorization header of the HTTP request that is used to obtain an access token. The Firebase ID Token can then be verified by the server that dispenses access tokens for connecting to a room. See this article on how to verify Firebase ID Tokens on the server: https://firebase.google.com/docs/auth/admin/verify-id-tokens
Here's an example of this. Say you want to limit the app's access to just a few users. You could write some code like this on your server:
const ALLOWED_USERS = ['test123@gmail.com', 'anotherTestUser@gmail.com'];
app.get('/token', async (req, res) => {
const authorization = req.get('Authorization') || '';
const token = await admin.auth().verifyIdToken(authorization);
 if (ALLOWED_USERS.includes(token.email)) {
// Send token
} else {
res.send(401); // Not Authorized
}
});
You may be able to modify the included server to perform this authorization check.
For deploying the app - just because you are using Firebase Auth doesn't mean that it has to be hosted there. If you do choose to use Firebase for hosting, then the included server.js
won't work. You would have to make a Firebase Cloud Function to perform the Authorization and dispense a token. Other providers just as Heroku or Google App Engine may let you run server.js
with Node.js.
With Heroku, it's important to know that it will try to run npm start
to start your app if no Procfile is found: https://devcenter.heroku.com/articles/deploying-nodejs#specifying-a-start-script
Deploying the app with npm start
is improper, as npm start
will serve the app in development mode. This shouldn't be done in production, and it also looks to be the reason that you are seeing the 'invalid host header message'. The best way to serve this app is to build all of the files with npm run build
, and then serve the static assets that are produced (server.js already does this for you). You'll have to set up heroku so that it runs npm run server
(after npm run build
has run) as it's start script, and you'll also need to configure server.js
to use the port as specified by process.env.PORT
.
Authentication, Authorization, and Deployment are tricky topics. I hope this can get you going in the right direction, but unfortunately I can't tell you exactly what to do. Lots of this remains up to you and your use case. Our goal was to make this app relatively unopinionated. We want users to use any auth/deployment combo that makes sense to them. This means that there isn't much of an out-of-the-box solution (other than the Twilio CLI quick deploy).
Let me know if you have any other questions! And good luck!
(I'm happy to continue the discussion, but I'm going to close this issue as there is no associated issue in the app that needs to be fixed.)
@wcousin if you need further assistance, we could actually use the software I am building using Twilio to help you fix your setup problem. Feel free to ask @ www.minute.tech
I wanted to let you know that I figured this out. Thank you so much for your help. Here is the solution to deploying this on heroku:
Modify your server.js file as follows:
const express = require('express'); const app = express(); const path = require('path'); const AccessToken = require('twilio').jwt.AccessToken; const VideoGrant = AccessToken.VideoGrant; require('dotenv').config();
const MAX_ALLOWED_SESSION_DURATION = 14400; const twilioAccountSid = process.env.TWILIO_ACCOUNT_SID; const twilioApiKeySID = process.env.TWILIO_API_KEY_SID; const twilioApiKeySecret = process.env.TWILIO_API_KEY_SECRET;
app.use(express.static(path.join(__dirname, 'build')));
if (process.env.NODE_ENV === 'production') { // Set static folder app.use(express.static(path.join(__dirname, 'build')));
app.get('/token', (req, res) => {
const { identity, roomName } = req.query;
const token = new AccessToken(twilioAccountSid, twilioApiKeySID, twilioApiKeySecret, {
ttl: MAX_ALLOWED_SESSION_DURATION,
});
token.identity = identity;
const videoGrant = new VideoGrant({ room: roomName });
token.addGrant(videoGrant);
res.send(token.toJwt());
console.log(issued token for ${identity} in room ${roomName}
);
});
app.get('*', (_, res) => res.sendFile(path.join(__dirname, 'build/index.html')));
}
const port = process.env.PORT || 8081;
app.listen(port, () => console.log(token server running on ${port}
));
Then modify your package.json script to this:
"scripts": { "client-install": "npm install", "start": "node server.js", "dev": "concurrently npm:server npm:dev", "build": "node ./scripts/build.js", "test": "jest", "eject": "react-scripts eject", "lint": "eslint src/*/.{ts,tsx}", "server": "nodemon server.js", "test:ci": "jest --ci --runInBand --reporters=default --reporters=jest-junit --coverage", "cypress:open": "cypress open", "cypress:run": "cypress run --browser chrome", "cypress:ci": "cross-env CYPRESS_baseUrl=http://localhost:8081 start-server-and-test server http://localhost:8081 cypress:run", "deploy:twilio-cli": "cross-env REACT_APP_SET_AUTH=passcode npm run build && twilio rtc:apps:video:deploy --authentication=passcode --app-directory ./build", "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm run install-client && npm run build" },
You will need to set up your environment variables within Heroku. I have this running on heroku using the firebase login - not the passcode.
Also, you should probably install ncu and update the dependencies with that before deploying.
I hope that helps anyone else in the future.
Fantastic! Great to hear 🙂
I finally got this puppy running on Firebase hosting too. Here's how I did it:
In the project root folder, I ran initiated the firebase project by running 'firebase init' (must be logged into Firebase through the console). In the init steps I selected 'firebase hosting', deploy folder to 'build', 'no' to overwrite the index.html file, and 'no' to single page application.
Then I initialized firebase functions following similar instructions. Here is the key to it all:
Initializing Firebase functions creates a folder called "functions". Go to this folder in your console and run npm install express cors twilio twilio-video
Inside the functions folder, open the index.js file and replace it with the following code:
const functions = require('firebase-functions'); const express = require('express'); const cors = require('cors'); const app = express(); const path = require('path'); const AccessToken = require('twilio').jwt.AccessToken; const VideoGrant = AccessToken.VideoGrant;
const MAX_ALLOWED_SESSION_DURATION = 14400; const twilioAccountSid = '########################'; const twilioApiKeySID = '#########################'; const twilioApiKeySecret = '#######################';
app.use(express.static(path.join(__dirname, 'build')));
app.get('/token', (req, res) => {
const { identity, roomName } = req.query;
const token = new AccessToken(twilioAccountSid, twilioApiKeySID, twilioApiKeySecret, {
ttl: MAX_ALLOWED_SESSION_DURATION,
});
token.identity = identity;
const videoGrant = new VideoGrant({ room: roomName });
token.addGrant(videoGrant);
res.send(token.toJwt());
console.log(issued token for ${identity} in room ${roomName}
);
});
app.get('*', (_, res) => res.sendFile(path.join(__dirname, 'build/index.html')));
app.listen(8081, () => console.log('token server running on 8081'));
exports.app = functions.https.onRequest(app);
Notes:
In the root folder, run npm run build
and then run firebase deploy
and your app should work perfectly. Of course you will need to have Google Authentication enabled in the Firebase console...don't forget that.
For reference, here are my dependencies in the functions/package.json file:
"dependencies": { "cors": "^2.8.5", "express": "^4.17.1", "firebase-admin": "^8.10.0", "firebase-functions": "^3.6.1", "twilio": "^3.46.0", "twilio-video": "^2.5.1" },
Friends,
I appreciate the Heroku and Firebase deployment solutions above, but has anyone solved firebase auth build deployed to Twilio Functions?@twilio anyone? :)
I'm trying to deploy this to firebase hosting and everything works great except when I try to connect to conference room I get an invalid token error. I tested locally first and everything connected.
How would I modify this to use cloud functions to connect to generate Twilio tokens from Google cloud functions?