1

Does anyone know the correct setup for the cloud project and redirect URL within the application for the following case?

Setup:

  • Spring + Apache Wicket
  • The application is installed on a server (Windows, Linux*) and accessed on the intranet via browser.

*) with or without desktop

Requirements:

  • Access to one or more Gmail-Accounts to retrieve emails, mark emails as read and move emails to trash
  • Credentials are stored for each account separately on the server
  • Creation of the access is done on a client by an admin user in the browser
  • Consent for an account is done only once on creation, emails are retrieved in a background thread (no user interaction, token is refreshed automatically)
  • No additional setups on the clients (e.g. changing the host-file, running a background-process/listener); Client could also be a mobile device accessing the intranet

Scopes:

  • Non-Restricted: userinfo.email
  • Restricted: gmail.modify

Cloud projects setups/attempts:

  • Cloud project: Desktop-App; Application: AuthorizationCodeInstalledApp.authorize - Does not work - the consent screen is opened on the server if this is used

  • Cloud project: Desktop-App; Application: urn:ietf:wg:oauth:2.0:oob as redirect url and popup on the client - Worked but Google is discontinuing oob

  • Current: Cloud project: Web-App with a public redirect url; Application: redirected to our website - only to show the auth code, which can be pasted in the application open in the browser

    public String getAuthorizationUrl(String clientId, String clientSecret, String credentialPath)
    {
      final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
      final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
      final List<String> SCOPES = 
        Arrays.asList(new String[] {GmailScopes.GMAIL_MODIFY, Oauth2Scopes.USERINFO_EMAIL});
    
      Details details = new Details();
      details.setClientId(clientId);
      details.setClientSecret(clientSecret);
    
      GoogleClientSecrets clientSecrets = new GoogleClientSecrets();
      clientSecrets.setInstalled(details);
    
      // Build flow and trigger user authorization request.
      GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
              HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
              .setDataStoreFactory(new FileDataStoreFactory(new File(credentialPath)))
              .setApprovalPrompt("force")
              .setAccessType("offline")
              .build();
    
      /* approval prompt and access type were not needed for desktop-app;
       * refresh token was generated anyway, they had to be added for web-app 
       * to get a refresh token */        
    
        String redirUri = "https://example.com/redirect";
    
        AuthorizationCodeRequestUrl authorizationUrl =
                flow.newAuthorizationUrl().setRedirectUri(redirUri);
    
    
      return authorizationUrl.build();
    }
    

Google Oauth verification: Google says that according to the generated traffic, the app is running on a web server and we need to change it to a local URL, otherwise we need a security assessment because the data is stored on a web server. While it's technically true that it's running on a web server, it's an intranet server. It's not possible to define a fixed local URL since the servers IP could be different for each user that is installing the app on his server.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
ApplDev
  • 33
  • 3

1 Answers1

1

You have several issues here. The first is that you are using a desktop application to run a web app. GoogleAuthorizationCodeFlow.Builder is designed for use with installed apps desktop apps or console applications. Its not designed to be run hosted on a web server.

Follow the following example Web server applications

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(), GsonFactory.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(), GsonFactory.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
  }
}

installing app.

You have stated this

It's not possible to define a fixed local URL since the servers IP could be different for each user that is installing the app on his server.

Which implies to me that you are giving the code for this app directly to your users with out it being compiled. This includes your credeitnals.json file. YOu may not do this this is against the TOS. Can I really not ship open source with Client ID?

Asking developers to make reasonable efforts to keep their private keys private and not embed them in open source projects.

You should be instructing your users in how to create their own client id and client secrete. in order to get their own creditnals.json file. They can then supply their own ip address of their server.

In which case your issue with verification is no longer an issue. You dont need to verfy for them. They should be doing that themselves.

push back on internal app

When your users go to verification their app make sure that they are clear with Google that this is an internal app. Hosted on their intranet. They should not need verification.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • There are no pre-configured credentials/accounts when installing it, they are added by the user( s ) for their respective Gmail accounts (or not, depending on their needs). The users can download our application and install it. The Google project is for our application and has a fixed clientid. An example would be Thunderbird (you can add Gmail access but you don't have to) with the difference that Thunderbird is installed/used only on one device and our application on a server in the intranet. – ApplDev Apr 20 '22 at 10:26
  • 1
    Unless the clientid and client secrete are compiled into your app. You cant give it to them. If its clear text when installed you cant give it to them. If its on a web server you need to use web server code. If its on a web server the redirect uri needs to be configured per use by your customer in their own project. The method you are using now used to work because of oob that is being removed Google doesnt want you to be doing it like this. – Linda Lawton - DaImTo Apr 20 '22 at 10:37