Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Calendar returning invalid grant

I have a customer that is trying to access their calendars from our web application. Everything works for all of our other customers, so I am not sure what is different here except this customer is in Australia and using a non gmail.com email address.

The customer is able to authorize our application and we do get a oauth token for the user. We request calendar access and the customer granted it. When we request a list of all of the calendars, we get the invalid grant message.

Below is the code that we use to access their calendars. The method being called is GetAllWritableCalendars.

public class GoogleCalendarAdapter : ICalendarAdapter {
    #region attributes
    private readonly ISiteAuthTokenQueryRepository _tokenRepo;
    private readonly GoogleCalendarSettings        _settings;

    private const string APPNAME = "SomeAppName";

    private const string ACL_OWNER = "owner";
    private const string ACL_WRITER = "writer";
    #endregion

    #region ctor
    public GoogleCalendarAdapter(ISiteAuthTokenQueryRepository tokenRepo,
                                 GoogleCalendarSettings        settings) {
        _tokenRepo = tokenRepo;
        _settings  = settings;
    }
    #endregion

    #region methods
    private GoogleAuthorizationCodeFlow BuildAuthorizationCodeFlow() {
        return new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer() {
            ClientSecrets = BuildClientSecrets(),
            Scopes        = BuildScopeList()
        });
    }

    private CalendarService BuildCalendarService(SiteAuthToken token) {

        return new CalendarService(new BaseClientService.Initializer() {
                ApplicationName       = APPNAME,
                HttpClientInitializer = BuildUserCredential(token)
        });
    }

    private ClientSecrets BuildClientSecrets() {
        return new ClientSecrets() {
            ClientId = _settings.ClientId,
            ClientSecret = _settings.ClientSecret
        };
    }

    private string[] BuildScopeList() {
        return new [] { CalendarService.Scope.Calendar };
    }

    private UserCredential BuildUserCredential(SiteAuthToken token) {
        TokenResponse responseToken = new TokenResponse() {
            AccessToken  = token.AccessToken,
            RefreshToken = token.RefreshToken
        };

        return new UserCredential(BuildAuthorizationCodeFlow(), APPNAME, responseToken);
    }

    public async Task<List<Cal>> GetAllWritableCalendars(Guid siteGuid) {
        SiteAuthToken token = await GetToken(siteGuid);
        CalendarService svc = BuildCalendarService(token);

        IList<CalendarListEntry> calendars = svc.CalendarList
                                                .List()
                                                .Execute()
                                                .Items;

        return calendars.Where(c => c.AccessRole.Equals(ACL_OWNER,  StringComparison.CurrentCultureIgnoreCase) ||
                                    c.AccessRole.Equals(ACL_WRITER, StringComparison.CurrentCultureIgnoreCase))
                        .Select(c => new Cal() {
                            Id   = c.Id,
                            Name = c.Summary
                        })
                        .OrderBy(o => o.Name)
                        .ToList();
    }

    private async Task<SiteAuthToken> GetToken(Guid siteGuid) {
        SiteAuthToken retVal = await _tokenRepo.GetSiteAuthToken(siteGuid);

        if (retVal == null) {
            throw new ApplicationException($"Could not find a SiteAuthToken for specified site (SiteGuid: {siteGuid})");
        }

        return retVal;
    }

    #endregion
like image 340
fizch Avatar asked Dec 10 '19 17:12

fizch


1 Answers

The credentials are the authorization from Google to Your Application to use the scopes you have set-up, this is okay to have it in a database if you update it every time you add new scopes to your app.

The Access Token is the authorization from the user to your application to get it's Google Data (calendar in this case). It has a limited lifetime so this is not okay to save in a database.

The Refresh Token is the token that allows your application to get more tokens for a client. It has a limited lifetime as well.

For more info see: Using OAuth 2.0 to Access Google APIs

Every time you change your scopes or add more scopes you have to re-generate the credentials. You have 50 refresh tokens per user account per client, see Token expiration. So having the tokens in a database makes no sense since they are going to get deprecated at some point, if you have 51 clients the 1st token will get deprecated.

Check:

  1. How do you have it set-up on your database
  2. If you renew properly the tokens
  3. If you are using the correct tokens for the users

You can delete all the tokens (NOT the CREDENTIALS) and your current users will only have to go through the consent screen and allow it again, they will not lose the connection.

like image 116
Kessy Avatar answered Oct 29 '22 05:10

Kessy