Authenticate Google Calendar on API.AI with Google Actions - google-calendar-api

I am writing an API.AI app with Google Actions (will run on google assistant and google home). The app should be able to read\add events to the user's google calendar account.
The backend is Google Cloud Functions, and I enabled the Calendar API in the developers console.
The flow the user should experience is something like this:
User: "Hey Google, when was my last meeting with Anna?"
Agent: "Your last meeting with Anna was two days ago"
User: "Set a meeting with Anna for the tomorrow"
Agent: "Ok. Meeting is set"
How should I build the authentication process to grant the app access to the calendar?

Update, 8 Nov 2017
As of about 4 October 2017, Google updated their policy to explicitly forbid this. Around the same time, they also took technical measures to prevent this.
The portion about enabling the Calendar API is still correct, although insufficient to do what the original question asked.
Original Answer
Broadly speaking, the auth tasks you need to do are in four parts:
Configure your project (in the cloud console) so that the Calendar API is enabled and that the OAuth2 client is correctly configured.
Configure the Action for account linking in the action console.
Configure the Actions on Google Integration for your API.AI Agent to indicate that sign-in is required.
When API.AI calls your webhook to fulfill an Intent, it will include an auth token as part of the JSON. You can use this token to make calls to the Google APIs you need.
Configure Cloud Project
You need to configure your cloud project so that it has access to the Google APIs you need and setup the OAuth2 Client ID, Secret, and Redirect URI.
Go to https://console.cloud.google.com/apis/dashboard and make sure you have the project you're working with selected. Then make sure you have the APIs you need enabled. (In this case, the Calendar API)
Select the "Credentials" menu on the left. You should see something like this:
Select "Create credentials" and then "OAuth client ID"
Select that this is for a "Web application" (it is... kinda...)
Enter a name. In the screen shot below, I used "Action client" so I remember that this is actually for Actions on Google.
In the "Authorized Redirect URIs" section, you need to include a URI of the form https://oauth-redirect.googleusercontent.com/r/your-project-id replacing the "your-project-id" part with... your project ID in the Cloud Console. At this point, the screen should look something like this:
Click the "Create" button and you'll get a screen with your Client ID and Secret. You can get a copy of these now, but you can also get them later.
Click on "Ok" and you'll be taken back to the "Credentials" screen with the new Client ID added. You can click the pencil icon if you ever need to get the ID and Secret again (or reset the secret if it has been compromised).
Configure the Action Console
Once we have OAuth setup for the project, we need to tell Actions that this is what we'll be using to authenticate and authorize the user.
Go to https://console.actions.google.com/ and select the project you'll be working with.
In the Overview, make your way through any configuration necessary until you can get to Step 4, "Account Linking". This may require you to set names and icons - you can go back later if needed to correct these.
Select the Grant Type of "Authorization Code" and click Next.
In the Client Information section, enter the Client ID and Client Secret from when you created the credentials in the Cloud Console. (If you forget, go to the Cloud Console API Credentials section and click on the pencil.)
For the Authorization URL, enter https://accounts.google.com/o/oauth2/v2/auth
For the Token URL, enter https://www.googleapis.com/oauth2/v4/token
Click Next
You now configure your client for the scopes that you're requesting. Unlike most other places you enter scopes - you need to have one per line. (In this case, you'd also add the correct scope you need to access the user's calendar.) Then click Next.
You need to enter testing instructions. Before you submit your Action, these instructions should contain a test account and password that the review team can use to evaluate it. But you can just put something there while you're testing and then hit the Save button.
Configure API.AI
Over in API.AI, you need to indicate that the user needs to sign-in to use the Action.
Go to https://console.api.ai/ and select the project you're working with.
Select "Integrations" and then "Actions on Google". Turn it on if you haven't already.
Click the "Sign in required for welcome intent" checkbox.
Handle things in your webhook
After all that setup, handling things in your webhook is fairly straightforward! You can get an OAuth Access Token in one of two ways:
If you're using the JavaScript library, calling app.getUser().authToken
If you're looking at the JSON body, it is in originalRequest.data.user.accessToken
You'll use this Access Token to make calls against Google's API endpoints using methods defined elsewhere.
You don't need a Refresh Token - the Assistant should hand you a valid Access Token unless the user has revoked access.

