A couple of weeks ago, I had to create an authorization process by Google API in an Apollo app. Perhaps, it wouldn’t have been such a big deal if I could make it in an easy way, using express.js server and passport.js. Yet I needed to use specifically Apollo server as we were using GraphQL/React application.
I had to make it for the first time, so I was looking for help everywhere. Since I work in a great company, I didn’t have to look far away. One of my senior colleagues explained to me through and through how to do that. This in turn has pushed me to write this article. It is a long way with quite a few spin-offs. I hope it’ll help you guys to make it easy.
How Apollo and Google authorization works.
This method looks simple but it’s not. It’s not a complicated JavaScript code but the sheer process can give you a headache.
Let us start with explaining how to organize the Google authorization.
(1) First of all, you must register your app in the Google console, log in and choose APIs and then Credentials.
The next step is to choose "Create OAuth client ID". Select application type: "Web application", name your app and set the URI: for example, localhost:3000
.
Afterwards, when you click on your App, you get Client ID & Client secret. These two keys are necessary for our next steps, because we will use it to communicate with Google API. In the bookmark "OAuth2 consent screen" you can set an authorization domain which tells us what domain users can log in from.
(2) At last, it’s time you get your hands on a rudder and start a real development. I think there are many different ways to do that. One of them, and quite a famous one, you can find here.
I followed this example more or less while also tried to combine many approaches together.
So now let’s make a simple login button:
I hope you know how to use CSS. Then, let’s put some logic behind.
I used react-create-app and because of that I lost some time to find where exactly I should put an external source of code. But the solution proved to be a simple one. Put it in public/index.html
in <head>
section:
This external code gives us the possibility to connect with Google API.
(3) In my client/index.js
I initialize my whole app with this script. I love this part of our app. The app has been written by both me and my colleague. Our approach to programming is totally different. I’m the one who wants to write quickly, even though less carefully, and he keeps insisting on refactoring the code constantly. We had a little fight on that. I asked him what the purpose was of improving the already perfect code from react create app designers. In the line 31 below you can see a non-standard way to load react app in an event listener. At the outset, it looked totally useless to me. But a day later I had to accept my colleague’s arguments as it proved to be a perfect place for me to fire my Google OAuth2 init function.
I hope the code is readable for you guys. It’s simple. All you need to know is your hostedDomain (optional) and clientId. This is what you created in the Google console. Of course you can hard-code it here, store in some config.js
file or in process.env
. It’s up to you. The rest of the file I’ll show later while explaining how I set headers in the context (that’s our goal). Below index.js
.
My component that use this script looks as follows.
What am I doing here? onClick
I fire the handleSignInClick
function that in turn takes an OAuth2 instance and tries to sign in. Now we can see how funny Google API is as we can see there many weird shortcuts like Zi, Eu, etc. You can console.log(res)
to see what else is inside. In .then()
I’ve implemented a login function that triggers a GraphQL mutation. I’ll show it to you later, just note in passing for now that I put here two important variables, namely webToken and idToken. The latter I’ll send to our server and then exchange it for a signed by JWT key. This key will help me to check if the user is logged in and whether he or she has permission to get data from the server. Subsequently I refetch one Query
: <Query query={GET_USER}>
which checks whether the user is logged in or not. I render the Routes based on whether currentUser is logged in or out. But routes are not our topic here.
Once the user clicks the button he or she sees the following simple and well known window.
I set the authorization domain before only for users from vazco.eu
. They can log in to my app. Now we can jump into the rabbit hole.
(4) Once we get idToken we can see what is happening on the server. Below you can see how I made part of my schema.js
file. Of course I adapted a standard approach with typeDefs as a gql
tag. The part which interests us most is Mutation login which returns UserInfo:
Primarily login returned string, but later I use this mutation to smuggle funny business idea with default user view.
Now let’s look into resolver.js
below. Here’s our login mutation. I get webToken from mutation variables, and give it to googleVerify function. I update or create user in MongoDB. In the context I store database instance and user. I will show it soon.
This mutation returns what we define in schema – a token which is a string and my defaultView which is of no matter for the topic. I’m using here jwt from jsonwebtoken, a library to sign a token and later verify the token (my app’s own token). This token is simply an encrypted user ID from my MongoDB. It is signed with JWT_SECRET (here’s where you can use your Google client secret key, but one of my colleagues decided to change it on JWT, so my client secret is useless now) which I stored in process.env
. Of course you can hard-code it here for example in const but I don’t recommend it.
What does in fact googleVerify do?
It returns a Google user based on idToken. The structure is easy, as you can see we only need a Google id key and idToken, and once Google verification is done we are done with Google as well.
(5) If you have a look on the mutation you can see that I returned an encrypted token to our client part of the app. Now, as you can see when mutation is done I put token to the localStorage. And that’s my point.
(6) Below there’s a lost part of the code from (3). And now crème de la crème. I get token from the local storage. If the user is logged in – after the token has been decrypted – our Apollo app knows that it’s a correct user. Of course if you stick to the code from (3) and code below, you’ll see my client side initialization process as a whole.
(7) The next step is to get token from header in server/context.js
. Once we’ve grabbed token we’re going to decrypt it using jwt library, and our JWT_SECRET. After all, we got userId in our hands, so we get this user from our MongoDB, and finally we store it in the context on the server.
(8) What for do we need the user in the server context? We need it to check every GraphQL query or every GraphQL mutation. So, if we have the user in the context we will return query or mutation unless we return null, empty array or a pink unicorn. It’s up to you. That’s how it works in my app:
The next task I got in the app was to integrate it with the Slack command. If you want to know how I did it, let me know.
Let’s summarize our process:
- Register App in Google console.
- Get the APIs source code.
- Initialize it at the start of the app. Then get the token from Google API once the user has clicked on the sign in button.
- Verify Google user by server and encrypt user id.
- Set encrypted id (token) in localStorage.
- Get the token from the localStorage and put it to the headers.
- Take the token from the headers on the server side, decrypt it and put the user to the Apollo context.
- Check the user in the context in every query and mutation.
Enjoy the article? Why not share it with others?
Written by Marcin Wydra
Top comments (0)