How Google API V 3.0 .Net library and Google OAuth2 Handling refresh token

后端 未结 3 1618
迷失自我
迷失自我 2020-11-29 05:32

In my application I am using Google API V 3.0 .Net library with Google OAuth2 to synchronize Google calender and outlook calender. I am using below code to get the Google.Ap

相关标签:
3条回答
  • 2020-11-29 05:59

    For me, client library did not do the refreshing, not even creating the refresh token.
    I check whether the token is expired and refresh. (Token has a 1hour life time). credential.Token.IsExpired will tell you whether it is expired, credential.Token.RefreshToken(userid) will refresh the necessary token.

    0 讨论(0)
  • 2020-11-29 06:03

    Spent the last two days figuring this out myself. The library does not refresh the tokens automatically unless you specify "access_type=offline".

    https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth

    I'm gonna paste the code I'm using and if there is anything you don't understand, just ask. I have read so many posts and I litteraly got this working right now so there is some commented code and it has not been refactored yet. I hope this will help someone. The NuGet packages I'm using are these:

    Google.Apis.Auth.MVC

    Google.Apis.Calendar.v3

    Code:

    AuthCallbackController:

    [AuthorizationCodeActionFilter]
    public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
    {
    protected static readonly ILogger Logger = ApplicationContext.Logger.ForType<AuthCallbackController>();
    
    /// <summary>Gets the authorization code flow.</summary>
    protected IAuthorizationCodeFlow Flow { get { return FlowData.Flow; } }
    
    /// <summary>
    /// Gets the user identifier. Potential logic is to use session variables to retrieve that information.
    /// </summary>
    protected string UserId { get { return FlowData.GetUserId(this); } }
    
    /// <summary>
    /// The authorization callback which receives an authorization code which contains an error or a code.
    /// If a code is available the method exchange the coed with an access token and redirect back to the original
    /// page which initialized the auth process (using the state parameter).
    /// <para>
    /// The current timeout is set to 10 seconds. You can change the default behavior by setting 
    /// <see cref="System.Web.Mvc.AsyncTimeoutAttribute"/> with a different value on your controller.
    /// </para>
    /// </summary>
    /// <param name="authorizationCode">Authorization code response which contains the code or an error.</param>
    /// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
    /// <returns>
    /// Redirect action to the state parameter or <see cref="OnTokenError"/> in case of an error.
    /// </returns>
    [AsyncTimeout(60000)]
    public async override Task<ActionResult> IndexAsync(AuthorizationCodeResponseUrl authorizationCode,
       CancellationToken taskCancellationToken)
    {
        if (string.IsNullOrEmpty(authorizationCode.Code))
        {
            var errorResponse = new TokenErrorResponse(authorizationCode);
            Logger.Info("Received an error. The response is: {0}", errorResponse);
            Debug.WriteLine("Received an error. The response is: {0}", errorResponse);
            return OnTokenError(errorResponse);
        }
    
        Logger.Debug("Received \"{0}\" code", authorizationCode.Code);
        Debug.WriteLine("Received \"{0}\" code", authorizationCode.Code);
    
    
        var returnUrl = Request.Url.ToString();
        returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?"));
    
        var token = await Flow.ExchangeCodeForTokenAsync(UserId, authorizationCode.Code, returnUrl,
            taskCancellationToken).ConfigureAwait(false);
    
        // Extract the right state.
        var oauthState = await AuthWebUtility.ExtracRedirectFromState(Flow.DataStore, UserId,
            authorizationCode.State).ConfigureAwait(false);
    
        return new RedirectResult(oauthState);
    }
    
    protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
    {
        get { return new AppFlowMetadata(); }
    }
    
    protected override ActionResult OnTokenError(TokenErrorResponse errorResponse)
    {
        throw new TokenResponseException(errorResponse);
    }
    
    
    //public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
    //{
    //    protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
    //    {
    //        get { return new AppFlowMetadata(); }
    //    }
    //}
    

    }

    Method for Controller calling Google API

        public async Task<ActionResult> GoogleCalendarAsync(CancellationToken cancellationToken)
        {
            var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
                AuthorizeAsync(cancellationToken);
    
            if (result.Credential != null)
            {
                //var ttt = await result.Credential.RevokeTokenAsync(cancellationToken);
    
                //bool x = await result.Credential.RefreshTokenAsync(cancellationToken);
    
                var service = new CalendarService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = result.Credential,
                    ApplicationName = "GoogleApplication",
                });
                var t = service.Calendars;
    
                var tt = service.CalendarList.List();
    
                // Define parameters of request.
                EventsResource.ListRequest request = service.Events.List("primary");
                request.TimeMin = DateTime.Now;
                request.ShowDeleted = false;
                request.SingleEvents = true;
                request.MaxResults = 10;
                request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
    
                // List events.
                Events events = request.Execute();
                Debug.WriteLine("Upcoming events:");
                if (events.Items != null && events.Items.Count > 0)
                {
                    foreach (var eventItem in events.Items)
                    {
                        string when = eventItem.Start.DateTime.ToString();
                        if (String.IsNullOrEmpty(when))
                        {
                            when = eventItem.Start.Date;
                        }
                        Debug.WriteLine("{0} ({1})", eventItem.Summary, when);
                    }
                }
                else
                {
                    Debug.WriteLine("No upcoming events found.");
                }
    
    
                //Event myEvent = new Event
                //{
                //    Summary = "Appointment",
                //    Location = "Somewhere",
                //    Start = new EventDateTime()
                //        {
                //            DateTime = new DateTime(2014, 6, 2, 10, 0, 0),
                //            TimeZone = "America/Los_Angeles"
                //        },
                //    End = new EventDateTime()
                //        {
                //            DateTime = new DateTime(2014, 6, 2, 10, 30, 0),
                //            TimeZone = "America/Los_Angeles"
                //        },
                //    Recurrence = new String[] {
                //        "RRULE:FREQ=WEEKLY;BYDAY=MO"
                //        },
                //    Attendees = new List<EventAttendee>()
                //        {
                //        new EventAttendee() { Email = "johndoe@gmail.com" }
                //        }
                //};
    
                //Event recurringEvent = service.Events.Insert(myEvent, "primary").Execute();
    
                return View();
            }
            else
            {
                return new RedirectResult(result.RedirectUri);
            }
        }
    

    Derived class of FlowMetadata

    public class AppFlowMetadata : FlowMetadata
        {
            //static readonly string server = ConfigurationManager.AppSettings["DatabaseServer"];
            //static readonly string serverUser = ConfigurationManager.AppSettings["DatabaseUser"];
            //static readonly string serverPassword = ConfigurationManager.AppSettings["DatabaseUserPassword"];
            //static readonly string serverDatabase = ConfigurationManager.AppSettings["DatabaseName"];
        ////new FileDataStore("Daimto.GoogleCalendar.Auth.Store")
        ////new FileDataStore("Drive.Api.Auth.Store")
        //static DatabaseDataStore databaseDataStore = new DatabaseDataStore(server, serverUser, serverPassword, serverDatabase);
    
    
        private static readonly IAuthorizationCodeFlow flow =
    new ForceOfflineGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = new ClientSecrets
        {
            ClientId = "yourClientId",
            ClientSecret = "yourClientSecret"
    
        },
        Scopes = new[]
        {
    CalendarService.Scope.Calendar, // Manage your calendars
    //CalendarService.Scope.CalendarReadonly // View your Calendars
        },
        DataStore = new EFDataStore(),
    });
    
        public override string GetUserId(Controller controller)
        {
            // In this sample we use the session to store the user identifiers.
            // That's not the best practice, because you should have a logic to identify
            // a user. You might want to use "OpenID Connect".
            // You can read more about the protocol in the following link:
            // https://developers.google.com/accounts/docs/OAuth2Login.
    
            //var user = controller.Session["user"];
            //if (user == null)
            //{
            //    user = Guid.NewGuid();
            //    controller.Session["user"] = user;
            //}
            //return user.ToString();
    
            //var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
            //var manager = new UserManager<ApplicationUser>(store);
            //var currentUser = manager.FindById(controller.User.Identity.GetUserId());
    
            return controller.User.Identity.GetUserId();
    
        }
    
        public override IAuthorizationCodeFlow Flow
        {
            get { return flow; }
        }
    
        public override string AuthCallback
        {
            get { return @"/GoogleApplication/AuthCallback/IndexAsync"; }
        }
    }
    

    Entity framework 6 DataStore class

     public class EFDataStore : IDataStore
        {
            public async Task ClearAsync()
            {
                using (var context = new ApplicationDbContext())
                {
                    var objectContext = ((IObjectContextAdapter)context).ObjectContext;
                    await objectContext.ExecuteStoreCommandAsync("TRUNCATE TABLE [Items]");
                }
            }
    
            public async Task DeleteAsync<T>(string key)
            {
                if (string.IsNullOrEmpty(key))
                {
                    throw new ArgumentException("Key MUST have a value");
                }
    
            using (var context = new ApplicationDbContext())
            {
                var generatedKey = GenerateStoredKey(key, typeof(T));
                var item = context.GoogleAuthItems.FirstOrDefault(x => x.Key == generatedKey);
                if (item != null)
                {
                    context.GoogleAuthItems.Remove(item);
                    await context.SaveChangesAsync();
                }
            }
        }
    
        public Task<T> GetAsync<T>(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key MUST have a value");
            }
    
            using (var context = new ApplicationDbContext())
            {
                var generatedKey = GenerateStoredKey(key, typeof(T));
                var item = context.GoogleAuthItems.FirstOrDefault(x => x.Key == generatedKey);
                T value = item == null ? default(T) : JsonConvert.DeserializeObject<T>(item.Value);
                return Task.FromResult<T>(value);
            }
        }
    
        public async Task StoreAsync<T>(string key, T value)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key MUST have a value");
            }
    
            using (var context = new ApplicationDbContext())
            {
                var generatedKey = GenerateStoredKey(key, typeof(T));
                string json = JsonConvert.SerializeObject(value);
    
                var item = await context.GoogleAuthItems.SingleOrDefaultAsync(x => x.Key == generatedKey);
    
                if (item == null)
                {
                    context.GoogleAuthItems.Add(new GoogleAuthItem { Key = generatedKey, Value = json });
                }
                else
                {
                    item.Value = json;
                }
    
                await context.SaveChangesAsync();
            }
        }
    
        private static string GenerateStoredKey(string key, Type t)
        {
            return string.Format("{0}-{1}", t.FullName, key);
        }
    }
    

    Derived class for GoogleAuthorizationCodeFlow. Enabling long-lived refresh token that take care of automatically "refreshing" the token, which simply means getting a new access token.

    https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth

    internal class ForceOfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
    {
        public ForceOfflineGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base (initializer) { }
    
        public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
        {
            return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
            {
                ClientId = ClientSecrets.ClientId,
                Scope = string.Join(" ", Scopes),
                RedirectUri = redirectUri,
                AccessType = "offline",
                ApprovalPrompt = "force"
            };
        }
    }
    

    GoogleAuthItem is used with EFDataStore

    public class GoogleAuthItem
    {
        [Key]
        [MaxLength(100)]
        public string Key { get; set; }
    
        [MaxLength(500)]
        public string Value { get; set; }
    }
    
    public DbSet<GoogleAuthItem> GoogleAuthItems { get; set; }
    
    0 讨论(0)
  • 2020-11-29 06:19

    That's the butty of the client library! this magic is done for you automatically :)

    UserCredential implements both IHttpExecuteInterceptor and IHttpUnsuccessfulResponseHandler. so whenever the access token is going to be expired, or is already expired, the client makes a call to the authorization server to refresh the token and get a new access token (which is valid for the next 60 minutes).

    Read more about it at https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#credentials

    0 讨论(0)
提交回复
热议问题