If you're using Google Sign-in as your login provider, you can request access to the calendar scope as part of your OAuth flow.
The public policy states:
"Don't request any OAuth scope from Google unless the user is signing in to your service using Google Sign-In."

Related

'Token has been expired or revoked' - Google OAuth2 Refresh token gets expired in a few days

I am using Google Analytics API to fetch analytics data. I tried to authenticate it using following steps:
Created OAuth client ID in https://console.developers.google.com/ credentials section.
In consent screen I had set publishing status as testing
In OAuth 2.0 Playground I got the refresh token using above generated client id and client secret
Then I am using it to generate access token through it.
But after a few days, the refresh token expires although it is mentioned that the refresh token's validity is life long.
If your app is in testing mode then user tokens will expire in 7 days. Please find this explanations here: https://support.google.com/cloud/answer/10311615#zippy=%2Ctesting
I needed to send mails from a gmail account that I have access to, using nodemailer. It works for a couple of days before my refresh token is mysteriously revoked, even though the account belongs to me. A google search brought me here and I had been watching for a while hoping someone would help with a solution.
As you mentioned, this seems to happen with only test/unverified apps and I'm guessing google revokes tokens for such applications in your account after a few days. After much trials and errors, here is what I did.
NOTE: This is solution is only applicable to accounts you own, otherwise you must verify your app to access other people's accounts
Generate a new refresh token (existing one is most likely revoked) as described in this SO post
Go to the security tab of your google account dashboard
Under the Recent security activity section, you should see a security alert for your app.
Click on the context menu next to the notification and click DISMISS
At this point you'll be presented with a dialog of options where you indicate the level of trust you have for the app. I just went ahead and said I trusted the developer/app, obviously. And that's it! The refresh token should persist after this.
I could not find anything related anywhere else.
The other answer pointed me in the right direction but for me the option was located somewhere else: security > security checkup/security issues found > context menu next to your app > dismiss
This issue seems to be for unverified apps, Simply delete the token file from your project and rerun the project, it will create a new token.
My problem was when I've added access_token instead of refresh_token.
What I did:
Go to https://console.cloud.google.com/apis/credentials/consent and change from the testing status to published.
Delete the current token file.
Authorize the API again by signing into your gmail account. You will be sent to a warning screen. From there, you can choose to proceed.
When done you'll get a new token file
The solution is to delete your token.json file to force Google to find a new token.
I was able to get it to work WITHOUT a verified app. Perhaps the refresh() method will work once my app is verified. Not sure on that one.

Wordpress setup, and finding the Authentication Code

I am going over to a Wordpress and my WP plugin is asking me about "Authentication Code" And I have no idea where to find it.
Without the plugin name it is hard for me to determine what specific authentication code is needed, so I'll assume that you mean the OAuth Client ID. Please, forgive me if I am mistaken. You can generate a new OAuth Client ID following this steps:
Open Google Cloud Console and select your project.
Go to Navigation menu ⮞ APIs & Services ⮞ Credentials.
Select +Create credentials ⮞ OAuth client ID at the top.
You'll be prompted to select an application type. In case of a Wordpress plugin you need to choose Web application and give it a descriptive name.
Click on +ADD URI and include your webpage URL. This action will whitelist your webpage to authorize this OAuth client.
Tick Create to finish the process and annotate your client ID and secret.
With this procedure you would have created an OAuth client ID and its secret. Now we can take this client/secret pair and use it to request an authorization code over some Calendar API scopes.
After getting an authorization code, you can ask Google to exchange it for a pair of refresh and access tokens that you can use on every call to the Calendar API.
Following these steps you would have created every authentication code possible, you just need to determine which one is the required for that specific plugin. Please, don't hesitate to ask me any additional doubts.

How can I access the Google Calendar API through Google Actions and Dialog Flow

