Search code examples
.netauthenticationgoogle-glassgoogle-gdkgoogle-mirror-api

Inserting accounts into Mirror API using service account returns "invalid_grant" error


I'm using the Mirror API .NET Library with a Google Service Account to do 3rd Party Authentication via the MyGlass app. I'm following the Authentication for GDK Glassware documentation to authenticate my users server-side and then insert the account.

When I try to insert a new account using the Mirror API, I get an OAuth Error: "invalid_grant":

Stack Trace:

Google.Apis.Auth.OAuth2.Responses.TokenResponseException was unhandled by user code HResult=-2146233088 Message=Error:"invalid_grant", Description:"", Uri:"" Source=Google.Apis

at Google.Apis.Requests.ClientServiceRequest`1.Execute() in c:\code\google.com\google-api-dotnet-client\default_182\Tools\Google.Apis.Release\bin\Debug\output\default\Src\GoogleApis\Apis\Requests\ClientServiceRequest.cs:line 96\r\n

It's being unwrapped and thrown by the Execute() function in ClientServiceRequest.cs. Line 96.

Source Code:

const string password = "notasecret";
X509Certificate2 certificateToExport = new X509Certificate2(HostingEnvironment.MapPath("/Path/To/Certificate.p12"), password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
var rsa = (RSACryptoServiceProvider)certificateToExport.PrivateKey;

// Have to export the provider or you get an "Invalid Algorithm" error when 
// trying to sign the request.
RSACryptoServiceProvider cryptoProvider = new RSACryptoServiceProvider();
cryptoProvider.ImportParameters(rsa.ExportParameters(true));

var serviceAccountCredential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(id: "MYCLIENTID.apps.googleusercontent.com")
{
Key = cryptoProvider,
Scopes = new List<string>() { "https://www.googleapis.com/auth/glass.thirdpartyauth" },
User = "[email protected]"
});

var mirrorService = new MirrorService(new BaseClientService.Initializer() { HttpClientInitializer = serviceAccountCredential });
Account account = new Account() { AuthTokens = new AuthToken[] { new AuthToken() { AuthTokenValue = sessionKey, Type = "sessionKey" } } };

// Exception thrown here
Account insertedAccount = mirrorService.Accounts.Insert(account, userToken: userToken, accountType: "example.com", accountName: accountName).Execute();

Some things I've double and triple-checked:

  • I have the correct client ID and email address.
  • I have provided the scope: https://www.googleapis.com/auth/glass.thirdpartyauth.
  • The userToken that was passed to me as a query parameter by the MyGlass app webview is provided back to the MirrorAPI.
  • I'm signing the request with the certificate provided to me by Google.
  • MyGlass is using the correct authentication redirect URL
  • The Google Analytics documentation says about an "invalid_grant" error:
  1. Your server's clock is not in sync with NTP.
  2. The refresh token limit has been exceeded.

Server clock is in-sync. This happens on multiple machines. Will investigate whether/how the token limit has been exceeded. I was under the impression that the API library would handle refreshes for me.

I feel I've completely mis-understood something (likely). I'd appreciate someone pointing out what I've got wrong here.


Solution

  • From the chat thread, the issue was due to the Service Account authentication failing due to the wrong data being passed in the ServiceAccountCredential class.

    The code snippet used in the original question should be fixed with:

    var serviceAccountCredential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(id: "[email protected]")
    {
        Key = cryptoProvider,
        Scopes = new List<string>() { "https://www.googleapis.com/auth/glass.thirdpartyauth" }
    });