How to Query Google Sign-In Authentication URL From a Client

Having the UI of the Login page built in our client, we'll investigate how we can make the query for Google Sign In's authentication URL when a user attempts to sign in.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two

With our login component now mostly prepared, we're going to handle how we can actually make the authentication URL query to direct the user to the Google consent form and then handle the response when the consent form takes us back to our client application. Just like how we have the viewer concept in our server, we'll create the same viewer concept in our client, which is essentially the person viewing the app. The viewer object on the client will help control and determine whether the user has been logged in and the ID and avatar whether it's available and whether the viewer has connected to their Stripe account. We'll create a viewer interface type in a lib types file, which is to be the file to keep type definitions that need to be accessed in multiple parts of the app. This viewer interface will have the same properties as we've seen before. For ID token avatar has wallet and did request, the ID token avatar field will either be strings or null values. The has wallet field will either be a boolean or a null value and the did request field will be a boolean value. In our GraphQL client library, null means the field's requested is not available, while undefined it will mean that the field itself hasn't been requested. In our case in our login and logout mutations, we're requesting all the fields within the viewer object type and as a result if they're not available, they will come back as null values. In the source index file, which is the main starting point of our app, will import the viewer interface and will use the use state hook to create a viewer object so that our other components can eventually use. We'll initialize the fields in this viewer object with null values except for the did request field, which will give a false boolean value. In the use state hook will also destruct a setViewer function. We'll want to have this setViewer function available in the login component so that login can update this clientViewer object after the login mutation becomes successful. To have the setViewer function available in the login component, we'll need to pass it down as props and with the routes component from React Router, we can take advantage of the render props pattern to pass in the setViewer function. Now, the login component expects a prop with which we should state and check for in the login component file. We'll create a props interface and state that setViewer is a prop it will receive. SetViewer is simply going to be a function that will receive a viewer object with which it's going to use to update the viewer state property. This is how we're going to use the setViewer function and the function doesn't return anything so we'll say it returns void. We have the interface for what this viewer object is so we'll import it and assign it as the type of this viewer argument. We'll then destruct the setViewer prop from the props argument. Let's continue to work on the login component. The first thing we'll want to do is query for the authentication URL from Google OAuth and direct the user to this URL. We've created the auth URL field in our GraphQL API that will provide this authentication URL. We know that the use query hook from React Apollo runs a query upon component amounts but that's not what we want to do here. Here we'll rather have this query field be fired or requested on the click event of the sign in with Google button. This is why React Apollo, among other reasons, also allows us to directly access the client objects with the use Apollo client hook. We'll import the use Apollo client hook from our hooks React Apollo package and at the top of the login component function we'll use the hook to get the client object. With our Apollo client available to us we have access to a query function that will allow us to run a query manually when we want. We'll create a handle authorized components function that will fire when the user clicks the sign in with Google button. We'll attach this function as a click listener to the button. In the handle authorized function we'll want to use the client objects from our user Apollo client hook to request the auth URL query. So we'll import the auth URL query documents and the corresponding type definition for the data from the lib graph ql folders. In handle authorized we'll use a try catch statement. The try statement will use the client object to make our query. The query function will look very similar to how the use query hook behaves. It accepts a type variable for the data and variables that exists and an options argument for where we can pass the query document. And it returns a result of options. In this case we're only interested in returning the data from the query. If we take a look at a browser now to see if everything works we see an error. This is probably due to the fact that we haven't re-exported our query and mutation documents from the queries and mutations folders in our GraphQL folder . So we'll go back and do just that. [silence] Now, when we look at the login page and we actually try to click the sign in with Google button Nothing will happen in our UI since we haven't made any changes but if we take a look in the network tab we can actually see the API call being made and the data now contains the auth URL field that we want. Amazing. What we need to do now is have our app be redirected to this URL since we want the user to be directed directly to the Google consent page. So in our handle authorize function to redirect our app to the new location we 'll use the window location href property and set it to the value of the data auth URL field. Now when we click the sign in with Google button we'll be taken to the Google consent page. Amazing. This consent page is the one referring to the project we've set up in our developer console with which we've given a name of TinyHouse. When we sign in I'm going to use my personal email address here. But now upon entering my password what's going to happen is the Google consent page is going to send us back to the slash login route of our app. How? This is because we've stated in our developer console for our tiny house project the redirect URL is going to be local host 3000 slash login which is the login page for our development environment. Amazing. Notice that we're being redirected to the slash login page but now Google has returned a code as part of our URL query parameter. We've just finished step one of our Google OAuth flow. Now from our client we must pass this code to our node server where our server will use the code to communicate with Google's people API and determine if the person signing in is an existing user or a brand new user signing it to the tiny house application. We've set up the login mutation in our GraphQL API that when triggered will accept the code returned from Google and essentially log in our user. To run this mutation we'll use the use mutation hook react to polar provides. So first we'll import the login mutation document and the auto generated type definitions for the data to be returned and the variables it will accept. And we'll also import the use mutation hook from react to polar. At the top of our login component function we'll use the use mutation hook to destruct the login mutation request as well as the results we're interested in. We're interested in the data loading and error properties of our requests with which we'll also name log in data log in loading and login error to be explicit . We'll want to run the login mutation request in a certain condition. We want to run the request the moment our login component is being rendered and the code is available as a URL query parameter. To run an effect in a certain condition like this we can use react use effect hook. We'll import the use effect hook and declare it near the top of our component function. We're placing an empty dependencies list since we don't want the effect to ever run after the component has been mounted. We'll access the code from the URL query parameter with the URL constructor and the capability to use the search params get property. This will simply return the value of the certain parameter we specify which in this case is the code query parameter. We'll then place an if condition to state that only if the code is available will we make the login request and pass the code as a variable. The use effect hook displays a warning since it tells us that if you want to use the login function you'll need to declare it as a dependency to the hook. This is where we won't do so though. This is because the login request function is being instantiated within the component. If the component was to ever rerender a new version of this login function will be established which may cause our use effect hook to run again. Instead what we're going to do is use a hook another hook that we haven't seen yet called the use ref hook to help us here. The use ref hook accepts an argument with which it returns a mutable object which will persist for the lifetime of the component. Here we'll pass the login function into the use ref hook and with the use ref hook now we can access the argument passed in with the dot current property. This login ref current will reference the original function regardless of how many renders happen and our use effect hook recognizes this and doesn't require us to specify it in the dependencies list. As a note what we've done here should be done sparingly. If you're dealing with actual data properties the effect may depend on you may want to include it in the dependencies list. But in this case we're referencing a function that's always going to remain the same. Now when our mutation fires we'll like to do something right after our mutation is complete successfully. What we intend to do is use the setViewer function available as a prop to update the viewer object available in the parent with the new viewer data being received from the mutation. There's a few ways we can run some functionality after the success of a mutation or a query. But one simple way to do so here is using the on completed callback function available to us as an option of our use mutation hook from reactor polo. On completed is a callback that is executed once a mutation is successfully complete. And the only argument it has is the data from the successful mutation. So in this callback we'll check this data and state that if this data exists we 'll call the setViewer function and set the viewer with the data received. Before we check our progress now let's head to the parent app component and place a console log to see what the value of viewer might be. When we now take a look at our login page we'll see that initially the viewer object has the initial data in our console. We'll go through the login flow and when we return the login mutation will be in flights. Just before the login mutation is fired we'll notice that the initial viewer data is the initial value to specify before but when login is complete we'll see the updated viewer data. This is the details given to us from the server application. If I take a look at the avatar property here I'll see my avatar from my Google account. Amazing. Let's update our login components to better notify the user when logging in is happening and when it completes successfully or where it might error. When the user is being logged in we probably want to show some loading indicator of sorts. So we'll import the add design spin component and return the spin component with a message when logging in is in flight. [silence] When our query is successful or when it errors or when our mutation is successful and when it errors we'll want to handle this in our UI. For most queries and for the login mutation when they run on component mount and are successful we'll use antdesigns notification components to display a success notification. For when a manual query or most mutations fail we'll use antdesigns message components to display an error message. When queries on load or login mutation runs on component mount fail we'll display an error alert banner. These pieces of UI functionality are going to be used in multiple parts of our app so we'll create them in a shared location that can be used everywhere. In the lib folder we'll create a components folder that has an error banner component folder and an index file. And we'll create a utils file within lib that has an index file as well. The error banner components will use antdesigns alert components and may receive a message or description prop. We'll use default props to state that if the message or description is in past we'll say some default values. [silence] And finally we'll simply directly render the alert component. [silence] The utils index file will be used to create the functions antdesigns gives us to render the notification and message components. We'll import the message and notification functions from antdesign and simply use them like how antdesigns gives examples for. We'll have a display success notification function that accepts a message and optional description arguments and will return a success notification that will be placed on the top left. [silence] And we'll have a display error message function that accepts an error property and simply returns a message error. [silence] In our login component we'll import the error banner component from the lib components folder. And we'll import the display success notification and display error message functions from the libutils folder. [silence] At the end of our on completed callback for the login mutation request, we'll use the display success notification function to display a success notification . In our catch statements for our handle authorized function, we'll place the display error message function with an error message. If the login error value is ever true, we'll conditionally create a constant element called login error banner element that is to be the error banner components. And we'll place this above our login card. [silence] The last thing we'll do here is if the user successfully logs in, we wouldn't want to keep them in the login page. We'll want to redirect them to the user page where we'll later populate with the user information. To achieve this, we can use the redirect component that React Router gives us. We'll import redirect from React Router. Since redirect is a component that does a redirect for us, we'll simply check if the login data from our mutation ever exists, and if so, return the redirect component with a 2 prop with a target location of user/viewer ID. Where Viewer ID is the ID of the logged in viewer. [silence] And that will be it for now. At this moment, when we log in, we're taken to the consent form. After completing the consent form, we're taken back to our component where the loading indicator is shown. This is where the login mutation is in flight. When complete, we're redirected to the user page and we see a success notification. If we take a look at the console, we'll see that the client, our application now recognizes the viewer that's logged in and keeps that information within a viewer state value in the parent app component. Amazing. Thank you.