Referencing this post
, I tried to create an access token for my Google Actions. I enabled Google sign in, and end up with a user-id. The top post says "You can combine this with a web- or app-based Google Sign-In to get their permission to access OAuth scopes if you need to access Google's APIs" but I honestly have no idea how to do that. What I'm trying to do is get an access token from Google Actions/ Dialog flow to send to my server-end code in order to make a successful API POST request to Google Calendar API.
The post you reference that points to another StackOverflow answer that discusses the approach. It basically says that you need to combine two things to do what you want:
You use Google Sign In on a web page to let the user authorize you to access the calendar on their behalf.
You probably should use the hybrid flow so the access/refresh tokens are only handled on the server side - not in your web client.
Your server will store the access/refresh tokens in some way so you can use them as part of your Action later.
You will use Google Sign In for Assistant to authenticate the user when they connect through the Assistant and determine if they have already authorized you to access their calendar. If they have, you'll be able to get their access/refresh tokens out of your store and access their calendar.
You can't do it completely through AoG/Dialogflow because there is no way for the user to grant OAuth permissions to you by voice alone - this is why you need to direct the user to a web page for them to grant you permission to access their calendar.

Google OAuth Always Showing Consent Screen

I'm building an installed application that will have features requiring the Google Drive REST API using Qt and C++. I understand Qt is now releasing new libraries to support OAuth flows but let's assume I'm a student and learning to use OAuth at this layer is a requirement for this project.
In my application, I have a working OAuth flow for installed applications that ends with an Access Token and Refresh Token being stored using QSettings (I'm open to input on whether this is a disastrously bad idea too). The application requires no authentication/login for its own sake/data, but it does need authentication to Google for calling API's using an Access Token. This application has no associated web backend being hosted; its simple and should be deployable completely locally (I've written and included a simple TCP server that will receive the authorization redirect_uri and will run and close when called from within the application).
As such, I'm curious about the best way to make sure that, when a user opens my application and wants to use the Google Drive features, they are appropriately authenticated on Google's end. Say, if I maintain an access token in the registry, and this access token is granted per-user/per-application basis (right?), then how can I make sure only the user the token belongs to is able to make calls to the API with it?
Here's my understanding and approach; feel free to correct me or educate me if I've got the wrong interpretation.
If an Access Token is found, perform the following:
Open a browser page to a Google login domain and have the user authenticate there (this could prohibit a user from being able to use a cached login session that would have access to a token they otherwise shouldn't have access to)
If user has correctly authenticated with a Google account, return control to the application and make a test call to an API using the stored token.
If the call fails (responds with an invalid_credentials) I should be able to be sure its because the access token has expired and the application will go through the flow to renew an Access Token from a Refresh Token.
If no Access Token is initially found:
Start a normal OAuth installed application flow
Get the tokens and store them so that when the user opens the application next time the former procedure is used
My issue then is the first two steps if the Access Token is found. Nominally this could be done by the typical OAuth flow but it appears that when using a localhost as the redirect uri, Google will always prompt for consent, regardless of settings for prompt and access_type authorization query parameters.
What can be done to accomplish these first two steps in a way that my application can control (i.e. not a solution that relies on a backend server being hosted somewhere)?
If this question is too open-ended for SO requirements I can make some more restrictions/assumptions to limit the problem domain but I'd rather not do that yet in case I unknowingly rope off a good viable solution.
Thanks for reading! Sorry if its a verbose; I wanted to ensure my problem domain was fully fleshed out!
If you are using an installed application, I wouldn't recommend using or storing refresh tokens. Storing refresh tokens on the client side means that if an intruder gains access to the client's application, they have infinite access to the user's application without ever having to enter the user's credentials. If you do insist on having a refresh token, ensure you follow the Google's installed app flow, with the code_verifier parameter included in your requests.
If the access token is found, you should try to verify it, and if verified then use it at the google api, otherwise force the user to login again (or refresh it if you chose to still use refresh tokens).
If no access token is found, your flow sounds fine.
A few notes on loggin in with Google:
Google will only return a refresh token if you specify access_type=offline in your auth request.
Google will only return a refresh token on the user's first authorization request, unless you always specify prompt=consent in your query params.
In my experience, when leaving out the prompt query param, the user is not prompted for their consent again. If they are logged in to google, you will get a new access token, but no refresh token, unless you have prompt=consent.
I think the idea is you use prompt=consent if you have no record of the user ever using your application. Otherwise if they have used it before, you may prefer to use prompt=select_account to allow the user to select which account he wants to use in case he has more then one, or you can just use prompt=none.
This is just my understanding of it all.
My approach I ended up using was just to deploy with an SQLite db that will be stored in the AppData roaming directory. The db schema includes a field for the user's Name (from the OpenID IDToken field if it exists), the user's picture URL (again from IDToken if it exists), the refresh and access token strings (will be stored as encrypted strings when I get around to it), the user's UID/sub string, and a field for a user name and password.
These latter two fields are authentication fields for within my own application, which, again, I wanted to avoid but it seems impossible to do so. So the user will be prompted to enter a username and password into a form, and these credentials will be checked against the existing SQLite db file mentioned previously.
If they exist and are correct, the user gets logged in and will have access to their respective access and refresh token.
If the user has forgotten their password, they'll be asked for reconsent (going through the installed app flow again), and whatever password they provided during initial login will be used as the reset password. It is considered, for my purposes, that logging into Google for the installed app flow is proof enough that the user account belongs to them and they should have authorization to reset the password.
If the user is a new user and doesn't have a record in the local SQLite db file, then they can also click a button to "Create New Account" - which effectively goes through the authorization flow as well but this time a whole new record is posted to the SQLite db with the appropriate fields filled.
There's still more optimization that could be done but at least I am getting closer to the level of security and control of access to Google user accounts that I want.
I'm not marking this as an answer because I feel like this solution is still not desired and that there should be an easier way. So if someone has evidence or experience of providing an equivalent level of authentication control without needing to maintain a local user account database then I would be more than happy to mark such a method as the solution!
Thanks again!

Google Calendar API . Can I write to a users calendar when the user isn't logged in?

This may seem like a totally idiotic question but I am looking at writing a web app/service that will utilize google's calendar API's and I needed to see if I have the ability to write to a users calendar when the user isn't logged into there google account. I couldn't find any clear documentation that illustrated whether or not I could do this or not. I pictured the authorization process something the user agrees to when they sign up for my site and then my app can go add things to there google calendar. Does anyone know if this is possible? If so, can someone hook me up with a link that illustrates how that works?
Thanks in advance
You're thinking about "logged in" wrong.
Because web connections don't persist between requests, any web-site or web-service is checking headers such as the Authorization header and/or Cookie header (depending on what authorisation method is used), with each and every request and matching the request to a user.
Hence logging in* means that the web client (browser or whatever) is set to use the details that relate to a particular user in making the request.
When you use the API, the authorisation information you send, is performing the same job. As such, when the user is using your API they are logged in through your API, whether they're logged in through a web page or other application or not.
E.g.:
My web browser is currently logged into google to an account I use for work stuff.
My mail client is logged into google calendar to an account I use for private stuff.
When I look at google calendar, I see that I don't have any calendars (I don't use it on my work account).
When I look at Thunderbird, I see my calendars.
If I peek at the traffic being sent by the browser and mail client, on both I see headers like:
Authorization: GoogleLogin auth=...stuff I'm not going to share or it would help you impersonate me
So, I'm "logged in" on Thunderbird because the code using the API logs me in itself.
Indeed, the same application can access multiple accounts and be "logged in" to all of them, as long as it has the authentication details to do so.
*Strictly, some would argue that "logged in" isn't the correct term at all, and refers to different sorts of access mechanism where there is state on the connection. Whatever about that opinion, we use the metaphor of being or not being logged in, because users understand it.
The answer to your question is yes. Use OAuth 2 and the Authorization Code flow (web server flow), with offline enabled. Store the refresh tokens (which last indefinitely until the user has revoked), and you'll be able to upload events to Google Calendar even when the user isn't currently logged in.
More info: https://developers.google.com/accounts/docs/OAuth2WebServer#offline
Credits to Ryan Boyd for this note.

Resources