5

I am currently attempting to authenticate a Google account with a Google Home Action and retrieve the Authorization Code from the credentials. I do not want the access token, but the authorization code.

I have looked at this post and discovered that Google has updated their policy and no longer allow their own OAuth endpoints to be used for the account linking authorization code flow:

When implementing account linking using OAuth, you must own your OAuth endpoint

That being said,

  1. What is the correct way to authenticate an existing Google user with my Action and the necessary scopes? (I need calendar access)
  2. Is it possible to do this authentication without having to create my own OAuth2.0 endpoints?
  3. And from this flow, is it possible to extract the authorization code?
Tim Creasman
  • 137
  • 2
  • 8

1 Answers1

18

The current way to authenticate a user through their Google account is to use Google Sign-In for Assistant. Once they log in to your Action, you'll get an id token which you can decode to get their Google ID, which you can use to look up their account in your datastore to get their access/refresh tokens.

Since you need additional scopes, if the user logs in to the Assistant and does not already have the scopes attached to their account, you'll redirect them to a web-based login page where they can log in using Google Sign-In with the scopes you need. In this case, when they log in and authorize access through the web, you will get the auth code which you will need to exchange for the auth token and refresh token and store these.

You do not need to create your own OAuth endpoints for this, although you will need to do a bit of additional work to make sure they get redirected to your website to do the authorization if necessary.

You will only get the auth code once when they log in and authorize you. You will need to exchange this for the auth and refresh tokens and then store these tokens.

Update to explain things a little better.

Looking at the architecture, we see it has a few components. We’ll go into the details of each of these as we go through the process flow:

enter image description here

  • You have a data store of some sort, where you will store the Auth Token and Refresh Token for the user. I’m going to assume that you’re using Google’s User ID as the index for this data store.

    • By "Google User ID" in this case, I mean the unique numeric identifier that Google assigns to each account. This is often represented as a string, despite having just numeric digits, since it is usually much longer than most numeric types. In the ID Token, this is the "sub" claim.

    • In theory, you could use other identifiers that are available from the claims in the ID Token, such as their email address. Unfortunately, not all of these fields are guaranteed to be available - only the "sub" is guaranteed.

  • You have a web server that will have a few important URLs for our purposes:

    • The webhook for your Action fulfillment.
    • A login/auth page.
    • An endpoint where the javascript on the login page will send you the Auth Code.
  • The Google Assistant, which may be running on a Google Home or on a mobile device. We also assume that the user will be able to get to a browser to review what they are authorizing.

  • The Google services that you will be using, including Google’s OAuth service

Let’s start with the case where the user has previously logged in and authorized us to access the service on their behalf. We have their Auth Token and Refresh Token in our data store, indexed against their Google User ID. This is the simple case, but it helps us understand the more complicated case of how all that data gets in there.

The data flow looks something like this:

enter image description here

  1. The Assistant sends the Action webhook an Intent and possible parameters with it. If this is the first message, it is a welcome intent, but it doesn’t matter. It includes an Identity Token, which we will need to decode and verify. As part of the data we get when we decode it, it includes the User ID for the user.
  2. Using the User ID…
  3. ...we get the Auth Token and Refresh Token from the data store.
  4. With the Auth Token and Refresh Token, we can carry out some action against Google’s services, acting on behalf of the user.
  5. We’ll get some results back from the service…
  6. ...which we usually want to pass back to the user in some form.

Easy, right? But what if the user has never used the Assistant to talk to our Action before? And has never authorized us to access their Google services, so we don’t have their tokens? That flow looks more like this:

enter image description here

  1. The Assistant sends the Action webhook an Intent and possible parameters. This will be the first message, so our welcome intent is triggered. There is no Identity Token.
  2. The webhook sees there is no Identity Token, so it sends back a message requesting the “Sign In” helper function. Since your project is configured to use Google Sign-In, the Assistant will prompt the user if they can give you their profile information.
  3. If they say yes, you’ll get another response saying they have signed in and including the Identity Token, which we decode and verify and get their User ID. (If they say no, we’ll get a response saying it failed. How you handle this is another story. I’m going to assume they say yes.)
  4. Using the User ID…
  5. ...we try to get the Auth Token and Refresh Token from the data store. But they haven’t authorized us yet. We have authenticated them, but don’t have authorization...
  6. ...so we send back a message saying they need to visit our website to authorize us to access their Google services. We may require them to switch to a mobile device to do this part and even include a link to the login page.
  7. They will follow the link on a device that has a screen.
  8. We’ll send back the login page, which includes a link to Login with Google. We’ve configured this button to also ask for the additional scopes we need to access their services, as well as permission to access the services on their behalf when “offline”.
  9. They will go through the Google Login dance, the OAuth scopes screen, and will hopefully grant all the permissions we want. (Again, I ignore what happens if they don’t.) I omit what that dance looks like since it doesn’t involve us. Assuming that all goes well, Google gives them an Auth Code, which the javascript on the login page sends to us.
  10. We call Google’s OAuth servers to verify the Auth Code and use it to get the Auth Token and Refresh Token…
  11. … which we then store in the data store…
  12. … and then send back something so the Javascript page can tell the user that they can use our Action normally from now on.

Which they can now do, and it behaves as it did in the earlier, simple, scenario.

That looks complex, but it turns out that we can remove some steps in some cases. If the Google Cloud Project is the same project you use for both your Action as well as the web-based Google Sign-In, then once they authorize the project on the web, all calls to your fulfillment will include the Identity Token. This lets us remove steps 2-6 above, so things look more like this:

