- Firebase is a comprehensive app development platform, with multiple tools we can use to make app development easier.
- Last week we covered:
- Setting up Firebase
- Firestore
- Firebase Hosting
- This week we will cover:
- Authentication
- Web Functions
-
If you haven't done so, create a Firebase Project (instructions here).
-
If you haven't done so, add Firebase to your web app (instructions here).
-
Multiple ways of doing this, but we will be using the Content Delivery Network (CDN) to do so. Other methods can be found here.
-
In the
<body>
of yourindex.html
, below the line which imports the core Firebase SDK, importfirebase-auth
. To illustrate, your code should look something like this:<body> <!-- Firebase App (the core Firebase SDK) is always required and must be listed first --> <script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-app.js"></script> <!-- Import firebase-auth --> <script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-auth.js"></script> </body>
-
In the side bar, click the cog on the right of
Project Overview
. There should be a dropdown menu. Select theProject settings
option. -
Scroll down to find the
Firebase SDK snippet
, and make sure that theCDN
option is selected. -
Copy your web app's Firebase configuration and initialise Firebase in your app. It should look something like the following:
<body> <!-- Previously loaded Firebase SDKs --> <script> // Replace the following with your app's Firebase project configuration var firebaseConfig = { apiKey: "<api-token>", authDomain: "<project-id>.firebaseapp.com", databaseURL: "https://<project-id>.firebaseio.com", projectId: "<project-id>", storageBucket: "<project-id>.appspot.com", messagingSenderId: "<message-sender-id>", appId: "<app-id>", measurementId: "G-<measurement-id>", }; // Initialize Firebase firebase.initializeApp(firebaseConfig); </script> </body>
First, let's specify where the sign-up / login inputs will be. For ease of implementation, let's display and hide content based on whether a user is logged in.
-
In the
div
block with class "Modal", enclose the content with an innerdiv
block.<!-- Original code: --> <body> <div class="Viewframe"> <div id="demo" class="Modal"> <img src="./assets/logo/logo_small.png" /> <!-- other content in Modal --> </div> </div> </body> <!-- Modified code: --> <body> <div class="Viewframe"> <div id="demo" class="Modal"> <img src="./assets/logo/logo_small.png" /> <div id="main"> <!-- other content in Modal --> </div> </div> </div> </body>
-
Add the div block containing the login / sign up block:
<!-- Original code: --> <body> <div class="Viewframe"> <div id="demo" class="Modal"> <img src="./assets/logo/logo_small.png" /> <div id="main"> <!-- content in main --> </div> </div> </div> </body> <!-- Modified code: --> <body> <div class="Viewframe"> <div id="demo" class="Modal"> <img src="./assets/logo/logo_small.png" /> <div id="main"> <!-- content in main --> </div> <div id="login"> <h1>Login / Sign Up</h1> <p> Just enter an email and we will allow you to login / sign up accordingly! </p> <div id="firebaseui-auth-container"></div> <div id="loader">Loading...</div> </div> </div> </div> </body>
<div id="firebaseui-auth-container"></div>
will be where the authentication container will be placed.<div id="loader">Loading...</div>
is a loading placeholder to be visible before the authentication container has loaded.- Now, there are 2 blocks, "main" and "login", and we want to load them according to if there is a user signed in.
-
To optimise user experience, we will first hide both blocks. To do so, we will first get the element by id as taught in the JS workshops, then setting their display to 'none', as shown below.
<script> let login_section = document.getElementById("login"); let main_section = document.getElementById("main"); login_section.style.display = "none"; main_section.style.display = "none"; // other content </script>
- On the side bar, click "Authentication". You will see the screen below.
- Click "Set up sign-in method" button. You will see the screen below.
- There are multiple sign-in options available to choose from. We will be using the
Email/Password
Method as it is the easiest to set up. More information on how to set up other methods can be found here. - Click the option we are using and click the toggle to enable it.
- That's it! You can now start using Firebase Auth on the front end!
-
There are multiple ways of setting this up as well, but we will be using FirebaseUI Auth as it is an easy drop-in solution. Other ways can be found here.
-
To use FirebaseUI Auth, we will need to import FirebaseUI as well with the following:
<body> <!-- Previously loaded Firebase SDKs, Firebase Config and App Initialisation --> <!-- Import FirebaseUI and its CSS --> <script src="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.js"></script> <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css" /> </body>
-
Initialize the FirebaseUI Widget using Firebase.
let ui = new firebaseui.auth.AuthUI(firebase.auth());
-
Configure FirebaseUI.
let uiConfig = { callbacks: { signInSuccessWithAuthResult: function (authResult, redirectUrl) { // User successfully signed in. // Return type determines whether we continue the redirect automatically // or whether we leave that to developer to handle. return false; }, uiShown: function () { // The widget is rendered. // Hide the loader. document.getElementById("loader").style.display = "none"; }, }, signInOptions: [firebase.auth.EmailAuthProvider.PROVIDER_ID], };
Let's unpack this:
-
callbacks
A callback is a function that is passed into another function as an argument, which is then invoked in the outer function to complete kind of action or routine.
- Put into context:
signInSuccessWithAuthResult
specifies what to do in the case of a successful sign in.uiShown
specifies what to do once the ui has been shown.document.getElementById
basically gets the element in thehtml
by the id we specified. This was previously covered in our Intro to JS Workshop here..style.display = 'none'
sets the display of this selected element to none, meaning that it would not be shown. To show it, you could set this value to'block'
(this is set by default).
- Note: these callbacks are optional and is not a required argument of uiConfig.
- Documentation on available callbacks found here.
- Put into context:
-
signInOptions
-
This array indicates the options which would be available to the user to sign in or login.
-
Other methods sign in options can be displayed with options such as the following:
signInOptions: [ firebase.auth.EmailAuthProvider.PROVIDER_ID, firebase.auth.GoogleAuthProvider.PROVIDER_ID, firebase.auth.FacebookAuthProvider.PROVIDER_ID, firebase.auth.TwitterAuthProvider.PROVIDER_ID, firebase.auth.GithubAuthProvider.PROVIDER_ID, firebase.auth.PhoneAuthProvider.PROVIDER_ID, ],
-
-
-
Render FirebaseUI Auth Container.
// Set UI config ui.start("#firebaseui-auth-container", uiConfig);
-
Now, let's display based the blocks based on if a user is signed in. Use the code below to trigger a change when the authentication state is changed.
firebase.auth().onAuthStateChanged(function (user) { if (user) { // User is signed in. } else { // No user is signed in. } });
-
CHALLENGE: Display and hide blocks based on authentication state.
- Hints:
- You can make a block visible by setting an element's
style.display
property to 'block'. - Refer to step 3 of Create Login Page, the code there is similar and can be used here.
- You can make a block visible by setting an element's
- Hints:
-
You should be able to sign-in now!
After signing in, we have to sign out as well. Let's see how to do that here.
-
Let's add the sign out button at the top of the page. (CHALLENGE: if you have gone through the previous JS workshops, try to do this without looking at the hints first :))
-
Hint:
-
Buttons are created using the
<button>
tag in html. -
To add text, type it between the
<button>
and</button>
tag. It should look something like this:<button>SIGN OUT</button>
-
To trigger something on click, use
onclick=...
. So if your sign out function is calledsignOut
, your code should look something like this.<button onclick="signOut()">SIGN OUT</button>
- Note: the function
signOut
has not been created yet.
-
-
-
Let's create the sign out function:
function signOut() { firebase .auth() .signOut() .then(function () { // Sign-out successful. ui.start("#firebaseui-auth-container", uiConfig); }) .catch(function (error) { // An error happened. console.log("error:", error); }); }
-
You can now logout and in easily!
To add profile information, we can use the user
object that we get once the authentication state is changed.
-
CHALLENGE: In the "main" block, let's add a
div
block called "greeting".- There should be no content in the "greeting" block.
-
Find the part in your code where we display blocks based on whether a user is logged in. It should look something like this:
firebase.auth().onAuthStateChanged(function (user) { if (user) { // User is signed in. login_section.style.display = "none"; main_section.style.display = "block"; } else { // No user is signed in. login_section.style.display = "block"; main_section.style.display = "none"; } });
-
To get the user's name, we can use
.displayName
property of the user object. Let's first save the user's name with a variablename
.firebase.auth().onAuthStateChanged(function(user) { if (user) { // User is signed in. login_section.style.display = 'none'; main_section.style.display = 'block'; let name = user.displayName;
-
Pro-tip: There are actually many other properties of the user object which we can use, such as the following:
email = user.email; photoUrl = user.photoURL; emailVerified = user.emailVerified; uid = user.uid;
- More information on user profile's properties in the documentation here.
-
-
Let's change the
profile
block we made earlier to display a "Welcome, [name]" message. Below the name variable, type the following:let profileDiv = document.getElementById("profile"); profileDiv.innerHTML = "<p>Welcome, " + name + "</p>";
Do note that to use Cloud Functions, Google requires the project to add a billing account and use the Blaze Plan instead of the Spark Plan (free). There are still free limits each month, so for a small demo or project like the one we are doing, we will not need to pay anything.
- Free tier limits are specified here.
- Difference between Firebase's Spark Plan and Blaze Plan is stated here.
- On the side bar in Firebase, click the "Upgrade" button beside "Spark". You should see this:
- Click "Select plan" under Blaze.
- Next, you should see this:
- Click "Continue" and follow through the procedure to add your billing information.
- After adding your billing account, you can add Budget Alerts as well so that Google will warn you when you have spent 50%, 90% and 100% of your budget.
- After this, we can use Cloud Functions :D
-
Navigate to
Functions
on the side bar in Firebase. -
Click
Get started
andcontinue
twice. -
Install Firebase tools. Open your terminal and navigate to the project folder. Enter the following line into it.
npm install -g firebase-tools
-
Configure
firebase-tools
withfirebase login
. Enter the following command in your terminal and follow the prompts:firebase login
- Enter
y
and login to the Google Account with the project as prompted.
- Enter
-
Initiate your project:
firebase init
-
Now, the command line will prompt you to pick some options. Read the instructions carefully on how to pick them. Pick the following options when prompted:
- Functions: Configure and deploy Cloud Functions
- Use an existing project
- Select the project that you are using for this workshop
- Javascript
- y
- y
Cloud functions are written in (surprise surprise) Javascript, albeit in a node.js environment instead of the browser environment. Syntax wise they are identical, but node.js gives us a few extra features, like modules.
Cloud functions can be invoked based on many different triggers, such as a HTTP request to the function endpoint, changes made on a firestore database, events on firebase authentication, and many more!
For a simple example, lets look at at the example cloud function generated by firebase init
in your/project/director/functions/index.js
const functions = require('firebase-functions');
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
Here we see some built-in functions and syntax we've never seen before. Firstly, require
. When you call require
, with the module name of a dependency, it makes sure the module is loaded and returns its interface. This is convenient, as it now means we can use functions and utilities from packages other people have made.
In this case, we will be using the firebase-functions
package, and binding the interface object to the functions
variable.
Next, exports
. What we are writing here, in index.js
, is basically a node.js module. That means it will be called by the require
function and loaded somewhere else. Now, by default, this module will not expose any of the methods and variables in the scope of index.js
unless we explicitly add it to the exports
object. Therefore, what we are doing here is exposing the method returned by functions.https.onRequest()
by binding it on the helloWorld
attribute on the exports
object.
Now, in contrast to the javascript functions we have written previously, calling functions.https.onRequest
returns a function, instead of an object or a primitive type like a number or a string. This is because Javascript supports first class functions, which allow us to treat functions like variables. This also means we can return functions from functions, and pass functions as arguments to functions. We call functions that take functions as input arguments and give functions as return values higher-order functions
Specifically, functions.https.onRequest
takes as an argument a function that has the arguments a Express Request
and a Response
object. The Request object gives you access to the properties of the HTTP request sent by the client, and the Response object gives you a way to send a response back to the client.
Getting started with Firebase Cloud Functions
-
Deploy your (only) your functions:
firebase deploy --only functions
-
That's it!