Search code examples
c#oauth-2.0google-docs-apigoogle-sheets-apigoogle-api-dotnet-client

Unauthorized to list Google spreadsheets with scoped OAuth token


I'm making a program running on Windows desktop. I'm pretty sure I registered the application type as an "Installed application" − does this matter anyway?

I can get an access code from the user and combine that with my client ID and secret to get an access token. I can use this access token to run some example code that requests the user's contacts, but when I try listing their spreadsheets (using example code), it fails: Execution of request failed: https://spreadsheets.google.com/feeds/spreadsheets/private/full, The remote server returned an error: (401) Unauthorized

I thought the scopes were correct and in terms of the APIs for my program, I've enabled Contacts API, Drive API and Drive SDK (I didn't think I needed this one anyway).

Here's the relevant C# code, thanks in advance if you can explain why the request to list the spreadsheets fails − I've indicated the point where this occurs:

using Google.GData.Client;
using Google.GData.Spreadsheets;
using Google.Contacts;
using Google.GData.Apps;
…
private static OAuth2Parameters googleAuth;
…
string CLIENT_ID = …;
string CLIENT_SECRET = …;
string SCOPE = "https://spreadsheets.google.com/feeds/ https://docs.googleusercontent.com/ https://docs.google.com/feeds/ https://www.google.com/m8/feeds/"; // read, write, create/delete sheets, use contacts
string REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
…

googleAuth = new OAuth2Parameters();
googleAuth.ClientId = CLIENT_ID;
googleAuth.ClientSecret = CLIENT_SECRET;
googleAuth.RedirectUri = REDIRECT_URI;
googleAuth.Scope = SCOPE;
googleAuth.ResponseType = "code";
…
string authorizationUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(googleAuth);
…
googleAuth.AccessCode = PaneManager.googleCode; // read it back from user (PaneManager is UI)
OAuthUtil.GetAccessToken(googleAuth);
…
RunContactsSample(googleAuth); // runs fine
SpreadsheetsService service = new SpreadsheetsService("Ribbon"); // does this name have any relevance?
service.SetAuthenticationToken(googleAuth.AccessToken);

// Instantiate a SpreadsheetQuery object to retrieve spreadsheets.
SpreadsheetQuery query = new SpreadsheetQuery();
SpreadsheetFeed feed = service.Query(query); // this fails
// Iterate through all of the spreadsheets returned
foreach (SpreadsheetEntry entry in feed.Entries)
{
    // Print the title of this spreadsheet to the screen
    Console.WriteLine(entry.Title.Text);
}

private static void RunContactsSample(OAuth2Parameters parameters)
{
    try
    {
        RequestSettings settings = new RequestSettings("Ribbon", parameters);
        ContactsRequest cr = new ContactsRequest(settings);

        Feed<Contact> f = cr.GetContacts();
        foreach (Contact c in f.Entries)
        {
            Console.WriteLine(c.Name.FullName);
        }
    }
    catch (AppsException a)
    {
        Console.WriteLine("A Google Apps error occurred.");
        Console.WriteLine();
        Console.WriteLine("Error code: {0}", a.ErrorCode);
        Console.WriteLine("Invalid input: {0}", a.InvalidInput);
        Console.WriteLine("Reason: {0}", a.Reason);
    }
}

Solution

  • It turns out the way to authorise the request wasn't to call SetAuthenticationToken; it's necessary to create a GOAuth2RequestFactory and add that to the service:

    SpreadsheetsService service = new SpreadsheetsService("Ribbon");
    GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "Ribbon", googleAuth);
    service.RequestFactory = requestFactory;
    

    I was under the impression that I would be the one calling the factory and hence, wasn't using it since I didn't know how.