This is my first time in working with Google's Drive api, so I apologize ahead of time if I've gotten something wrong.
Here's what I've got:
I used a web app to manually walk through the OAuth2 dance so that I could get an AccessToken, RefreshToken, and TokenType in a TokenResponse. Those values all came back successfully, and I stored them.
Now, I'm wanting to use that same information, along with the same username of a Google acct I created explicitly for this purpose, in a Windows Service that will monitor files within a specific directory of that user's Drive.
I manually create a TokenResponse, GoogleAuthorizationCodeFlow, UserCredential and DriveService as shown here:
//Populate the token response with the stored values
_tokenResponse = new TokenResponse
{
AccessToken = AppConfiguration.GetSetting("StoredAccessToken"),
TokenType = AppConfiguration.GetSetting("StoredTokenType"),
RefreshToken = AppConfiguration.GetSetting("StoredRefreshToken"),
Issued = DateTime.Parse(AppConfiguration.GetSetting("StoredTokenIssuedDate")),
ExpiresInSeconds = long.Parse(AppConfiguration.GetSetting("StoredExpiresInSeconds"))
};
//Next, build our code flow
_apiCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = AppConfiguration.GetSetting("ApiClientId"),
ClientSecret = AppConfiguration.GetSetting("ApiClientSecret")
},
Scopes = new[] { DriveService.Scope.Drive },
DataStore = new FileDataStore(AppConfiguration.GetSetting("RegisteredApplicationName"))
});
//Now create the UserCredential with those stored values
_credential = new UserCredential(_apiCodeFlow, AppConfiguration.GetSetting("DriveApiServiceAccountEmail"), _tokenResponse);
//And generate a DriveService instance using the UserCredential
_driveService = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = _credential,
ApplicationName = AppConfiguration.GetSetting("RegisteredApplicationName")
});
This all appears to work okay, but when I try to do something like
DriveService.Files.List.Execute()
I get an exception:
"Error:\"invalid_grant\", Description:\"\", Uri:\"\""
Now, I've been digging into the Google documentation for several days, and am pretty much at a loss. I started looking at the DrEdit solution they provide, but it's referencing objects that I don't see in their most recent API namespaces such as: IAuthorizationState, NativeApplicationClient. I'm using Google.Apis.Drive.v2, Google.Apis.Auth.OAuth2, Google.Apis.Auth.OAuth2.Flows, Google.Apis.OAuth2.Responses, Google.Apis.Services, Google.Apis.Util, and Google.Apis.Util.Store namespaces. Is there something I'm missing?
I'm wondering if it has anything to do with the url the request is coming from, as you have to register all redirect urls in the google console for registering these apps. However, I don't see a way for me to specify what redirect url to use when doing this from a windows service. That may not even be the issue though, just a thought.
My need is fairly simple - I just need to be able to use the refresh token I've saved, along with the username of my new user created only for this purpose, and get a DriveService instance that will let me traverse the files that this user can access.
Any help would be sincerely appreciated. Thanks in advance!!
EDIT
Using Fiddler to monitor the network traffic, it looks like the request is going out as it should. The grant_type is marked as "refresh_token", and all other items are populated with the correct values. See the attached image:
EDIT 2
In the GoogleAuthorizationCodeFlow class, there's a method named "RefreshTokenAsync" that takes a username, a refresh token, and a cancellation token. It produces the same results in Fiddler as does calling any method in the DriveService class, so I'm thinking that it is automatically trying to refresh the token behind the scenes when making a call to the DriveService. Not sure if this is helpful to those who may be able to answer this, but I thought I'd mention anyways.
12/18/2013 Update based on DaImTo's Suggestion
I created my own implementation of IDataStore, and here is the code for the GetAsync method, which I've stepped through and it returns a Token object as it should:
public Task<T> GetAsync<T>(string key)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
try
{
//First, load the token from our local json file
//NOTE: If using the built in FileDataStore, Google would store this same json info in the local users appdata\roaming\{appname} folder
Stream stream = new FileStream("StoredToken.json", FileMode.Open, FileAccess.Read);
//Read the file contents into a string
string sToken = (new StreamReader(stream)).ReadToEnd();
//Close the stream so that the file isn't locked
stream.Close();
stream.Dispose();
//Deserialize into the type that Google requests
var token = Google.Apis.Json.NewtonsoftJsonSerializer.Instance.Deserialize<T>(sToken);
//Set the result of the TaskCompletionSource to our value
tcs.SetResult(token);
}
catch (Exception exc)
{
//No need to throw exception here - whatever is calling this method would want to handle exceptions.
//So, just set the Exception of the TaskCompletionSource and return it
tcs.SetException(exc);
}
return tcs.Task;
}
Now, here's my updated way of getting a UserCredential object and using it to populate my DriveService instance:
_credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
clientSecrets: new ClientSecrets
{
ClientId = AppConfiguration.GetSetting("ApiClientId"),
ClientSecret = AppConfiguration.GetSetting("ApiClientSecret")
},
scopes: new[] {DriveService.Scope.Drive},
dataStore: new CustomTokenDataStore(),
user: AppConfiguration.GetSetting("DriveApiServiceAccountEmail"),
taskCancellationToken: CancellationToken.None).Result;
_driveService = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = _credential,
ApplicationName = AppConfiguration.GetSetting("RegisteredApplicationName")
});
I then tried to do a simple query with the DriveService API:
using (GoogleDriveApiUtils util = new GoogleDriveApiUtils())
{
FilesResource.ListRequest request = util.DriveService.Files.List();
/*Documentation for using the search parameters:
* https://developers.google.com/drive/search-parameters
*/
//Add this to the query to get only folders
request.Q = "mimeType='application/vnd.google-apps.folder'";
FileList fList = request.Execute();
}
And here is where I get the same exception - "invalid_grant". From what I've been able to research, this is to do with the url the response is coming from. However, I'm doing this from a Windows Service - so I'm clueless how I can make it come from the registered url on the request to the DriveService. However, I may be totally on the wrong track.
Any other ideas? Again, all help is greatly appreciated :)