Well, after more reading I found the reason of this exception.
https://developers.google.com/accounts/docs/OAuth2#expiration
https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtAuthorization?#helpme
Invalid Grant: The refresh token limit has been exceeded (default is 25).
That's all.
According to these documentation: There is currently a 25-token limit per Google user account. If a user account has 25 valid tokens, the next authentication request succeeds, but quietly invalidates the oldest outstanding token without any user-visible warning.
If the application attempts to use an invalidated refresh token, an invalid_grant error response is returned. The limit for each unique pair of OAuth 2.0 client and Google Analytics account is 25 refresh tokens (note that this limit is subject to change).
Understood, they limit # of refresh tokens to 25, but they don't say what to do when you need to go above that limit. Arghhh... I have been experimenting and found a solution how to bypass that limitation. It seems indeed that recycling the Application Pool solves the problem (of course untill next 25-limit is reached). We can manually recycle the AppPool from IIS or by running the command:
c:\Windows\System32\inetsrv\appcmd.exe recycle apppool /apppool.name:AppPoolName
You can schedule that command to execute every night or every hour, whatever...
But I found a have a programmatic solution:
Override OnException method for your controller (it's for MVC app)
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled) return;
// Log exception details
Global.LogException(filterContext.Exception, EventLogEntryType.Error);
if (filterContext.Exception.Message.Contains("invalid_grant"))
{
// Invalid Grant: The refresh token limit has been exceeded (default is 25).
// https://developers.google.com/accounts/docs/OAuth2#expiration
// https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtAuthorization?#helpme
Global.RecycleAppPool();
Global.LogException(new Exception("AppPool has been recycled"), EventLogEntryType.Information);
Response.Redirect("Index");
}
var actionName = filterContext.RouteData.Values["action"].ToString();
// Return friendly error message
var errorMessage = string.Format("Action {0} failed with error: {1}. Please try again.", actionName, filterContext.Exception.Message);
filterContext.Result = Content(errorMessage);
filterContext.ExceptionHandled = true;
base.OnException(filterContext);
}
Where RecycleAppPool is defined like this (this operation is fast, not like restarting IIS :):
public static void RecycleAppPool()
{
ServerManager serverManager = new ServerManager();
ApplicationPool appPool = serverManager.ApplicationPools["Homepage"];
if (appPool != null)
{
if (appPool.State == ObjectState.Stopped) appPool.Start();
else appPool.Recycle();
}
}
So, in case of invalid_grant exception, the exception "swallowed": logged, apppool is recycled and the limit for refresh tokens is reset. Hope this helps.
Please let me know if you find some issues.