enter image description here

  1. The Assistant sends the Action webhook an Intent and possible parameters. This will be the first message, so our welcome intent is triggered. There is no Identity Token.
  2. The webhook sees there is no Identity Token, so we send back a message saying they need to visit our website to authorize us to access their Google services. We may require them to switch to a mobile device to do this part and even include a link to the login page. (This is the collapsed steps 2 and 6 from above.)
  3. They will follow the link on a device that has a screen.
  4. We’ll send back the login page, which includes a link to Login with Google. We’ve configured this button to also ask for the additional scopes we need to access their services, as well as permission to access the services on their behalf when “offline”.
  5. They will go through the Google Login dance, the OAuth scopes screen, and will hopefully grant all the permissions we want. (Again, I ignore what happens if they don’t.) I omit what that dance looks like since it doesn’t involve us. Assuming that all goes well, Google gives them an Auth Code, which the javascript on the login page sends to us.
  6. We call Google’s OAuth servers to verify the Auth Code and use it to get the Auth Token and Refresh Token…
  7. … which we then store in the data store…
  8. … and then send back something so the Javascript page can tell the user that they can use our Action normally from now on.

It is also worth noting that if they visit the website before even trying out the Assistant version (ie - because of a search result or whatever they start on step 8 from the second diagram or 4 from that third diagram) and log in, then we will get their Identity Token the first time they visit us through the Assistant, and this will work just like the simple scenario.

Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • (And I need to go back to all those older auth questions and mention this.) – Prisoner Jun 19 '18 at 15:59
  • Thanks for the quick reply! I have a backend setup that handles the storing of the auth and refresh tokens. It is the acquiring of additional scopes that I am most confused about. Would using a piece of middleware that handles the redirecting between the account linking and Google sign be the way to go? – Tim Creasman Jun 19 '18 at 16:49
  • It depends exactly what you mean by that. I guess you would need something (call it middleware or whatever) that gets the auth code from the web Google Sign In and then exchanges it for the auth/refresh tokens to store in your backend. I'm trying to think how to make my answer more clear to explain the flow and what needs to be done where. – Prisoner Jun 19 '18 at 17:16
  • I suppose it is important to clarify that I am not trying to link any kind of existing account to the Google Action. I want to have calendar permissions of the current user granted to my Action. Since Google disabled the ability to use their OAuth endpoints in account linking I assume they implemented another way for a user to supply permissions to an Action? The Google Sign-in linking type seemed promising but does not give the option to specify additional scopes. I hope this clarified any confusion. I am still wrapping my mind around OAuth in general. Thanks for the patience. – Tim Creasman Jun 19 '18 at 18:08
  • I get that, but once you're storing the auth/refresh token - you end up having a de-facto "account" that you have to store for the user. Correct? (And this is why you want the auth code as well, correct?) – Prisoner Jun 19 '18 at 19:30
  • Correct, my current backend flow uses an auth code (given via a post to the server) to grab the auth token and refresh token server-side. It then stores the auth token in session storage and the refresh token in more long term storage. I understand how I would get the auth code from a more typical webpage client flow. However, I don't understand the flow when the "client" is a user on Google Home. – Tim Creasman Jun 19 '18 at 20:18
  • I'll elaborate later, but for now, see the part of my answer where I say "you'll redirect them to a **web-based login page**" – Prisoner Jun 19 '18 at 20:28
  • Answer updated. Hopefully this clears everything up. – Prisoner Jun 20 '18 at 00:47
  • I've updated it again to provide a third flow, which should make things a little simpler in some cases, and discuss a nice side-effect of this. (My thanks for @YoichiroTanaka who talked some of this out with me and who had another great approach to the problem.) – Prisoner Jun 20 '18 at 14:05
  • Thank you so much! This clarifies everything. I have a lot of setup work ahead of me but at least I know what needs to be done. Once again thank you! – Tim Creasman Jun 20 '18 at 14:21
  • How can I manage linking between Gmail id and my organization id, for example, my auth endpoint stores the Auth token, refresh token as s value of `my organization id` which user will select when he came the first time, but after that if he came and select Gmail account that he selected first time, my database won't identify it, because from my auth point it had stored my organization id. – Jay Patel Oct 25 '18 at 07:17
  • @JayPatel - It depends on a lot of things, but this sounds like a new StackOverflow question. – Prisoner Oct 25 '18 at 10:53
  • Okay, one more confusion is you said **"indexed data against their Google User ID"**, What did you mean by Google User ID? email of the user or the `userId` parameter that we get in webhook request? – Jay Patel Oct 25 '18 at 12:40
  • @JayPatel In this case I mean the "Unique ID of the user's Google account", which is a long numeric value, usually represented as a string. This is available in the "sub" claim of their ID Token. It is the only guaranteed value that you can get about a user. While Email is often available, it is not guaranteed. – Prisoner Oct 25 '18 at 13:00
  • (I've updated the answer to clarify this. Along with a change to reflect that Google Sign-In is now generally available.) – Prisoner Oct 25 '18 at 13:04
  • @Prisoner Great post! Explains a lot. But I am still unclear on how to popup the OAuth login page from Google home. I am using 'OAuth2 and Google Sign in' option in Google Actions, Google home does ask for my profile permission, but then it hits the Token endpoint directly with a JWT token. It never calls the Authorization endpoint even if I say NO to the original profile sharing request. I want users to login with our own login system and not using google sign in. The documentation says that it should open the Auth endpoint, which is forever confusing. Any ideas and help would be great!Thanks – Gurtej Singh Dec 13 '18 at 23:24
  • @GurtejSingh - this sounds like a different question, and I encourage you to start a new question on StackOverflow describing your architecture, how users are supposed to log in, and what you're seeing exactly. – Prisoner Dec 13 '18 at 23:49