Target/Summary: I have an Actions App developed in Google DialogFlow and I want the user be able to create Google Calendar Event using the App (from Google Assistant). In other words, authenticate the user to Allow my app to use his Calendar to create Events.
What is done:
- Since Google Actions doesn't allow use of Google Auth/Token endpoints, I opted to use http://www.auth0.com.
- Created an account (used my Google account) on auth0.com, created an Application and setup the following values using their management panel (Domain, CliendId and ClientSecret generated by auth0):
- Created OAuth Client Id on Google Cloud Console Credentials page:
- Configured Actions Account Linking like so:
- Went back to auth0.com > Connections > Social > Enabled Google:
- Checked the "Sign in required" in DialogFlow > Integrations > Google Assistant:
Wrote log on the first line of my DialogFlow Webhook method to log the following Response:
{ "originalRequest":{ "source":"google", "version":"2", "data":{ "isInSandbox":true, "surface":{ "capabilities":[ { "name":"actions.capability.AUDIO_OUTPUT" }, { "name":"actions.capability.WEB_BROWSER" }, { "name":"actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name":"actions.capability.SCREEN_OUTPUT" } ] }, "inputs":[ { "rawInputs":[ { "query":"test", "inputType":"KEYBOARD" } ], "arguments":[ { "rawText":"test", "textValue":"test", "name":"text" } ], "intent":"actions.intent.TEXT" } ], "user":{ "lastSeen":"2018-05-03T11:40:57Z", "accessToken":"4CfRs-Lt5lWVQuyOYODvf1xxxxxxx", "locale":"en-US", "userId":"15229245xxxxx" }, "conversation":{ "conversationId":"15253476xxxxx", "type":"ACTIVE", "conversationToken":"[\"authentication\",\"wh_patient-details\"]" }, "availableSurfaces":[ { "capabilities":[ { "name":"actions.capability.AUDIO_OUTPUT" }, { "name":"actions.capability.SCREEN_OUTPUT" } ] } ] } }, "id":"1d6ed865-0803-49ca-bbac-xxxx", "timestamp":"2018-05-03T11:42:22.835Z", "lang":"en-us", "result":{ "source":"agent", "resolvedQuery":"test", "speech":"", "action":"v00.xxxxx", "actionIncomplete":false, "parameters":{ "CallEnum":"Test" }, "contexts":[ { "name":"authentication", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":1 }, { "name":"actions_capability_screen_output", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"actions_capability_audio_output", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"wh_patient-details", "parameters":{ "patientId":0, "CallEnum":"Test", "fallbackLifespan":0, "providerId":0, "practiceId":0, "CallEnum.original":"", "fullDob":"01 January, 0001" }, "lifespan":199 }, { "name":"google_assistant_input_type_keyboard", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"actions_capability_web_browser", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"actions_capability_media_response_audio", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 } ], "metadata":{ "intentName":"v00xxxx", "isResponseToSlotfilling":false, "intentId":"c7bd9113-d5b4-4312-8851-xxxxxxx", "webhookUsed":"true", "webhookForSlotFillingUsed":"false", "nluResponseTime":556 }, "fulfillment":{ "speech":"Test", "messages":[ { "type":0, "speech":"Test" } ] }, "score":0.8399999737739563 }, "status":{ "code":200, "errorType":"success" }, "sessionId":"152534xxxxxxx", "isStackdriverLoggingEnabled":false
}
where the relevant section is:
"user":{
"lastSeen":"2018-05-03T11:40:57Z",
"accessToken":"4CfRs-Lt5lWVQuyOYODvf1xxxxxxx",
"locale":"en-US",
"userId":"15229245xxxxx"
}
- From this post: See answer by @Prisoner
The auth token (which you have issued, because you're the OAuth server) will be sent in the JSON object at originalRequest.data.user.accessToken.
So I used the authorization token from above in the code below:
string clientId = "361385932727-ksg6jgjxxxxxSNIP";
string clientSecret = "rc2K1UUyntxxxxxxSNIP";
string accessToken = jsonObject.SelectToken("originalRequest.data.user.accessToken");
string userId = jsonObject.SelectToken("originalRequest.data.user.userId");
IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = clientId,
ClientSecret = clientSecret
},
Scopes = new[] { CalendarService.Scope.Calendar }
});
TokenResponse token = flow.ExchangeCodeForTokenAsync(userId, accessToken,
"https://oauth-redirect.googleusercontent.com/r/xxxxxxxxx", CancellationToken.None).Result;
UserCredential credential = new UserCredential(flow, userId, new TokenResponse { AccessToken = token.AccessToken });
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Test Auth0",
});
var list = service.CalendarList.List().Execute().Items;
And the exception:
Error:"invalid_grant", Description:"Malformed auth code.", Uri:""
Stacktrace: at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<ExchangeCodeForTokenAsync>d__30.MoveNext()
And when I change the ClientId/ClientSecret in the code above to the one from auth0.com, the exception is:
Error:"invalid_client", Description:"The OAuth client was not found.", Uri:""
Stacktrace: at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<ExchangeCodeForTokenAsync>d__30.MoveNext()
What am I missing here? Can somebody help.
accessToken
in the source fragment you've given. How are you getting the userId and how are you associating it with the tokens? – Prisoner