I hate dealing with user authentication, so I’m very happy to make user management and authentication somebody else’s problem. “Somebody else” like Google. Handling user information securely, supporting various kinds of multi-factor authentication, enabling account recovery while avoiding account hijacking… those are all much better handled by Google than by me!
Unfortunately for me, I found it pretty confusing to get Google to do that for me. Oh, there’s accurate and detailed information about how to do it on Google’s sites. A lot of information. About a lot of different ways to do it. Mostly using libraries that, for me at least, make it hard to debug things that I do wrong. So I decided to get down to basics and work through the fundamental steps, in detail, needed to have my server-side web apps use Google Sign-in. This blog post shows how I did it, and how you can do it if you want.
I’m comfortable managing sessions for my app, so my real problem was verifying a user’s identity before I create such a session. The steps for that are:
- The browser sends request to my app. My server checks whether there is a current active session, as indicated by a cookie it set and trusts. If there is, it’s okay, and my app returns the requested page. Otherwise, my app returns a web page stating that the user needs to authenticate to use the site further. The page has a link the user should click to do that. (Alternatively, my app could just send a redirect response to that link, but that’s pretty abrupt and might be confusing to the user.)
- The user clicks the link, and the browser sends a page request to Google. That link is to a Google page at accounts.google.com. The exact format of that link is described a bit later in this post. Google will return web pages and handle responses as needed to authenticate the user. If the authentication succeeds, Google will return a redirect response pointing to a page at my site.
- The user’s browser processes the redirect by sending a request to my app. My server uses information in that redirect URL to retrieve the user’s information from Google. This retrieval is from my web server to Google, not from the user’s browser. If everything goes right my server now knows who the user is, so the server creates a session and returns a response to the user that includes a header to set a cookie for that session. That response might be a web page, or a redirect back to the page the user originally requested. But now the user’s browser has a valid session cookie and the user can access my site.
Of those three steps, my server has to deal with step 1, where it gets a request that may or may not have an active session associated with it, and if not, has to respond with a page or redirect with the exact right URL. That URL sends the user’s browser to Google for step 2; that’s Google’s to handle, so my app is not involved. Finally, Google redirects the browser back to my app for step 3, and my server has to use information in that redirect URL to get the user’s identity.
So let’s look at how an app handles step 1 and step 3. But first, there’s a step 0. Google is not going to identify a user when it’s asked to by just anybody. An application that wants to use Google Sign-in needs to register itself with Google first.
Let’s go through the steps you need in order to have your web server use Google sign-in.
Step 0 – Register the app with Google
You will need to create a Google Cloud Platform project at console.cloud.google.com. You can use a Gmail or G Suite account for this, or you can create a plain Google Cloud account when asked. You will probably have to provide a credit card, but there’s a generous free trial, you won’t get billed when the trial is up without first being notified, and in any case, this registration doesn’t require using any services that currently cost money.
Once you have a project, use the menu on the left hand side of the console to select APIs & Services, then OAuth consent screen. Unless you intend to authenticate only users in a G Suite domain you control, your app will be considered External, so you will select that and click Create. Fill in the page with information about the app (you can name it anything you like) and save it. You’ll then need to go to the Credentials tab (you may be directed there automatically) and create an OAuth Client ID for your web application. As part of this you should register an Authorized redirect URL. That’s the URL in your app that will be called in Step 3.
When you finish this step you will have a CLIENT_ID and CLIENT_SECRET from the Credentials you created. You’ll also have selected and registered the REDIRECT_URI for Step 3. You’ll use those in the following steps.
Step 1 – Provide a sign-in URL
When your server receives a request that doesn’t include a valid session cookie, it will reply either with a page that has a link to a specific sign-in address, or with a redirect response to that same address. The task for this step is to create that address.
The address is below, broken over several lines for display, but is all on one line for the actual app:
https://accounts.google.com/signin/oauth? response_type=code& client_id=CLIENT_ID& scope=openid%20email& redirect_uri=REDIRECT_URI& state=STATE
The words in CAPITAL LETTERS all need to be replaced by the correct values. Step 0 provided those values for CLIENT_ID and REDIRECT_URI. The value for STATE can be almost any string you’d like – the URL the app is directed to in Step 3 will include it, so it’s a way to pass that information from this step to that step. I usually put the path of the page that was originally requested here.
response_type=code argument is asking for the eventual redirect request to include a code that can be used to retrieve information about the sign-in results.
scope=open_id%20email says that the information the app wants is the signed-in user’s email address.
Step 2 – Authenticate the user
This is Google’s problem, not yours. But Google is going to check that:
- The application asking for this is registered with Google (the CLIENT_ID)
- The location to send the user back to (the REDIRECT_URI) is registered for that application
Step 3 – Retrieve the user information
This step starts when the user’s browser makes a request to the REDIRECT_URI that the Google response specified. That request will include query parameters, and be of the form:
You app’s server will use those query parameter values (STATE and CODE) to get the user’s identity information. The value of STATE is just whatever value the server sent back in Step 1. That’s handy for keeping continuity from that step to this one, since so far as the server is concerned this request could have come from any user currently in the process of authenticating. There’s no other way for your server to know which page in your app the user was trying to access when told they needed to authenticate first.
The CODE value does not include any user information itself, but the server can use it to get that information. To do that, the server (not the user’s browser) will make an HTTP POST request to
https://oauth2.googleapis.com/token, and the information needed will be returned as the body of the response. The POST request’s body should have the content type application/x-www-form-urlencoded, and include the following values (as before, this is broken over multiple lines for clarity, but the request body should all be in a single line):
code=CODE& client_id=CLIENT_ID& client_secret=CLIENT_SECRET& redirect_uri=REDIRECT_URI& grant_type=authorization_code
CODE is the value extracted from the query parameter the user’s browser sent. CLIENT_ID, CLIENT_SECRET, and REDIRECT_URI are the values from Step 0. And
grant_type=authorization_code tells Google what kind of code your app’s server is providing.
The response to this POST request will be an application/json body containing information about the user. One field from this JSON object is called id_token, and its value is a JSON Web Token (JWT). That’s a digitally signed piece of data that includes fields about the user, including: the email address, the issuer (which will be
https://accounts.google.com in this case), the audience (that is, the recipient this token is intended for, which is your app), and validity periods. You could do all the cryptography and parsing yourself, but the Python library
google.oauth2.id_token can handle that, including verifying that the digital signature is valid and from Google.
Once your server has this information, it can save the user’s information in a session object and set a cookie referring to that session. The authentication job is done.
Information between your app’s server and the Google Sign-in service flows in two ways: indirectly, through the user’s browser, and directly from your app’s server to Google. The indirect information is provided in query parameters of URLs the browser requests, either in response to your app sending the browser to Google, or Google directing the browser back. Those query parameters include CLIENT_ID, REDIRECT_URI, STATE, and CODE.
Sensitive information cannot be passed that way (via the browser) because it could be intercepted and used outside the context it is intended for. That’s why Google just sends an opaque code back to your app rather than the user’s email address. Sensitive information must pass directly and securely between your app’s server and Google’s service. That’s via the POST request and response in Step 3. That connection returns the user’s identity, which Google is only willing to provide to your registered app upon user approval, and passes the CLIENT_SECRET, which your server uses to prove its identity to Google.
I’ve shared a sample application’s source code on GitHub, showing how all this can work in an extremely minimal web app. You can register a project for Google Cloud Platform and then run this code to try things out. I wrote the code for Google App Engine, but it should run pretty much anywhere. It could be on Cloud Run or Compute Engine, or a different cloud provider, or your own data center, or even your own desktop for testing. For the time being, you can try it out here.