Fairly new in looking at the Google OAuth library, but have spent a while in Azure/Exchange OAuth. We have a wrapper around the various libraries in the Adapter Pattern, so our app code is consistent and calls the same methods. Here's what the Google one looks like
private string _state;
private GoogleAuthorizationCodeFlow _flow;
public GoogleTokenProvider(IOAuthSettings settings, string state, string baseUrl) : base(settings, state, baseUrl)
{
//https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-asp.net-mvc
_state = state;
GoogleAuthorizationCodeFlow.Initializer init = new GoogleAuthorizationCodeFlow.Initializer {
ClientSecrets = new ClientSecrets
{
ClientId = settings.ClientId,
ClientSecret = settings.ClientSecret
},
Scopes = this.Scopes,
DataStore = new GoogleTokenStore(settings) //Our implementation - not called yet
};
_flow = new GoogleAuthorizationCodeFlow(init);
}
The first step (once constructed) is to get the url to start the auth process.
public string GetAuthorizationUrl()
{
//Using Flow
var req = _flow.CreateAuthorizationCodeRequest(this.RedirectUrl);
var baseUrl = req.Build().AbsoluteUri;
baseUrl += "&state=" + this._state;
return baseUrl;
// Manually build request
//StringBuilder builder = new StringBuilder();
//builder.Append("https://accounts.google.com/o/oauth2/v2/auth?");
//builder.AppendFormat("scope={0}", String.Join("+", this.Scopes));
//builder.Append("&access_type=offline&include_granted_scopes=true&response_type=code");
//builder.AppendFormat("&state={0}&", this._state);
//builder.AppendFormat("&redirect_uri={0}", this.RedirectUrl);
//builder.AppendFormat("&client_id={0}", this.settings.ClientId);
//return builder.ToString();
}
This works, and we're directed off to the OAuth site, we log in with our Google account. Our callback is fired and when debugging the callback, we obtain the code, and state passed through and call this method:
public string AcquireTokenByAuthorizationCode(string code)
{
Google.Apis.Auth.OAuth2.Responses.TokenResponse result = _flow.ExchangeCodeForTokenAsync("userid", code, RedirectUrl, new System.Threading.CancellationToken()).Result;
return result.AccessToken;
}
However, this causes an error:

Error:"invalid_client", Description:"Unauthorized", Uri:""
There are two queries.
- I am unsure what "UserId" should be, a lot of posts just have "userid" or "me" in there, so what it is for?
- Since the Flow class is built by the same settings, why does the client suddenly become invalid.
Thanks in advance.
Google Configuration
The intent is to be able to read the emails (Gmail) from this account
Enabled APIs:
- Gmail API
- Google+ API
google Data Store Implementation
public class GoogleTokenStore : IDataStore
{
private readonly IOAuthSettings _settings;
public GoogleTokenStore(IOAuthSettings settings)
{
this._settings = settings;
}
private Dictionary<string, T> Decode<T>()
{
using (var memoryStream = new MemoryStream())
{
var binaryFormatter = new BinaryFormatter();
memoryStream.Write(_settings.TokenStore, 0, _settings.TokenStore.Length);
memoryStream.Seek(0, SeekOrigin.Begin);
return binaryFormatter.Deserialize(memoryStream) as Dictionary<string, T>;
}
}
private void Encode<T>(Dictionary<string, T> store)
{
using (var memoryStream = new MemoryStream())
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, store);
_settings.TokenStore = memoryStream.ToArray();
}
}
public Task ClearAsync()
{
_settings.TokenStore = null;
return Task.CompletedTask;
}
public Task DeleteAsync<T>(string key)
{
if (_settings.TokenStore == null)
{
return Task.CompletedTask; //THIS IS CALLED, RETURNS HERE
}
var store = Decode<T>();
if (store.ContainsKey(key))
{
store.Remove(key);
Encode<T>(store);
}
return Task.CompletedTask;
}
public Task<T> GetAsync<T>(string key)
{
var store = Decode<T>();
if (store.ContainsKey(key))
{
return Task.FromResult( store[key] );
}
return null;
}
public Task StoreAsync<T>(string key, T value)
{
var store = Decode<T>();
if(store != null)
{
store.Add(key, value);
Encode<T>(store);
}
return Task.CompletedTask;
}
}
Stack trace of the Google error
at Google.Apis.Auth.OAuth2.Responses.TokenResponse.<FromHttpResponseAsync>d__36.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<ExchangeCodeForTokenAsync>d__30.MoveNext()
I have included this code as it does appear the Delete was called and on returning Task.CompletedTask the error appears (on initial call the store is null so just return Task.CompletedTask
