0

I'm trying to connect to the Google Calendar API. I am have followed the step in Google calendar quick start for Java

{ "error" : "invalid_grant", "error_description" : "Bad Request" }

Can you please advise on how to debug this? The error message is unfortunately not helpful and I already tried everything I could find about this particular error on Stack overflow or elsewhere

Every time I got the same access token for different credentials:

Access token: {user=Class{accessToken=null, refreshToken="" expirationTimeMilliseconds=null}}

code:

public class CalendarServiceImpl implements CalendarService {

    public static final String APPLICATION_NAME = "GoogleCalenderApi";
    public static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    public static final String TOKENS_DIRECTORY_PATH = "/data.json";
    public static final List<String> SCOPES = Collections.singletonList(CalendarScopes.CALENDAR);
    public static final String CREDENTIALS_FILE_PATH = "/data.json";

    public Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT, HttpServletRequest request)
            throws IOException {

        InputStream in = CalendarServiceImpl.class.getResourceAsStream(CREDENTIALS_FILE_PATH);

        if (in == null) {
            log.info("Resource not found: " + CREDENTIALS_FILE_PATH);
            throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
        }

        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
                clientSecrets, SCOPES)
                        .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
                        .setAccessType("offline")
                        .build();

        System.out.println("Access token: " + flow.getCredentialDataStore());

        LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(80)
                .build();


        return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    }

    public void createCalendarEvent(String candidateMailId, String companyEmailId, DateTime fromTime, DateTime toTime,
            String summary, String description, HttpServletRequest request)
            throws GeneralSecurityException, IOException {
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();

        Event event = new Event().setSummary(summary).setLocation("Test").setDescription(description);

        EventDateTime start = new EventDateTime().setDateTime(fromTime).setTimeZone("Asia/Kolkata");
        event.setStart(start);

        EventDateTime end = new EventDateTime().setDateTime(toTime).setTimeZone("Asia/Kolkata");
        event.setEnd(end);

        String[] recurrence = new String[] { "RRULE:FREQ=DAILY" };
        event.setRecurrence(Arrays.asList(recurrence));

        EventAttendee[] attendees = new EventAttendee[] { new EventAttendee().setEmail(candidateMailId),
                new EventAttendee().setEmail(companyEmailId) };
        event.setAttendees(Arrays.asList(attendees));

        EventReminder[] reminderOverrides = new EventReminder[] { new EventReminder().setMethod("email").setMinutes(10),
                new EventReminder().setMethod("popup").setMinutes(10), };
        Event.Reminders reminders = new Event.Reminders().setUseDefault(false)
                .setOverrides(Arrays.asList(reminderOverrides));
        event.setReminders(reminders);


        // Build service account credential.
        Credential credential = getCredentials(HTTP_TRANSPORT, request);
        log.info("got  credential:" + event);

        Calendar service = new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
                .setApplicationName(APPLICATION_NAME).build();


        String calendarId = "primary";
        try {
            System.out.printf("Event started" + event);
            event = service.events().insert(calendarId, event).setSendUpdates("all").execute();
        } catch (IOException e) {
            log.info("event  IOException:" + e);
            e.getMessage();
        }
        log.info("Event created:" + event.getHtmlLink());

    }

}
  • 2
    Please edit your question and include your code, are you using Java or Curl your question contains both. – Linda Lawton - DaImTo Jan 05 '21 at 07:00
  • 1
    You are not following the Java quickstart if you are trying `manual HTTP POST request using CURL`. Are you providing an access token to your request? – Iamblichus Jan 05 '21 at 10:12
  • @Iamblichus :I am giving accesstoken link to request : "token_uri": "https://oauth2.googleapis.com/token", – khushbu shah Jan 07 '21 at 11:29
  • 1
    Please edit your question and include your code, we cant help you without seeing your code. – Linda Lawton - DaImTo Jan 07 '21 at 11:30
  • @DaImTo i am using java. – khushbu shah Jan 07 '21 at 11:32
  • @DaImTo please check the updated question. – khushbu shah Jan 07 '21 at 11:33
  • I recommend you delete your last comment. Its against TOS to post your client secrets publicly. – Linda Lawton - DaImTo Jan 07 '21 at 11:39
  • I recommend you delete that comment to you just posted a refresh token that will give everyone access to your account. Do you want to get hacked? – Linda Lawton - DaImTo Jan 07 '21 at 11:40
  • Access tokens are only valid for one hour i would expect the access token to change every time the user authenticates your application. – Linda Lawton - DaImTo Jan 07 '21 at 11:42
  • @DaImTo how to change every time because i directly give the ACCEES token ,.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) LINK:https://oauth2.googleapis.com/token – khushbu shah Jan 07 '21 at 11:45
  • The library will handle that all for you. It is has a refresh token which means that automatically it will request a new access token if it needs one. – Linda Lawton - DaImTo Jan 07 '21 at 11:46
  • @DaImTo when I give new credential for client id& client secret,still same problem Every time I got the same access token for different credentials: Access token: {user=Class{accessToken=null, refreshToken="" expirationTimeMilliseconds=null}} – khushbu shah Jan 07 '21 at 11:48
  • credential means the client id and client secret. You should only have one of these for your application. Run your app for "user" and it will store the credeitals for that user. change "user" to something else say "user2" and run it again and this time login with a different user. now switch user back to "user" and you should have access to that users data. – Linda Lawton - DaImTo Jan 07 '21 at 12:10
  • You have a refresh token The system will automaticly request a new access token as it needs for each "user" or "user2" depending upon which one is logging in. If the system does not have a token for a new user say "user3" it will request access of that user. – Linda Lawton - DaImTo Jan 07 '21 at 12:10
  • Im not sure i understand what your issue is. You will have the same access token as long as it has not expired which takes an hour. Once it has expired the system will request a new one. – Linda Lawton - DaImTo Jan 07 '21 at 12:11
  • @DaImTo same refreshtoken for every user, accesstoken is null – khushbu shah Jan 07 '21 at 12:27
  • if you are changing "user" in your code and logging in with a different user then there is no way the refresh token is the same for every user. – Linda Lawton - DaImTo Jan 07 '21 at 12:45
  • @DaImTo ok, with changing user ->user1->user 2 ,its working . – khushbu shah Jan 08 '21 at 05:39
  • @DaImTo when i deployed these code on domain server ,its showing error: ex: LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost("https://test.shared.in") .setPort(80).build(); "IOException found: Address already in use: bind" – khushbu shah Jan 25 '21 at 09:54
  • AuthorizationCodeInstalledApp <--- this is code for an installed application, you understand this is not going to work hosted as a website correct? – Linda Lawton - DaImTo Jan 25 '21 at 10:05
  • @DaImTo i want to use this in hosted website, how can i use this? – khushbu shah Jan 25 '21 at 10:12
  • @khushbushah you cant you need to use AuthorizationCodeFlow for web application not AuthorizationCodeInstalledApp your code wont work. – Linda Lawton - DaImTo Jan 25 '21 at 10:27
  • @DaImTo still not getting this: .setDataStoreFactory( DATA_STORE_FACTORY), how to pass token url? GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), "clietnid", "clietnsecret", SCOPES).setDataStoreFactory(new FileDataStoreFactory(file)).setAccessType("online") .setApprovalPrompt("auto").build(); – khushbu shah Feb 08 '21 at 06:55
  • You are over thinking this. The library handles all that for you and if you want to know how your going to have to dig in the source code for the [library](https://github.com/googleapis/google-api-java-client/blob/91401e905ba72e513918844e41dd8a3159d33b2c/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleAuthorizationCodeFlow.java) – Linda Lawton - DaImTo Feb 08 '21 at 09:15
  • @DaImTo i mean here compalsory to pass toke.json file to get token! .setDataStoreFactory(new FileDataStoreFactory(file))" – khushbu shah Feb 08 '21 at 10:24
  • FileDataStoreFactory actually takes a directory name your variable names are misleading pass it DATA_STORE_DIR its the directory where the user credentials will be stored after they have authorized your applicaiton. – Linda Lawton - DaImTo Feb 08 '21 at 10:31
  • @DaImTo anything wrong in this? GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), "client-id", "client-secret", SCOPES).setDataStoreFactory(new FileDataStoreFactory("tokenfile")).setAccessType("online") .setApprovalPrompt("auto").build(); so we have to pass empty file or credential file? – khushbu shah Feb 08 '21 at 10:40
  • You are using JacksonFactory to send the credentials for your application, what empty file are you talking about.? "tokenfile" is the name of the directory where the users tokens will be stored after they have consented to your application. Its not a file its a directory. – Linda Lawton - DaImTo Feb 08 '21 at 10:55
  • Please consider reading [FileDatastore demystified](https://www.daimto.com/google-net-filedatastore-demystified/) Yes i know its .net but trust me when i say that its works exactly the same for Java client library. – Linda Lawton - DaImTo Feb 08 '21 at 10:57
  • @DaImTo hi, can you help me please, my local calendar api not working not a server calendar api, AuthorizationCodeInstalledApp: i got null response from : GoogleAuthorizationCodeFlow so final response : java.net.BindException: Address already in use: bind – khushbu shah Feb 24 '21 at 07:51
  • @khushbushah try this https://stackoverflow.com/q/12737293/1841839 – Linda Lawton - DaImTo Feb 24 '21 at 08:14
  • @DaImTo solve this : java.net.BindException: Address already in use: bind, but event not created,even no error found.GoogleAuthorizationCodeFlow response null – khushbu shah Feb 24 '21 at 08:33

1 Answers1

0

You appear to be using AuthorizationCodeInstalledApp which as it stats is for installed applications. for web applications you need to use AuthorizationCodeFlow.

The official example can be found here Web server application

public class CalendarServletSample extends AbstractAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), JacksonFactory.getDefaultInstance(),
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class CalendarServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), JacksonFactory.getDefaultInstance()
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

AuthorizationCodeFlow opens the consent browser window in the machine that the code is running on. When you are running something as a website it needs to open on the client machine so that the user can see it.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449