--- tags: mstu5013, tutorial, js, firebase, realtime --- # Firebase RealtimeDB Tutorial 04: Authentication and Authorization When we think about an APP, one of the common entry requirements is to signup the user and log the user in. That is, we want to ensure that the individual claiming the email abc123@tc.columbia.edu really is the individual who has access to that identity. This is called **authentication**. > Are you who you say you are? Authentication then allows users to have certain privileges. You wouldn't want someone who can't authenticate themselves as _you_, to tweet on your behalf (or at all!) If we think of Twitter for example, autenticated users can post tweets, follow other people, read and comment their tweets, remove or edit their own, etc. This, in terms of database, means that they can write and read to the database. Therefore, when we talk about permissions of who has access (i.e. read, write) to data, This is called **authorization**. > Are you authorized to read or write particular data? Firebase provides us with an easy ways to authenticate and authorize users. Users can sign in using the conventional passwords authentication, or third party providers like Google and Facebook. By default, Firebase gives authenticated users the priviledge to read and write to the database. DEFAULT RULES (i.e. must be authenticated to read and write everywhere): ```json { "rules": { ".read": auth != null, ".write": auth != null } } ``` To learn more about the different ways of authenticating users with Firebase, you can read the [Firebase Documentation](https://firebase.google.com/docs/auth/). ## Google Authentication We are going to cover **Google Authentication** since most of our users have a Gmail account and they probably have some experience using Google as sign-in provider. To have Google as authentication provider up and running we need: 1. Enable Google as a Sign-in Method in Firebase Console. 2. Implement Firebase Methods for sign-in and sign-out in our JS file. ### Firebase Console In order to use Google Authentication, you need to `enable` **Google** as a **sing-in provider**. To do so, go to your Firebase Console and look for Sign-In Methods under the Authentication menu. By default, all providers are going to be disabled. You just need to click on the ones you're interested about (Google in our case) and change it from `Disabled` to `Enabled`. ![](https://i.imgur.com/cDXfpdK.png) Firebase also allows you to manage authorized domains. If you scroll down the same page you will find the default authorized domains, which are the Firebase hosting site to your project and localhost. You can add more domains by clicking on ADD DOMAIN and setting the specific domains that you are interested in using with your App. ![](https://i.imgur.com/tSITYic.png) :::info **Note:** Each domain (e.g. tc.columbia.edu) has to be authorized for the sign-in method. This is important to notice since any authentication attempt that uses a non-aunthorized domain will fail. So for example, in the current illustration above, if your https://codepen.io sample code requires a Google Login, you would need to authorize `codepen.io` to allow Google authentication with your app. This prevents other sites not your domain whitelist, from using your app. By default, your live firebase url and `localhost` will be given authorization. ::: ### JS Code Firebase provide us with easy methods to sign-in and sign-out. We can access all Firebase authentication methods by creating an Auth Object via the following: ```javascript firebase.auth(); // returns an Auth Object ``` Recall that this is somewhat similar in pattern to getting a Firebase Database Object: ```javascript firebase.database(); // returns a Database Object ``` Calling `firebase.auth()` will gives us back the firebase authentication object. Then, depending on what do we want to accomplish, we can append more specific methods to it. Let's see the implementation code to both, sign-in and sign-out methods: #### CODE TO SIGN-IN ```javascript= function logIn() { var provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider); } ``` **Line 1:** We created a general `logIn()` function that can be triggered via say, a click event. **Line 2:** Inside, we are creating an instance of the Google provider object and storing it in the `provider` var. **Line 3:** We are passing the Google provider object as an argument to the `signInWithPopup` method. This will open a popup window and have users sign in using their existing Gmail accounts. Although there are sign-in methods other than `signInWithPopup` (and Google,) we are going to use this one because it's most common for our TC community. You can learn more at [Google Sign-In](https://firebase.google.com/docs/auth/web/google-signin). #### User State > The outcome of signing-in is a change in the state of data based on the _existence of an authenticated user_ or not. In our code, we can get the state of our authenticated user via: ```javascript var user = firebase.auth().currentUser; ``` - IF an authenticated user exists (i.e. logged in) - `firebase.auth().currentUser` will return a user object. - IF an authenticated user does not exist (i.e. logged out), - `firebase.auth().currentUser` will return `null`. By default, logged-out users will have the value of `null`. Indeed, if we `console.log(firebase.auth())`, this is what we get: ![](https://i.imgur.com/9sJwfdt.png) When a user is logged-in, authenticated, `firebase.auth().currentUser` will be a user object with useful properties about the authenticated user. Properties like: - `uid` > LV6KPB8D98347nslidWGfHc6ku2 - `displayName` > Andy Capp - `email` > abc123@tc.columbia.edu - `emailVerified` > true - `photoURL` > http://images.com/imageOfMe.jpg When a user logs out, the user object becomes `null`. Thus, the properties will no longer be available. #### CODE TO SIGN-OUT ```javascript= function logOut(){ firebase.auth().signOut(); } ``` **Line 1:** We create a generic `logOut` function. Can be called from an event for example. **Line 2:** In here, we are using the `signOut()` method to the firebase Auth Object, `firebase.auth()`. By doing so, we are setting the user object back to `null`. :::info **NOTE:** It's important to understand that the call `.signOut()` does not change the state of the user to `null` immediately. This is an _asynchronous_ operation. Here's how it happens.<br><br> 1. `firebase.auth().signOut()` is called. 2. Client sends a signal to server to log the user out. 3. Server ends the server-side session 4. Server sends back a response that the user has been signed out. 5. Client receives this response. 6. If successful, _AT THAT MOMENT_ `firebase.auth().currentUser` is set to `null`. Therefore, in the following example: ```javascript firebase.auth().signOut(); console.log(firebase.auth().currentUser); ``` The `console.log()` will print out a user object while the 3ms operation to talk to the server and properly log the user out is still asynchronously happening. After that 3ms travel time, then the user becomes `null` client-side. It takes a few milliseconds for this process so it's important to understand if we want our apps to do something on the condition of whether `user` exists or not. We need a way to definitively determine _at the moment_ the state of the user (e.g. logged-in or logged-out) changes. We will discuss this in the next section. ::: As you can see, with just a few lines of code, you can have users log-in or log-out. Aside from giving logged-in users some authorization privileges, we can obtain more specific information about them that can later be use to create other artifacts in our database. **Example of Book Model utilizing User Object Data** ```javascript var user = firebase.auth().currentUser; // Assume logged-in var book = { id: "-L9BtjnbdthF9yTt6myU", title: "How to Win Friends and Influence People", author: "Dale Carnegie", read: true, username: user.displayName, userPhoto: user.photoURL, uid: "LV6KPB8D98347nslidWGfHc6ku2", // user ID rating: 5 }; ``` ## User Object When we talk about the state of the user in our app, we're talking about whether the user is authenticated (logged-in) or not (logged-out). Since logging-in and logging-out are asynchronous operations, we can't assume we have a user object right after `.signInWithPopup()` or `null` right after `.signOut()`. What we CAN do is set up a **LISTENER** that listens for that user/auth state change. Whenever a change happens, we can execute a callback that runs different code depending on whether or not we have an authenticated user. **Changes of state based on the existence of user:** - **Before sign-in:** user has value of `null`. - **When signed-in:** a user object with specific properties is created. More specific methods to retrieve and manipulate the user data is available to us. - **When signed-out:** user object is set back to `null`. By signing in with Google, for example, we are obtaining the sign-in information that Google has about the user and setting it to the user object. We can create an instance of the user object as follows: ```javascript= // Creating an instance of the user object var user = firebase.auth().currentUser; // User is logged in console.log(user.email); // prints email console.log(user.photoURL); // prints http://photoUrl console.log(user.uid); // prints the Google user id // User is logged out console.log(user); // prints null console.log(user.email); // Uncaught TypeError: Cannot read property 'email' of null ``` - In _authenticated_ state, user data can be retrieved and manipulated by accessing the user object. We can use the `currentUser` property to access the user data. - In _non-authenticated_ state, there is no user data. `firebase.auth().currentUser` will have value of `null`. ### Listen for changes in the user state As explained before, the `signOut()`, but also `signIn()` methods occur asynchronously. As a consequence, if we may expect some behavior in our app to occur as a result of this change, but we check for an authenticated user before that change has occurred, we might get a logical error. **Example** - User clicks button _SIGN-OUT_ - I expect the page to show the logged out view - App checks `firebase.auth().currentUser` before the async operation actually completes the authentication state change to `null`. - Since user object still exists, page does NOT show logged out view - Async state change happens. - User is logged out, but my code already executed the view toggle - and I'm stuck in the logged in view. One way that we can get around this is by setting a listener on the Auth object. In this way, we can actively listen for changes in the state of the user and ensure that the user object isn't in an intermediate state. By implementing `.onAuthStateChanged(callback)`, we can keep track of the user's sign-in and sign-out status and set a default behavior for when there is a user object and when there is not. ```javascript= // Instance of the user object var user = firebase.auth().currentUser; // User State Listener firebase.auth().onAuthStateChanged(function(userObj) { if (userObj) { user = userObj; // Code to toggle the app state to logged-in view etc. } else { user = null; // Code to toggle the app state to logged-out view etc. } }); ``` - Here, we are listening for changes in the auth state, and setting the `user` variable to the authenticated `userObj` whenever the user is logged-in. Otherwise, `user` will take the value of `null`. If there is a user object, this is what we get: ![](https://i.imgur.com/ji4cFvf.png) Once we create an instance of the user object, we can easily access and pass around that data. **For our purposes, properties that are of special interest are:** - `displayName`: contains the username - `uid`: contains the user unique id - `email:` contains the email used to sign-in **Not so special, but helpful properties:** - `metadata`: contains timestamps with data about the `creationTime` & `lastSignInTime`, and those same times in milliseconds. - `photoURL`: you can use this property to have users upload their profile picture as part of your App. - `emailVerified:` boolean on whether the email provided was verified by Google **LOG-IN & LOG-OUT JS IMPLEMENTATION CODE** ```javascript= // Cached Variables var logOutBtnEl = document.getElementById('logOutBtn'); var logInBtnEl = document.getElementById('logInBtn'); var user = firebase.auth().currentUser; // Listener firebase.auth().onAuthStateChanged(function(userObj) { if (userObj) { user = firebase.auth().currentUser; logInBtnEl.style.visibility = 'hidden'; logOutBtnEl.style.visibility = 'visible'; } else { user = null; logInBtnEl.style.visibility = 'visible'; logOutBtnEl.style.visibility = 'hidden'; } }); // Log In Function function logIn(){ var provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider) } // Log Out Function function logOut(){ firebase.auth().signOut(); } ``` - **Line 2-4:** Creating a cache of the button elements. We are going to use these to toggle the visibility of our buttons based on the user's log-in or log-out state. - **Lines 7-17:** We are setting an observer to keep track of the user's sign-in and sign-out status. It will handle changes in data based on the existence of a user. - **Lines 9-11:** If there is a user object, user will take its value. We are using the `currentUser` method to retrieve the user object data. Based on the same condition, we are setting the `logOutBtnEl` to `visible` , and the `logInBtnEl` to `hidden`, since the existence of a user object implies that there's a user logged-in. - **Lines 13-15:** If there isn't a user object, `user` will take the value of null. Based on the same condition, we setting the visibility of the `logOutBtnEl` to `hidden`, and the `logInBtnEl` to `visible`, since the absence of a user object implies that there's no user signed-in yet. - **Lines 20-23:** We are implementing the function to log-in using Google as a sign-in provider. - **Lines 26-28:** We are implementing the function to log-out. Calling the `.signOut()` method which will asynchronously set the value of user object to `null`. ## Authenticating with RIOT With Firebase ane Riot - what we're mostly interested in is toggling the Riot app components on or off depending on whether the user authentication state is logged-in or logged-out. RIOT makes it easy to toggle the view based on the tags current state. ```jsx <tag> <div id="public-home" if={ !user } >Sign up today!</div> <div id="private-home" if={ user } >Here are your email messages.</div> <script> this.user = firebase.auth().currentUser; // User Object or null </script> </tag> ``` In this way, by using RIOT special attribute `if={}`, we can mount and unmount tags based on the current user state. **Another Example w/ More Tags:** ```jsx= <app> <button if={ !user } onclick={ logIn }>LOGIN</button> <button if={ user } onclick={ logOut }>LOGOUT</button> <homepage if={ !user }></homepage> <user if={ user }></user> <script> var tag = this; this.user = firebase.auth().currentUser; logIn(e){ var provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider); } logOut(e){ firebase.auth().signOut(); } firebase.auth().onAuthStateChanged(function(userObj) { if (userObj) { tag.user = firebase.auth().currentUser; } else { tag.user = null; } }); </script> </app> ``` RIOT will react to changes in user state by mounting and unmounting the `button`, `homepage` and `user` tags. - As you can see on **lines 21-27**, we're adding the `.onAuthStateChanged(callback)` listener to ensure that the state of our tags is always reflecting the most current state of `this.user`. We can also take advantage of the **user object** by borrowing some of its properties when writing to the database. For example, if we are creating a Blog Post App, we can integrate into our `post` object information pertaining to the user object like the username, user ID, etc. By having all this information integrated into our `post` object, it wil be easier for us to organize and manipulate that data, set authorization rules and keep track of authenticated users' activity. Let's see an example: ```jsx= <user> <h1>Hello { user.displayName }. Welcome back!</h1> <h3>Throw a line to the world!</h3> <input type="text" ref="myPost"></input> <button onclick={ savePost }>POST</button> <script> var tag = this; this.user = firebase.auth().currentUser; var database = firebase.database(); var userRef = database.ref('postsByUser/' + this.user.uid); savePost(e){ var myKey = userRef.push().key; // Here, we include authenticated user info in our post model. var post = { id: myKey, userID: this.user.uid, author: this.user.displayName, message: this.refs.myPost.value, email: this.user.email, createdAt: firebase.database.ServerValue.TIMESTAMP, }; userRef.child(myKey).set(post); } </script> </user> ``` - **Line 13:** We are adding the `uid` from the user object as part of the reference. This way we are creating a unique reference point per user using their unique authentication ID (`uid`). - **Line 19-26:** We are creating a new `post` object and embedding some of the user object properties like `uid`, `displayName` & `email`. - Different people might have the same `displayName` e.g. "John Smith" but each user has a unique `uid` - **Line 21:** As you can see, we're not just adding the post unique`id` generated by Firebase but the `uid` property from the user object. In this way, our post model will always be bundled with this important identifying information. We can use information like this, in things like our database rules for authorization. For example, a pseudo-rule might be:<br><br> ``` IF the current logged in user's uid, matches the property userID of the post model, let them read AND write to this reference. ``` Authentication and authorization allows us to do some more sophisticated things with our apps, particularly because most user apps require some sort of _ownership_ of content and artifacts generated by the user. If you plan on releasing an app to a wide audience, this becomes an important concept to implement. But for the purposes of setting up a demo or prototype, it may NOT be as important. You can always "fake" or "simulate" a login flow without these functionalities. But again, if you want users to actually use your app, and they expect some level of privacy, control, etc. you can't really honor that without these tools. Focus on the core user experience. But be aware that these things exist and if you have the time, challenge yourself to implement some of these techniques. :::success **AUTHORS** By Anabel Bugallo and Jin Kuwata :::