While testing locally, I was able to authenticate & use the Oauth2Service
to retrieve basic information for account creation. Once it went live, the user was able to access the google consent page but once they choose an account (or entered their google credentials) it would sit on the load screen until it hit my predefined timeout of 60 seconds.
I am currently receiving the following error:
The operation has timed out.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.TimeoutException: The operation has timed out.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[TimeoutException: The operation has timed out.] System.Web.Mvc.Async.<>c__DisplayClass8.b__4() +189 System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult) +427 System.Web.Mvc.Async.<>c__DisplayClass37.b__36(IAsyncResult asyncResult) +23 System.Web.Mvc.Async.AsyncInvocationWithFilters.b__3d() +112 System.Web.Mvc.Async.<>c__DisplayClass46.b__3f() +452 System.Web.Mvc.Async.<>c__DisplayClass46.b__3f() +452 System.Web.Mvc.Async.<>c__DisplayClass46.b__3f() +452 System.Web.Mvc.Async.<>c__DisplayClass33.b__32(IAsyncResult asyncResult) +15 System.Web.Mvc.Async.<>c__DisplayClass2b.b__1c() +37 System.Web.Mvc.Async.<>c__DisplayClass21.b__1e(IAsyncResult asyncResult) +241 System.Web.Mvc.Controller.b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +29 System.Web.Mvc.Async.WrappedAsyncVoid
1.CallEndDelegate(IAsyncResult asyncResult) +111 System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +53 System.Web.Mvc.Async.WrappedAsyncVoid
1.CallEndDelegate(IAsyncResult asyncResult) +19 System.Web.Mvc.MvcHandler.b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +51 System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +111 System.Web.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar) +282
I was able to narrow down where it was hanging to this line:
var token = await Flow.ExchangeCodeForTokenAsync(UserId, authorizationCode.Code, returnUrl,
taskCancellationToken).ConfigureAwait(false);
For Context, here is the containing class:
[AuthorizationCodeActionFilter]
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected new IAuthorizationCodeFlow Flow { get { return FlowData.Flow; } }
protected new string UserId
{ get { return FlowData.GetUserId(this); } }
[AsyncTimeout(60000)]
public async override Task<ActionResult> IndexAsync(AuthorizationCodeResponseUrl authorizationCode,
CancellationToken taskCancellationToken)
{
if (string.IsNullOrWhiteSpace(authorizationCode.Code))
{
var errorResponse = new TokenErrorResponse(authorizationCode);
return OnTokenError(errorResponse);
}
var returnUrl = Request.Url.ToString();
returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?"));
var token = await Flow.ExchangeCodeForTokenAsync(UserId, authorizationCode.Code, returnUrl,
taskCancellationToken).ConfigureAwait(false);
var oauthState = await AuthWebUtility.ExtracRedirectFromState(Flow.DataStore, UserId,
authorizationCode.State).ConfigureAwait(false);
return new RedirectResult(oauthState);
}
protected override FlowMetadata FlowData
{
get { return new GoogleFlowMetaData(); }
}
protected override ActionResult OnTokenError(TokenErrorResponse errorResponse)
{
throw new TokenResponseException(errorResponse);
}
}
For additional context, here is my implementation of FlowMetaData:
public class GoogleFlowMetaData : FlowMetadata
{
public static readonly IAuthorizationCodeFlow flow =
new ForceOfflineGoogleAuthorizationCodeFLow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = ConfigurationManager.AppSettings["Google.ClientID"],
ClientSecret = ConfigurationManager.AppSettings["Google.ClientSecret"]
},
Scopes = new[] {
Scope.UserinfoProfile,
Scope.UserinfoEmail
},
DataStore = new GoogleDataStore(new DBContext())
});
public override string GetUserId(Controller controller)
{
var user = controller.User.Identity;
if (controller.User.Identity.IsAuthenticated)
{
return controller.User.Identity.GetUserId();
}
var userId = (string)controller.Session["google_userId"];
if (string.IsNullOrWhiteSpace(userId))
{
userId = Guid.NewGuid().ToString();
controller.Session["google_userId"] = userId;
}
return userId;
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
internal class ForceOfflineGoogleAuthorizationCodeFLow : GoogleAuthorizationCodeFlow
{
public ForceOfflineGoogleAuthorizationCodeFLow(GoogleAuthorizationCodeFlow.Initializer initializer)
: base(initializer)
{
}
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
{
var ss = new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl));
ss.AccessType = "offline";
ss.ApprovalPrompt = "force";
ss.ClientId = ClientSecrets.ClientId;
ss.Scope = string.Join(" ", Scopes);
ss.RedirectUri = redirectUri;
return ss;
}
}
}
Nuget packages used:
- Google.Apis
- Google.Apis.Auth
- Google.Apis.Auth.Mvc
- Google.Apis.Core
- Google.Apis.Oauth2.v2
Additional Considerations:
- Hosted on Windows Web Server 2008 (IIS 7)
- Hosted as a subdomain
- I am the verified owner of the subdomain/domain
- subdomain has SSL
- subdomain & domain have been added to the 'allowed domains' list on the Google API Manager for the project
- Redirect URL is set up for the OAuth Client ID within Google API Manager
- Verified that ClientID & ClientSecret match between API Manager & application
- Token storage in database is setup and working
- Privacy Policy is set within the OAuth Client
- Server's system clock is valid.
Any suggestions or ideas on how to fix this? Please let me know if there is any additional information that would help!