According to the docuemtntation for Moments.insert with the Google+ API autentication with the following scope is required
https://www.googleapis.com/auth/plus.login
I am authenticating with all of the possible PlusService scopes but i am still getting the following error
Google.Apis.Requests.RequestError Unauthorized [401] Errors [
Message[Unauthorized] Location[ - ] Reason[unauthorized]
Domain[global]
//Scopes for use with Google+ API
// activating Google+ API in console
// Documentation: https://developers.google.com/+/api/oauth
string[] scopes = new string[] {
PlusService.Scope.PlusLogin,
PlusService.Scope.UserinfoEmail,
PlusService.Scope.UserinfoProfile
};
string _client_id = "2046123799103-d0vpdthl4ms0soutcrpe036ckqn7rfpn.apps.googleusercontent.com";
string _client_secret = "NDmluNfTgUk6wgmy7cFo64RV";
PlusService service = null;
UserCredential credential = null;
try {
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets {
ClientId = _client_id, ClientSecret = _client_secret
},
scopes,
Environment.UserName,
CancellationToken.None,
new FileDataStore("Daimto.GooglePlus.Auth.Store")).Result;
} catch (Exception ex) {
//If the user hits cancel you wont get access.
if (ex.InnerException.Message.IndexOf("access_denied") != -1) {
Console.WriteLine("User declined access");
Console.ReadLine();
return;
} else {
Console.WriteLine("Unknown Authentication Error:" + ex.Message);
Console.ReadLine();
return;
}
}
// Now we create a Google service. All of our requests will be run though this.
service = new PlusService(new BaseClientService.Initializer() {
HttpClientInitializer = credential,
ApplicationName = "Google Plus Sample",
});
Moment body = new Moment();
body.Type = "http://schema.org/AddAction";
ItemScope itemScope = new ItemScope();
itemScope.Id = "target-id-1";
itemScope.Type = "http://schema.org/AddAction";
itemScope.Name = "The Google+ Platform";
itemScope.Description = "A page that describes just how awesome Google+ is!";
itemScope.Image = "https://developers.google.com/+/plugins/snippet/examples/thing.png";
body.Object = itemScope;
try {
var l = service.Moments.Insert(body, "me", MomentsResource.InsertRequest.CollectionEnum.Vault);
l.Execute();
} catch (Exception ex) {
int i = 1;
}
I have tested authentication and it is working i am able to list activities and other things. Its only inserting moments that is giving me this error. I have also tried doing this in PHP and am getting the same error. What am I missing?
Update: I found something in the documentation for moments.insert
When authenticating for moments.insert, you must include the
data-requestvisibleactions parameter to specify which types of App
Activities your application will write.
I have not figured out yet how to set this data-requestvisibleactions.
As you've noticed, you need to add the request_visible_actions parameter to the request. Most of the other OAuth libraries from Google have added a shortcut to do this, but it looks like the .NET library hasn't. For example, the PHP library has setRequestVisibleActions().
Internally, the convenience method GoogleWebAuthorizationBroker.AuthorizeAsync() calls AuthorizationCodeFlow.CreateAuthorizationCodeRequest() to generate the URL used in the call. You can subclass AuthorizationCodeFlow and AuthorizationCodeRequestUrl (which it returns) to add the parameter in question and then go through the flow more directly.
You can see an example of how to do this at https://github.com/gguuss/google-dotnet-demo/blob/master/AuthWithAppActivities/Program.cs
You can also use a Google+ Sign-In button to do the initial auth flow and pass the one-time code to the server, which can then proceed to turn this into a valid token. Since the Sign-In button has the data-requestvisibleactions attribute, this will take care of that part of the auth for you.
Related
I'm trying to create a daemon using Microsoft Graph API v1.0.
I've registered my app with application permission Calendars.ReadWrite and User.Read.All with granted admin consent.
I get the access token correctly and I call GetUserId that returns the user id for setting requestURI.
After that I want to retrieve Outlook Calendar:
var id = await GetUserId(result.AccessToken);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
String requestURI = $"https://graph.microsoft.com/v1.0/users/{id}/calendars";
var response = await httpClient.GetAsync(requestURI);
var responseString = await response.Content.ReadAsStringAsync();
but I get this error:
{
"error": {
"code": "ResourceNotFound",
"message": "Resource could not be discovered.",
"innerError": {
"request-id": "5ecd547b-9281-4824-94e5-095691e759aa",
"date": "2020-01-14T16:44:16"
}
}
}
When I set requestURI to users/{id} or organization the request works fine, but adding /calendars, /events, or /mailFolder results in the above error.
I think my problem is that I used a Personal Account. Do I need to use a Work or School Account? Is it possible to use a Personal Account? Is there another reason for my error?
Update: Code for retrieving a token:
app = ConfidentialClientApplicationBuilder
.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithAuthority($"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/token&grant_type=client_credentials&resource=https://graph.microsoft.com")
.Build();
string[] scopesClient =
new string[] { $"https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopesClient).ExecuteAsync();
}
catch (MsalServiceException ex) when(ex.Message.Contains("AADSTS70011"))
{
}
You're Authority isn't quite right:
resource=https://graph.microsoft.com is a legacy setting and not used for the v2 Endpoint (aka authentication with Scopes rather than Resources).
The ConfidentialClientApplicationBuilderhandes setting the OAuth Grant automatically so specifying grant_type=client_credentials is not needed.
The Authority should only contain the authentication authority (https://login.microsoftonline.com/) and the tenant id. The easiest way to handle this is using the AzureCloudInstance.AzurePublic enumeration
Your token code should look something like this:
app = ConfidentialClientApplicationBuilder
.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.Build();
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app
.AcquireTokenForClient(scopes)
.ExecuteAsync();
}
catch (MsalServiceException ex)
{
}
Note: You will not be able to use this method with an #outlook.com account. Personal Accounts do not support client_credentials.
In order to call /{user-id}/events,/calendar or /mailFolder to work the user must have mailbox on Exchange Online if you are using client credentials for Daemon application.
We are generally getting this {"error":{"code":"ResourceNotFound","message":"Resource could not be discovered."}} error for organizational users when license is not assigned to the users or mailbox is not configured for them.
In Microsoft personal Account user does not have a mailbox (which make sense with null value as below), so the call wouldn’t work.
It seems assigning license to a guest account (Microsoft personal account in this case) is not possible and hence the user account never gets access to the calendar service (part of o365 exchange online). due to which it cannot retrieve the calendar information of personal Outlook account.
The documentation states that you have to add the CalenderRead and CalenderReadWrite both. If not, I would use delegated permission if you are using personal account. I would also go to ms.jwt, where they check your token and tell you what is the permission you have and what do you, need to call calendar endpoints
I "just" want to integrate google calendar api to my little web project. The user should be able to add calendar entries to his calendar - via a c# core (2.2) mvc project. The problem is I can't find any complete example how to do this and tried a lot without any solution.
The main problem - how can I get the permission? And how can I set the redirect url?
Why google does not provide a complete example für c# core?
I build a simple console projekt (based on an example) - that works if I set the permission manually. But I must ask my user to give the permission.
Btw - I created and saved the ClientId, ClientSecret and so on at/from https://console.developers.google.com/.
Thanks Ralf
public IActionResult GoogleCalendar(string id)
{
string refreshToken = string.Empty;
string credentialError;
var credential = GetUserCredential(out credentialError);
if (credential != null && string.IsNullOrWhiteSpace(credentialError))
{
//Save RefreshToken into Database
refreshToken = credential.Token.RefreshToken;
}
string addEventError;
string calendarEventId = string.Empty;
calendarEventId = AddCalenderEvents(refreshToken, "mytestuser#googlemail.com", "Test-Event " + DateTime.Now, DateTime.Now.AddMinutes(5), DateTime.Now.AddHours(2), out addEventError);
return View();
}
public static UserCredential GetUserCredential(out string error)
{
UserCredential credential = null;
error = string.Empty;
try
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = ClientId,
ClientSecret = ClientSecret
},
Scopes,
"mytestuser#googlemail.com",
CancellationToken.None,
new FileDataStore("Google Oauth2 Client App")).Result;
}
catch (Exception ex)
{
credential = null;
error = "Failed to UserCredential Initialization: " + ex.ToString();
}
return credential;
}
I get from google
That’s an error.
Error: invalid_client
The OAuth client was not found.
On a (changing) local port an url I have never set.
access_type=offline
response_type=code
client_id=xxxxxx-yyyyyyyyyyyyy.apps.googleusercontent.com
redirect_uri=http://127.0.0.1:56253/authorize/
scope=https://www.googleapis.com/auth/calendar
The provided samples from google are quite confusing, the provided console demo application only works for local environments because it tries to launch the browser and use it to authenticatie.
To authenticatie users on a web app, check the following google guide: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
i am try to create a new project using resourcemanager.projects.create But got an error like :(
Google.Apis.Requests.RequestError
Request had insufficient authentication scopes. [403]
Errors [
Message[Request had insufficient authentication scopes.] Location[ - ] Reason[forbidden] Domain[global]
]
Can anyone please tell me What i am doing wrong.
Here is my code :
private async void GoogleClick(object sender, RoutedEventArgs e)
{
try
{
var cr = new PromptCodeReceiver();
var result = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets { ClientId = GoogleClientId,ClientSecret = GoogleSecretKey },
new[] { "email", "profile" },
"user",
CancellationToken.None);
if (result.Token.IsExpired(SystemClock.Default))
{
await result.RefreshTokenAsync(CancellationToken.None);
}
CloudResourceManagerService cloudResourceManagerService = new CloudResourceManagerService(new BaseClientService.Initializer
{
HttpClientInitializer = GetCredential(result.Token.AccessToken, FirebaseAuthType.Google),
ApplicationName = "Kapiling",
//ApiKey = "apikey"
});
// TODO: Assign values to desired properties of `requestBody`:
Data.Project requestBody = new Data.Project();
requestBody.Name = "TESTING";
requestBody.ProjectNumber = 415104041262;
requestBody.ProjectId = "tokyo-rain-123";
requestBody.CreateTime = "2014-10-02T15:01:23.045123456Z";
ProjectsResource.CreateRequest request = cloudResourceManagerService.Projects.Create(requestBody);
}
I am try to access using public static GoogleCredential FromAccessToken(string accessToken, IAccessMethod accessMethod = null); method
public static GoogleCredential GetCredential(string accessToken, FirebaseAuthType authType)
{
GoogleCredential credential = GoogleCredential.FromAccessToken(accessToken, null);
return credential;
}
Thanks for everyone who help me i Solved this issues.
Thanks again. :)
This trouble is caused by incorrect scopes of authorization, that's correct. The solution for this particular case is to authorize only once with correct scopes(preferred) or to authorize at every such situation when you need to write/update/append.
However, the author only commented that the problem is solved but not described the solution. In many cases which guided by official API documentation, you can see this code
const string credPath = "token.json";
credential = GoogleWebAuthorizationBroker
.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true))
.Result;
In those examples, you're specifying the last parameter to save some auth data to the folder defined by the credPath variable. Note that if you want to change your scopes after you run the app once, this auth data won't be overwritten so you have to:
delete this existing data every time before running the app
change credPath
or simply remove this parameter, but in this case, you'll have to confirm your auth every time you start the app.
You first have to set your scope to Calendar (as this is the one with most permissions, you also have: CalendarEvents, CalendarEventsReadonly, CalendarReadonly,CalendarSettingsReadonly choose which one you need depending on your needed permission level) like this :
static string[] Scopes = {CalendarService.Scope.Calendar};
But this is not done, as you need to delete your credentials path that was already created or change its name , in the documentation his name was "token.json" as if you leave this one the user had already agreed on specific access to his data but now if you delete this file or changed he'll be prompted to a new window on his browser to accept the new access to his data (in the case of Scope.Calendar scope, he'll be prompted to accept that we have the right to get, insert, update and delete his events on his calendar among others)
From looking at the GCP documentation, it seems like you (also) need the https://www.googleapis.com/auth/cloud-platform scope. See https://cloud.google.com/resource-manager/docs/authorizing
Change your code for GetCredential(). Note: I am not sure what you are trying to do with FirebaseAuthType so I have left authType as an unused parameter.
public static GoogleCredential GetCredential(string accessToken, FirebaseAuthType authType)
{
GoogleCredential credential = GoogleCredential.FromAccessToken(accessToken, null);
// The following line is no longer needed
// credential = credential.CreateScoped("https://www.googleapis.com/auth/cloud-platform");
return credential;
}
[EDIT]: Additional code to change due to the comment createScopedRequired = false
You received this message because you already authenticated with scopes. To change the scopes requires another authorization. In this case request the correct scopes. Note, you do not need the other scopes (email and profile). Put them back in later if you do.
string[] scopes = new string[] { "https://www.googleapis.com/auth/cloud-platform" };
var result = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets {
ClientId = GoogleClientId,
ClientSecret = GoogleSecretKey },
scopes,
"user",
CancellationToken.None).Result;
Using .net, I'm trying to make a request to the Google ScriptService, however I keep getting this error "Request is missing required authentication credential", although I am including the credential. In fact I use the same credential not too long before to successfully make a request to the YoutubeService.
Below is my code, it actually used to work, so I'm not sure what has changed:
Scopes = new string[] { ScriptService.Scope.Forms, ScriptService.Scope.Spreadsheets,
ScriptService.Scope.Drive, YouTubeService.Scope.YoutubeUpload, YouTubeService.Scope.Youtube };
UserCredential credential;
using (var stream = new FileStream(#"Resources\client_secret.json", FileMode.Open, FileAccess.Read))
{
var credPath = Path.Combine(parentDir, ".credentials/" + folderName);
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
}
// Create Google Apps Script Execution API service.
var service = new ScriptService(new BaseClientService.Initializer()
{
HttpClientInitializer = this.credential,
ApplicationName = Properties.Resources.ApplicationName,
});
// Create an execution request object.
ExecutionRequest request = new ExecutionRequest();
request.Function = "createForm";
request.Parameters = new List<object>();
request.Parameters.Add(this.id);
request.Parameters.Add(name);
request.Parameters.Add(email);
request.Parameters.Add(this.link);
ScriptsResource.RunRequest runReq = service.Scripts.Run(request, Globals.Script_ID);
try
{
// Make the API request.
Operation op = runReq.Execute();
catch (Google.GoogleApiException e)
{
Debug.WriteLine("Error calling API:\n{0}", e.ToString());
}
I have enabled the API and generated OAuth 2.0 credentials for my platform in the developer console. The client_secret.json is the OAuth 2.0 credential that I downloaded from my console.
Any thoughts on what could be going wrong? I recall having a similar issue after updating my google packages, however in this instance I did not do so. I also tried updating the packages and still got the same issue.
The issue was that I did not have the correct authorization scopes. Originally I had:
Scopes = new string[] { ScriptService.Scope.Forms, ScriptService.Scope.Spreadsheets,
ScriptService.Scope.Drive, YouTubeService.Scope.YoutubeUpload, YouTubeService.Scope.Youtube };
However, upon checking the "Scopes" tab in my apps script Project Properties, I was missing the "userinfo.email" scope. And so, I updated my code in the following way:
Scopes = new string[] { ScriptService.Scope.Forms, ScriptService.Scope.Spreadsheets,
ScriptService.Scope.Drive, ScriptService.Scope.UserinfoEmail, YouTubeService.Scope.YoutubeUpload, YouTubeService.Scope.Youtube };
My guess is that the API had been updated since I originally wrote my script some months ago, as I was able to successfully run it at that time with the three original authorizations that I had.
I have an installed c# app with code working that gets the authorization code and exchanges it for an access token. I am storing off the refresh token. I know at some point I need to use it to get a new access token. Let's assume that I am periodically calling the following method to monitor the files that have been shared with my Drive account.
/// <summary>
/// Retrieve a list of File resources.
/// </summary>
/// <param name="service">Drive API service instance.</param>
/// <returns>List of File resources.</returns>
public static List<File> retrieveAllFiles(DriveService service) {
List<File> result = new List<File>();
FilesResource.ListRequest request = service.Files.List();
request.Q = "sharedWithMe and trashed=false";
do {
try {
FileList files = request.Fetch();
result.AddRange(files.Items);
request.PageToken = files.NextPageToken;
} catch (Exception e) {
Console.WriteLine("An error occurred: " + e.Message);
request.PageToken = null;
}
} while (!String.IsNullOrEmpty(request.PageToken));
return result;
}
}
I assume that at some point the call to service.Files.List() is going to fail. How do I know it has failed due to an expired access token and what is the code to use the refresh token? I already have some code (below) that I gleaned from here to use the refresh token. Will this method get called when the access token expires?
private static IAuthorizationState GetAuthorization(NativeApplicationClient arg)
{
// If we already have a RefreshToken, use that
if (!string.IsNullOrEmpty(RefreshToken))
{
state.RefreshToken = RefreshToken;
if (arg.RefreshToken(state)) {
mTextBox.Text = "RF: " + RefreshToken;
return state;
}
}
// authCode is a TextBox on the form
var result = arg.ProcessUserAuthorization(mTextBox.Text, state);
RefreshToken = state.RefreshToken;
return result;
}
An access token will expire after 1 hour - after that time you will begin to receive "401 Invalid Credentials" errors when you make calls against a Google API.
I'm not familiar with the .NET Google API Client library - the Java and Python libraries will automatically make a request for a new access token when this occurs, depending on how you are creating the DriveService object. I would expect the .NET library to have similar semantics.
If someone still have Problems with refreshing the AccessToken, maybe this can help you finding a solution:
Google.GData.Client.RequestSettings settings = new RequestSettings("<AppName>");
Google.GData.Client.OAuth2Parameters parameters = new OAuth2Parameters()
{
ClientId = "<YourClientId>",
ClientSecret = "<YourClientSecret>",
AccessToken = "<OldAccessToken>", //really necessary?
RedirectUri = "urn:ietf:wg:oauth:2.0:oob",
RefreshToken = "<YourRefreshToken>",
AccessType = "offline",
TokenType = "refresh",
Scope = "https://www.google.com/m8/feeds/" //Change to needed scopes, I used this for ContactAPI
};
try
{
Google.GData.Client.OAuthUtil.RefreshAccessToken(parameters);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
When to use the refresh token:
From what I understand you use the refresh token when you do not wish to authenticate your app each time it boots. This is extremely useful for debugging during application development (as manual authentication can get annoying after a while).
How to use the refresh token:
In the most basic sense:
public static GOAuth2RequestFactory RefreshAuthenticate(){
OAuth2Parameters parameters = new OAuth2Parameters(){
RefreshToken = "<YourRefreshToken>",
AccessToken = "<AnyOfYourPreviousAccessTokens>",
ClientId = "<YourClientID>",
ClientSecret = "<YourClientSecret>",
Scope = "https://spreadsheets.google.com/feeds https://docs.google.com/feeds",
AccessType = "offline",
TokenType = "refresh"
};
string authUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
return new GOAuth2RequestFactory(null, "<YourApplicationName>", parameters);
}
You would use this method in other code with a service, perhaps like this
GOAuth2RequestFactory requestFactory = RefreshAuthenticate();
SpreadsheetsService service = new SpreadsheetsService("<YourApplicationName>");
service.RequestFactory = requestFactory;
Hope this helps!
Spent the last two days figuring out how to use and renew the access token using the refresh token. My answer is posted in another thread here:
How Google API V 3.0 .Net library and Google OAuth2 Handling refresh token