I am using Push Notifications API for integrating client's calendar event. I have created channel also. But I am not able to get refresh token while authentication. I need to get user consent from UI and based on the verification code I am getting access token and refresh token. But when I am trying to get access token using existing refresh token, it is asking for client Id and secret. As I am just using consent from user I do not have client Id and client secret for each user. How can I resolve this for my application?
I am using below code for authentication
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT)
throws IOException {
// Load client secrets.
InputStream in = CalendarQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
if (in == null) {
throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
}
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
Here is how I am calling calender API
public static void main(String... args) throws IOException, GeneralSecurityException {
// Build a new authorized API client service.
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
Credential credential = getCredentials(HTTP_TRANSPORT);
String accessToken = credential.getAccessToken();
Calendar service =
new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, googleCredential)
.setApplicationName(APPLICATION_NAME)
.build();
System.out.println(accessToken);
// List the next 10 events from the primary calendar.
DateTime now = new DateTime(System.currentTimeMillis());
Events events =
service
.events()
.list("primary")
.setMaxResults(10)
.setTimeMin(now)
.setOrderBy("startTime")
.setSingleEvents(true)
.execute();
List<Event> items = events.getItems();
if (items.isEmpty()) {
System.out.println("No upcoming events found.");
} else {
System.out.println("Upcoming events");
for (Event event : items) {
DateTime start = event.getStart().getDateTime();
if (start == null) {
start = event.getStart().getDate();
}
// System.out.printf("%s %s (%s)\n", event.getId(), event.getSummary(), event.toString());
}
}
}
}
Related
I have set up a google service account with Domain-wide delegation permissions. I am using Google Calendar APIs to manage the calendar events. Here is the sample code snippet:
InputStream inputstream = null;
try {
inputstream = new ByteArrayInputStream(this.key);
Credential cred = new GoogleCredential.Builder().setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory())
.setServiceAccountId(cs.getGoogleServiceAccountId())
.setServiceAccountScopes(Arrays.asList(CalendarScopes.CALENDAR))
.setServiceAccountUser("c_188d28jockafkhljibdpn05gio6iq#resource.calendar.google.com")
.setServiceAccountPrivateKey(SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(),
inputstream,"notasecret",
"privatekey", "notasecret"))
.build();
calendar = new Calendar.Builder(new NetHttpTransport(), new JacksonFactory(), cred).setApplicationName("applicationName").build();
Event e = new Event().setSummary("Test");
e.setDescription("Test");
e.setStart(new EventDateTime().setDateTime(new DateTime(new Date().getTime())));
e.setStart(new EventDateTime().setDateTime(new DateTime(DateUtils.addHours(new Date(), 1).getTime())));
Event insert = calendar.events().insert("c_188d28jockafkhljibdpn05gio6iq#resource.calendar.google.com", e).setSendNotifications(true).execute();
logger.info("event created = {}", JsonUtil.getJSONString(insert));
}
finally {
if(inputstream != null) {
inputstream.close();
}
}
It gives the following exception when I try to create an event in the room resource calendar:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
POST https://oauth2.googleapis.com/token
{
"error" : "invalid_grant",
"error_description" : "Invalid email or User ID"
}
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:326)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:346)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:397)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:494)
at com.google.api.client.auth.oauth2.Credential.intercept(Credential.java:217)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:880)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:541)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:474)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:591)
It works fine when I try to create an event in any active user's calendar by replacing the resource email id with a particular user's email. What could be the issue?
Is it possible to use bot framework to send / update message as a user rather than the bot as the sender of message (perhaps after some form of authentication with the user that allows the bot to perform such operations)?
Below is an illustration of the current situation:
I have sent a message by person A into Teams channel, and I would like to do an update to the message using bot framework as Graph API does not support update of message. However, the message does not get updated although there was no error.
This is placed in a web api controller "/test". Hence the update will be trigger by sending a POST to /test.
ServiceClientCredentials service = new CustomLoginCredentials();
var connectorClient = new ConnectorClient(new Uri("https://smba.trafficmanager.net/apac/"), service);
var newActivity = MessageFactory.Text($"hello: updated on {DateTime.Now}");
string destActivityId = "this is the activity id of the existing message sent using person A account on MS Teams";
string conversationId = $"channelidhere;messageid={destActivityId}";
connectorClient.Conversations.UpdateActivityAsync(conversationId, destActivityId, newActivity, default(CancellationToken));
CustomLoginCredentials
public class CustomLoginCredentials:ServiceClientCredentials
{
private string AuthenticationToken { get; set; }
public override void InitializeServiceClient<T>(ServiceClient<T> client)
{
System.Diagnostics.Debug.WriteLine("CustomLoginCredentials:InitializeServiceClient 1");
IPublicClientApplication publicClient = PublicClientApplicationBuilder.Create("clientid")
.WithAuthority(AzureCloudInstance.AzurePublic, "tenantId")
.Build();
AuthenticationResult authenticationResult = publicClient.AcquireTokenByUsernamePassword(new string[] { "https://graph.microsoft.com/.default" }, "personA MS email", new NetworkCredential("", "personA MS email password").SecurePassword).ExecuteAsync().GetAwaiter().GetResult();
AuthenticationToken = authenticationResult.AccessToken;
}
public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null) throw new ArgumentNullException("request");
if (AuthenticationToken == null) throw new InvalidOperationException("Token Provider Cannot Be Null");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AuthenticationToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
await base.ProcessHttpRequestAsync(request, cancellationToken);
}
}
Where a bot sends messages on behalf of a user, attributing the message to that user helps with engagement and showcase a more natural interaction flow. This feature allows you to attribute a message from your bot to a user on whose behalf it was sent. You can use on-behalf-attribute to send message as a user - please check User attribution for bots messages
AFAIK, the Firebase Instance Token will be refreshed under the following 4 conditions:
App deletes Instance ID
App is restored on a new device
User uninstalls/reinstall the app
User clears app data
Suppose a user is using Token A as his 'FCM address'. Every time when he logs in the app, he will register the Token A to the Firestore along with this user's UUID so user-specific cloud message can be sent to him. When he logs out, the system will fire a request to firestore for removing the token A record.
Now, when the user reinstalls the app, the instance id is refreshed and a new Token B is generated. The Token A becomes useless. Unfortunately, if the user does not log out before the uninstallation, token A will stay in the firestore forever.
Any workaround or wiser way to handle this case?
Keeping your token registry up to date requires two steps:
Remove outdated tokens from your application code.
Check for outdated tokens and remove them when you send messages.
Your approach of removing a token that is no longer used, is #1.
The second step though is to remove tokens from your registry/database when you get a messaging/invalid-registration-token or messaging/registration-token-not-registered response when trying to send a message to it. The functions-samples repo contains a great example of this:
admin.messaging().sendToDevice(tokens, payload).then((response) => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
// TODO: remove the token from your registry/database
}
}
});
});
The above code uses the Firebase Admin SDK for Node.js, but the same logic could also be applied to other platforms or when sending messages through the HTTPS endpoints.
As Frank mentioned in his answer you can remove them when sending messages and getting not registered error.
Here how I delete outdated registration tokens when registering a new one using C#.
First of all using Instance ID API I get token info as following:
public async Task<FCMTokenInfo> GetTokenInfoAsync(string token)
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://iid.googleapis.com");
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", String.Format("key={0}", "your-authorization-key"));
var uri = $"/iid/info/{token}";
var httpResponse = await client.GetAsync(uri);
var responseStr = await httpResponse.Content.ReadAsStringAsync();
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
//log 400 bad request and do whatever you want
}
var result = JsonConvert.DeserializeObject<FCMTokenInfo>(responseStr);
return result;
}
catch (Exception ex)
{
//log the exception
throw;
}
}
FCMTokenInfo.cs
public class FCMTokenInfo
{
public string Application { get; set; }
public string Subtype { get; set; }
public string Scope { get; set; }
public string AuthorizedEntity { get; set; }
public string Platform { get; set; }
}
And then inside the service that saves registration tokens inside the database:
//this method gets called when a new token is sent by the javascript web app
public async Task AddTokenAsync(Guid accountId, string token)
{
try
{
//getting TokenInfo of the current token(or refreshed one for that app)
var fcmTokenInfo = await firebaseServices.GetTokenInfoAsync(token);
//adding the current token
dbContext.FcmRegisterationTokens.Add(new FcmRegisterationToken
{
Token = token,
AccountId = accountId,
AddingDate = DateTimeOffset.UtcNow,
Application = fcmTokenInfo.Application,
Subtype = fcmTokenInfo.Subtype,
AuthorizedEntity = fcmTokenInfo.AuthorizedEntity,
Scope = fcmTokenInfo.Scope,
Platform = fcmTokenInfo.Platform
});
var outdatedTokens = await dbContext.FcmRegisterationTokens
.Where(x => x.AccountId == accountId
&& x.Application == fcmTokenInfo.Application
&& x.Platform == fcmTokenInfo.Platform
).ToListAsync();
//remove them
dbContext.FcmRegisterationTokens.RemoveRange(outdatedTokens);
dbContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}
I'm trying to implement a simple OAuthAuthorizationServerProvider in ASP.NET WebAPI 2. My main purpose is to learn how to have a token for a mobile app. I would like users to login with username & password, and then receive a token (and a refresh token so they won't have to re-enter credentials once token expires). Later on, I would like to have the chance to open the API for external use by other applications (like one uses Facebook api and such...).
Here is how I've set-up my AuthorizationServer:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = new SimpleAuthorizationServerProvider(new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = ValidateUser
}),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
});
This is my SimpleAuthorizationServerProviderOptions implementation:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public delegate Task<bool> ClientCredentialsValidationFunction(string clientid, string secret);
public delegate Task<IEnumerable<Claim>> UserCredentialValidationFunction(string username, string password);
public SimpleAuthorizationServerProviderOptions Options { get; private set; }
public SimpleAuthorizationServerProvider(SimpleAuthorizationServerProviderOptions options)
{
if (options.ValidateUserCredentialsFunction == null)
{
throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null");
}
Options = options;
}
public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction)
{
Options = new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = userCredentialValidationFunction
};
}
public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction, ClientCredentialsValidationFunction clientCredentialsValidationFunction)
{
Options = new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = userCredentialValidationFunction,
ValidateClientCredentialsFunction = clientCredentialsValidationFunction
};
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if (Options.ValidateClientCredentialsFunction != null)
{
string clientId, clientSecret;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
var clientValidated = await Options.ValidateClientCredentialsFunction(clientId, clientSecret);
if (!clientValidated)
{
context.Rejected();
return;
}
}
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (Options.ValidateUserCredentialsFunction == null)
{
throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null");
}
var claims = await Options.ValidateUserCredentialsFunction(context.UserName, context.Password);
if (claims == null)
{
context.Rejected();
return;
}
// create identity
var identity = new ClaimsIdentity(claims, context.Options.AuthenticationType);
// create metadata to pass to refresh token provider
var props = new AuthenticationProperties(new Dictionary<string, string>()
{
{ "as:client_id", context.UserName }
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
// enforce client binding of refresh token
if (originalClient != currentClient)
{
context.Rejected();
return;
}
// chance to change authentication ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
newIdentity.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
}
}
And my SimpleRefreshTokenProvider implementation:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens =
new ConcurrentDictionary<string, AuthenticationTicket>();
public void Create(AuthenticationTokenCreateContext context)
{
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
};
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
_refreshTokens.TryAdd(guid, refreshTokenTicket);
context.SetToken(guid);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
}
What I don't fully understand is the use of ClientId and Secret vs Username and Password. The code I pasted generates a token by username and password and I can work with that token (until it expires), but when I try to get a refresh token, I must have the ClientId.
Also, if a token expires, the correct way is to send the refresh token and get a new token? What if the refresh token gets stolen? isn't it the same as a username & password getting stolen?
What I don't fully understand is the use of ClientId and Secret vs Username and Password. The code I pasted generates a token by username and password and I can work with that token (until it expires), but when I try to get a refresh token, I must have the ClientId.
Also, if a token expires, the correct way is to send the refresh token and get a new token? What if the refresh token gets stolen? isn't it the same as a username & password getting stolen?
In OAuth2 is essential to authenticate both the user and the client in any authorization flow defined by the protocol. The client authentication (as you may guess) enforces the use of your API only by known clients. The serialized access token, once generated, is not bound to a specific client directly. Please note that the ClientSecret must be treated as a confidential information, and can be used only by clients that can store this information in some secure way (e.g. external services clients, but not javascript clients).
The refresh token is simply an alternative "grant type" for OAuth2, and, as you stated correctly, will substitute the username and password pair for a User. This token must be treated as confidential data (even more confidential than the access token), but gives advantages over storing the username & password on the client:
it can be revoked by the user if compromised;
it has a limited lifetime (usually days or weeks);
it does not expose user credentials (an attacker can only get access tokens for the "scope" the refresh token was issued).
I suggest you to read more about the different grant types defined in OAuth 2 checking in the official draft. I also recommend you this resource I found very useful when firstly implemented OAuth2 in Web API myself.
Sample requests
Here are two request examples using fiddler, for Resource Owner Password Credentials Grant:
and for Refresh Token Grant:
I need to connect a Google Calendar on my .NET 4.5 application (VS 2013 project).
I want to get all the information from the Calendar, such as: events, dates, notes, names, guests, etc...
I used the Google Developer Console to create both a Web Application Client ID and a Service Account, but I get different errors and no results.
I've implemented 2 different methods, one to login with Web Application Client ID and one to use Service Account.
This is the common ASPX page
public partial class Calendar : System.Web.UI.Page
{
// client_secrets.json path.
private readonly string GoogleOAuth2JsonPath = ConfigurationManager.AppSettings["GoogleOAuth2JsonPath"];
// p12 certificate path.
private readonly string GoogleOAuth2CertificatePath = ConfigurationManager.AppSettings["GoogleOAuth2CertificatePath"];
// #developer... e-mail address.
private readonly string GoogleOAuth2EmailAddress = ConfigurationManager.AppSettings["GoogleOAuth2EmailAddress"];
// certificate password ("notasecret").
private readonly string GoogleOAuth2PrivateKey = ConfigurationManager.AppSettings["GoogleOAuth2PrivateKey"];
// my Google account e-mail address.
private readonly string GoogleAccount = ConfigurationManager.AppSettings["GoogleAccount"];
protected void Page_Load(object sender, EventArgs e)
{
// Enabled one at a time to test
//GoogleLoginWithServiceAccount();
GoogleLoginWithWebApplicationClientId();
}
}
Using Web Application Client ID
I've tried to configure the redirect URIs parameter for the JSON config file, but no URI seems to work. I'm on development environment so I'm using IIS Express on port 44300 (SSL enabled). The error I'm getting is:
Error: redirect_uri_mismatch
Application: CalendarTest
The redirect URI in the request: http://localhost:56549/authorize/ did not match a registered redirect URI.
Request details
scope=https://www.googleapis.com/auth/calendar
response_type=code
redirect_uri=http://localhost:56549/authorize/
access_type=offline
client_id=....apps.googleusercontent
The code
private void GoogleLoginWithWebApplicationClientId()
{
UserCredential credential;
// This example uses the client_secrets.json file for authorization.
// This file can be downloaded from the Google Developers Console
// project.
using (FileStream json = new FileStream(Server.MapPath(GoogleOAuth2JsonPath), FileMode.Open,
FileAccess.Read))
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(json).Secrets,
new[] { CalendarService.Scope.Calendar },
"...#developer.gserviceaccount.com", CancellationToken.None,
new FileDataStore("Calendar.Auth.Store")).Result;
}
// Create the service.
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "CalendarTest"
});
try
{
CalendarListResource.ListRequest listRequest = service.CalendarList.List();
IList<CalendarListEntry> calendarList = listRequest.Execute().Items;
foreach (CalendarListEntry entry in calendarList)
{
txtCalendarList.Text += "[" + entry.Summary + ". Location: " + entry.Location + ", TimeZone: " +
entry.TimeZone + "] ";
}
}
catch (TokenResponseException tre)
{
txtCalendarList.Text = tre.Message;
}
}
Using Service Account (preferred)
I can reach the CalendarListResource.ListRequest listRequest = service.CalendarList.List(); line, so I guess the login works, but then, when I want the list on IList<CalendarListEntry> calendarList = listRequest.Execute().Items; I get the following error:
Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:""
The code
private void GoogleLoginWithServiceAccount()
{
/*
* From https://developers.google.com/console/help/new/?hl=en_US#generatingoauth2:
* The name of the downloaded private key is the key's thumbprint. When inspecting the key on your computer, or using the key in your application,
* you need to provide the password "notasecret".
* Note that while the password for all Google-issued private keys is the same (notasecret), each key is cryptographically unique.
* GoogleOAuth2PrivateKey = "notasecret".
*/
X509Certificate2 certificate = new X509Certificate2(Server.MapPath(GoogleOAuth2CertificatePath),
GoogleOAuth2PrivateKey, X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(GoogleOAuth2EmailAddress)
{
User = GoogleAccount,
Scopes = new[] { CalendarService.Scope.Calendar }
}.FromCertificate(certificate));
// Create the service.
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "CalendarTest"
});
try
{
CalendarListResource.ListRequest listRequest = service.CalendarList.List();
IList<CalendarListEntry> calendarList = listRequest.Execute().Items;
foreach (CalendarListEntry entry in calendarList)
{
txtCalendarList.Text += "[" + entry.Summary + ". Location: " + entry.Location + ", TimeZone: " +
entry.TimeZone + "] ";
}
}
catch (TokenResponseException tre)
{
txtCalendarList.Text = tre.Message;
}
}
I prefer the Service Account login because there's no need for user to login with consent screen, since the application should do it by itself each time it needs to refresh. Is it possible to use a Service Account with free Google Account or do I need Admin console? I've read many conflicting reports about that...
Anyway, looking around with Google and also in StackOverflow, I didn't find a solution. I've seen and tried many questions and solutions but with no results. Some examples:
Keep getting Error: redirect_uri_mismatch using youtube api v3
Google Calendar redirect_uri_mismatch
Google API Calender v3 Event Insert via Service Account using Asp.Net MVC
https://developers.google.com/google-apps/calendar/instantiate
https://groups.google.com/forum/#!topic/google-calendar-api/MySzyAXq12Q
Please help! :-)
UPDATE 1 - Using Service Account (preferred) - SOLVED!
The only problem in my code was:
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(GoogleOAuth2EmailAddress)
{
//User = GoogleAccount,
Scopes = new[] { CalendarService.Scope.Calendar }
}.FromCertificate(certificate));
There's NO NEED for User = GoogleAccount.
There is definitely something wrong with your authentication. Here is a copy of my Service account authentication method.
/// <summary>
/// Authenticating to Google using a Service account
/// Documentation: https://developers.google.com/accounts/docs/OAuth2#serviceaccount
/// </summary>
/// <param name="serviceAccountEmail">From Google Developer console https://console.developers.google.com</param>
/// <param name="keyFilePath">Location of the Service account key file downloaded from Google Developer console https://console.developers.google.com</param>
/// <returns></returns>
public static CalendarService AuthenticateServiceAccount(string serviceAccountEmail, string keyFilePath)
{
// check the file exists
if (!File.Exists(keyFilePath))
{
Console.WriteLine("An Error occurred - Key file does not exist");
return null;
}
string[] scopes = new string[] {
CalendarService.Scope.Calendar , // Manage your calendars
CalendarService.Scope.CalendarReadonly // View your Calendars
};
var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
try
{
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
}.FromCertificate(certificate));
// Create the service.
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar API Sample",
});
return service;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
}
I have a tutorial on it as well. My tutorial Google Calendar API Authentication with C# The code above was ripped directly from my sample project Google-Dotnet-Samples project on GitHub
Note/headsup: Remember that a service account isn't you. It does now have any calendars when you start you need to create a calendar and insert it into the calendar list before you are going to get any results back. Also you wont be able to see this calendar though the web version of Google Calendar because you cant log in as a service account. Best work around for this is to have the service account grant you permissions to the calendar.