Google Ads API is returning to beta status. Please read our blog post for more details.

Authenticate in Web Application

Java
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.googleads.examples.authentication;

import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.lib.GoogleAdsClient.Builder.ConfigPropertyKey;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.util.Key;
import com.google.auth.oauth2.ClientId;
import com.google.auth.oauth2.UserAuthorizer;
import com.google.auth.oauth2.UserCredentials;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Creates an OAuth2 refresh token for the Google Ads API using the Web application
 * flow.
 *
 * <p>This example will start a basic server that listens for requests at {@code
 * http://localhost:PORT}, where {@code PORT} is dynamically assigned.
 *
 * <p>IMPORTANT: You must add {@code http://localhost/oauth2callback} to the "Authorize redirect
 * URIs" list in your Google Cloud Console project before running this example.
 */
public class AuthenticateInWebApplication {

  // Scopes for the generated OAuth2 credentials. The list here only contains the AdWords scope,
  // but you can add multiple scopes if you want to use the credentials for other Google APIs.
  private static final ImmutableList<String> SCOPES =
      ImmutableList.<String>builder().add("https://www.googleapis.com/auth/adwords").build();
  private static final String OAUTH2_CALLBACK = "/oauth2callback";

  public static void main(String[] args) throws Exception {
    // To fill in the values below, generate a client ID and client secret from the Google Cloud
    // Console (https://console.cloud.google.com) by creating credentials for a Web application.
    // Set the "Authorized redirect URIs" to:
    //   http://localhost/oauth2callback
    String clientId;
    String clientSecret;
    String loginEmailAddressHint;

    Console console = System.console();
    if (console == null) {
      // The console will be null when running this example in some IDEs. In this case, please
      // set the clientId and clientSecret in the lines below.
      clientId = "INSERT_CLIENT_ID_HERE";
      clientSecret = "INSERT_CLIENT_SECRET_HERE";
      // Optional: If your application knows which user is trying to authenticate, you can set this
      // to the user's email address so that the Google Authentication Server will automatically
      // populate the account selection prompt with that address.
      loginEmailAddressHint = null;
      // Ensures that the client ID and client secret are not the "INSERT_..._HERE" values.
      Preconditions.checkArgument(
          !clientId.matches("INSERT_.*_HERE"),
          "Client ID is invalid. Please update the example and try again.");
      Preconditions.checkArgument(
          !clientSecret.matches("INSERT_.*_HERE"),
          "Client secret is invalid. Please update the example and try again.");
    } else {
      console.printf(
          "NOTE: When prompting for the client secret below, echoing will be disabled%n");
      console.printf("      since the client secret is sensitive information.%n");
      console.printf("Enter your client ID:%n");
      clientId = console.readLine();
      console.printf("Enter your client secret:%n");
      clientSecret = String.valueOf(console.readPassword());
      console.printf("(Optional) Enter the login email address hint:%n");
      loginEmailAddressHint = Strings.emptyToNull(console.readLine());
    }

    new AuthenticateInWebApplication().runExample(clientId, clientSecret, loginEmailAddressHint);
  }

  public void runExample(String clientId, String clientSecret, String loginEmailAddressHint)
      throws Exception {
    // Creates an anti-forgery state token as described here:
    // https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
    String state = new BigInteger(130, new SecureRandom()).toString(32);

    // Creates an HTTP server that will listen for the OAuth2 callback request.
    URI baseUri;
    UserAuthorizer userAuthorizer;
    AuthorizationResponse authorizationResponse = null;
    try (SimpleCallbackServer simpleCallbackServer = new SimpleCallbackServer()) {
      userAuthorizer =
          UserAuthorizer.newBuilder()
              .setClientId(ClientId.of(clientId, clientSecret))
              .setScopes(SCOPES)
              .setCallbackUri(URI.create(OAUTH2_CALLBACK))
              .build();
      baseUri = URI.create("http://localhost:" + simpleCallbackServer.getLocalPort());
      System.out.printf(
          "Paste this url in your browser:%n%s%n",
          userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri));

      // Waits for the authorization code.
      simpleCallbackServer.accept();
      authorizationResponse = simpleCallbackServer.authorizationResponse;
    }

    if (authorizationResponse == null || authorizationResponse.code == null) {
      throw new NullPointerException(
          "OAuth2 callback did not contain an authorization code: " + authorizationResponse);
    }

    // Confirms that the state in the response matches the state token used to generate the
    // authorization URL.
    if (!state.equals(authorizationResponse.state)) {
      throw new IllegalStateException("State does not match expected state");
    }

    // Exchanges the authorization code for credentials and print the refresh token.
    UserCredentials userCredentials =
        userAuthorizer.getCredentialsFromCode(authorizationResponse.code, baseUri);
    System.out.printf("Your refresh token is: %s%n", userCredentials.getRefreshToken());

    // Prints the configuration file contents.
    Properties adsProperties = new Properties();
    adsProperties.put(ConfigPropertyKey.CLIENT_ID.getPropertyKey(), clientId);
    adsProperties.put(ConfigPropertyKey.CLIENT_SECRET.getPropertyKey(), clientSecret);
    adsProperties.put(
        ConfigPropertyKey.REFRESH_TOKEN.getPropertyKey(), userCredentials.getRefreshToken());
    adsProperties.put(
        ConfigPropertyKey.DEVELOPER_TOKEN.getPropertyKey(), "INSERT_DEVELOPER_TOKEN_HERE");

    showConfigurationFile(adsProperties);
  }

  private void showConfigurationFile(Properties adsProperties) throws IOException {
    System.out.printf(
        "Copy the text below into a file named %s in your home directory, and replace "
            + "INSERT_XXX_HERE with your configuration:%n",
        GoogleAdsClient.Builder.DEFAULT_PROPERTIES_CONFIG_FILE_NAME);
    System.out.println(
        "######################## Configuration file start ########################");
    adsProperties.store(System.out, null);
    System.out.printf(
        "# Required for manager accounts only: Specify the login customer ID used to%n"
            + "# authenticate API calls. This will be the customer ID of the authenticated%n"
            + "# manager account. You can also specify this later in code if your application%n"
            + "# uses multiple manager account + OAuth pairs.%n"
            + "#%n");
    System.out.println(
        "# " + ConfigPropertyKey.LOGIN_CUSTOMER_ID.getPropertyKey() + "=INSERT_LOGIN_CUSTOMER_ID");
    System.out.println(
        "######################## Configuration file end ##########################");
  }

  /** Basic server that listens for the OAuth2 callback from the Web application flow. */
  private static class SimpleCallbackServer extends ServerSocket {

    private AuthorizationResponse authorizationResponse;

    SimpleCallbackServer() throws IOException {
      // Passes a port # of zero so that a port will be automatically allocated.
      super(0);
    }

    /**
     * Blocks until a connection is made to this server. After this method completes, the
     * authorizationResponse of this server will be set, provided the request line is in the
     * expected format.
     */
    @Override
    public Socket accept() throws IOException {
      Socket socket = super.accept();

      try (BufferedReader in =
          new BufferedReader(
              new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
        String callbackRequest = in.readLine();
        // Uses a regular expression to extract the request line from the first line of the
        // callback request, e.g.:
        //   GET /?code=AUTH_CODE&state=XYZ&scope=https://www.googleapis.com/auth/adwords HTTP/1.1
        Pattern pattern = Pattern.compile("GET +([^ ]+)");
        Matcher matcher = pattern.matcher(Strings.nullToEmpty(callbackRequest));
        if (matcher.find()) {
          String relativeUrl = matcher.group(1);
          authorizationResponse = new AuthorizationResponse("http://localhost" + relativeUrl);
        }
        try (Writer outputWriter = new OutputStreamWriter(socket.getOutputStream())) {
          outputWriter.append("HTTP/1.1 ");
          outputWriter.append(Integer.toString(HttpStatusCodes.STATUS_CODE_OK));
          outputWriter.append(" OK\n");
          outputWriter.append("Content-Type: text/html\n\n");

          outputWriter.append("<b>");
          if (authorizationResponse.code != null) {
            outputWriter.append("Authorization code was successfully retrieved.");
          } else {
            outputWriter.append("Failed to retrieve authorization code.");
          }
          outputWriter.append("</b>");
          outputWriter.append("<p>Please check the console output from <code>");
          outputWriter.append(AuthenticateInWebApplication.class.getSimpleName());
          outputWriter.append("</code> for further instructions.");
        }
      }
      return socket;
    }
  }

  /** Response object with attributes corresponding to OAuth2 callback parameters. */
  static class AuthorizationResponse extends GenericUrl {

    /** The authorization code to exchange for an access token and (optionally) a refresh token. */
    @Key String code;

    /** Error from the request or from the processing of the request. */
    @Key String error;

    /** State parameter from the callback request. */
    @Key String state;

    /**
     * Constructs a new instance based on an absolute URL. All fields annotated with the {@link Key}
     * annotation will be set if they are present in the URL.
     *
     * @param encodedUrl absolute URL with query parameters.
     */
    public AuthorizationResponse(String encodedUrl) {
      super(encodedUrl);
    }

    @Override
    public String toString() {
      return MoreObjects.toStringHelper(getClass())
          .add("code", code)
          .add("error", error)
          .add("state", state)
          .toString();
    }
  }
}
C#
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;

namespace Google.Ads.GoogleAds.Examples
{
    public partial class Login : System.Web.UI.Page
    {
        /// <summary>
        /// The login helper.
        /// </summary>
        private WebLoginHelper loginHelper;

        /// <summary>
        /// Initializes a new instance of the <see cref="Login"/> class.
        /// </summary>
        public Login()
        {
            loginHelper = new WebLoginHelper(this);
        }

        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            // Initialize login helper only in the page load, otherwise session information
            // won't be available.

            if (loginHelper.IsLoggedIn)
            {
                // Redirect to the main page.
                Response.Redirect("/Default.aspx");
            }
            else if (loginHelper.IsCallbackFromOAuthServer())
            {
                loginHelper.ExchangeAuthorizationCodeForCredentials();

                // Redirect to the main page.
                Response.Redirect("/Default.aspx");
            }
            else
            {
                // Redirect the user to the OAuth2 login page.
                loginHelper.RedirectUsertoOAuthServer();
            }
        }
    }
}
PHP
<?php
/*
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Google\AdsApi\Examples\Authentication;

require __DIR__ . '/../../vendor/autoload.php';

use Google\Auth\CredentialsLoader;
use Google\Auth\OAuth2;
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Factory;
use React\Http\Response;
use React\Http\Server;
use UnexpectedValueException;

/**
 * This example will create an OAuth2 refresh token for the Google Ads API using the Web application
 * flow.
 *
 * <p>This example will start a basic server that listens for requests at `http://localhost:PORT`,
 * where `PORT` is dynamically assigned.
 *
 * <p>IMPORTANT: You must add `http://localhost/oauth2callback` to the "Authorize redirect
 * URIs" list in your Google Cloud Console project before running this example.
 */
class AuthenticateInWebApplication
{

    /**
     * @var string the OAuth2 scope for the Google Ads API
     * @see https://developers.google.com/google-ads/api/docs/oauth/internals#scope
     */
    const SCOPE = 'https://www.googleapis.com/auth/adwords';

    /**
     * @var string the Google OAuth2 authorization URI for OAuth2 requests
     * @see https://developers.google.com/identity/protocols/OAuth2InstalledApp#step-2-send-a-request-to-googles-oauth-20-server
     */
    const AUTHORIZATION_URI = 'https://accounts.google.com/o/oauth2/v2/auth';

    /**
     * @var string the OAuth2 call back URL path.
     */
    const OAUTH2_CALLBACK_PATH = '/oauth2callback';

    public static function main()
    {
        if (!class_exists(Server::class)) {
            echo 'Please install "react/http" package to be able to run this example';
            exit(1);
        }

        $loop = Factory::create();
        // Creates a socket for localhost with random port.
        $socket = new \React\Socket\Server(0, $loop);

        print 'Enter your OAuth2 client ID here: ';
        $clientId = trim(fgets(STDIN));

        print 'Enter your OAuth2 client secret here: ';
        $clientSecret = trim(fgets(STDIN));

        $redirectUrl = str_replace('tcp:', 'http:', $socket->getAddress());
        $oauth2 = new OAuth2(
            [
                'clientId' => $clientId,
                'clientSecret' => $clientSecret,
                'authorizationUri' => self::AUTHORIZATION_URI,
                'redirectUri' => $redirectUrl . self::OAUTH2_CALLBACK_PATH,
                'tokenCredentialUri' => CredentialsLoader::TOKEN_CREDENTIAL_URI,
                'scope' => self::SCOPE,
                // Create a 'state' token to prevent request forgery. See
                // https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
                // for details.
                'state' => sha1(openssl_random_pseudo_bytes(1024))
            ]
        );

        $authToken = null;

        $server = new Server(
            function (ServerRequestInterface $request) use ($oauth2, $loop, &$authToken) {
                // Stops the server after tokens are retrieved.
                if (!is_null($authToken)) {
                    $loop->stop();
                }

                // Check if the requested path is the one set as the redirect URI.
                if ($request->getUri()->getPath()
                    !== parse_url($oauth2->getRedirectUri(), PHP_URL_PATH)) {
                    return new Response(
                        404,
                        ['Content-Type' => 'text/plain'],
                        'Page not found'
                    );
                }

                // Exit if the state is invalid to prevent request forgery.
                $state = $request->getQueryParams()['state'];
                if (empty($state) || ($state !== $oauth2->getState())) {
                    throw new UnexpectedValueException(
                        "The state is empty or doesn't match expected one." . PHP_EOL
                    );
                };

                // Set the authorization code and fetch refresh and access tokens.
                $code = $request->getQueryParams()['code'];
                $oauth2->setCode($code);
                $authToken = $oauth2->fetchAuthToken();

                $refreshToken = $authToken['refresh_token'];
                print 'Your refresh token is: ' . $refreshToken . PHP_EOL;

                $propertiesToCopy = '[GOOGLE_ADS]' . PHP_EOL;
                $propertiesToCopy .= 'developerToken = "INSERT_DEVELOPER_TOKEN_HERE"' . PHP_EOL;
                $propertiesToCopy .=  <<<EOD
; Required for manager accounts only: Specify the login customer ID used to authenticate API calls.
; This will be the customer ID of the authenticated manager account. You can also specify this later
; in code if your application uses multiple manager account + OAuth pairs.
; loginCustomerId = "INSERT_LOGIN_CUSTOMER_ID_HERE"
EOD;
                $propertiesToCopy .= PHP_EOL . '[OAUTH2]' . PHP_EOL;
                $propertiesToCopy .= "clientId = \"{$oauth2->getClientId()}\"" . PHP_EOL;
                $propertiesToCopy .= "clientSecret = \"{$oauth2->getClientSecret()}\"" . PHP_EOL;
                $propertiesToCopy .= "refreshToken = \"$refreshToken\"" . PHP_EOL;

                print 'Copy the text below into a file named "google_ads_php.ini" in your home '
                    . 'directory, and replace "INSERT_DEVELOPER_TOKEN_HERE" with your developer '
                    . 'token:' . PHP_EOL;
                print PHP_EOL . $propertiesToCopy;

                return new Response(
                    200,
                    ['Content-Type' => 'text/plain'],
                    'Your refresh token has been fetched. Check the console output for '
                    . 'further instructions.'
                );
            }
        );

        $server->listen($socket);
        printf(
            'Log into the Google account you use for Google Ads and visit the following URL '
            . 'in your web browser: %1$s%2$s%1$s%1$s',
            PHP_EOL,
            $oauth2->buildFullAuthorizationUri(['access_type' => 'offline'])
        );

        $loop->run();
    }
}

AuthenticateInWebApplication::main();
Python
#!/usr/bin/env python
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This example creates an OAuth 2.0 refresh token for the Google Ads API.

This illustrates how to step through the OAuth 2.0 native / installed
application flow.

It is intended to be run from the command line and requires user input.
"""

from __future__ import absolute_import

import argparse

from google_auth_oauthlib.flow import InstalledAppFlow


SCOPE = u'https://www.googleapis.com/auth/adwords'


def main(client_secrets_path, scopes):
    flow = InstalledAppFlow.from_client_secrets_file(
        client_secrets_path, scopes=scopes)

    flow.run_local_server()

    print('Access token: %s' % flow.credentials.token)
    print('Refresh token: %s' % flow.credentials.refresh_token)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Generates OAuth 2.0 credentials with the specified '
                    'client secrets file.')
    # The following argument(s) should be provided to run the example.
    parser.add_argument('--client_secrets_path', required=True,
                        help=('Path to the client secrets JSON file from the '
                              'Google Developers Console that contains your '
                              'client ID and client secret.'))
    parser.add_argument('--additional_scopes', default=None,
                        help=('Additional scopes to apply when generating the '
                              'refresh token. Each scope should be separated '
                              'by a comma.'))
    args = parser.parse_args()

    configured_scopes = [SCOPE]

    if args.additional_scopes:
        configured_scopes.extend(args.additional_scopes.replace(' ', '')
                                 .split(','))

    main(args.client_secrets_path, configured_scopes)
Ruby
#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example will create an OAuth2 refresh token for the Google Ads API using
# the Web application flow.
#
# This example will start a basic server that listens for requests at
# http://localhost:PORT, where PORT is the port specified below.

require 'googleauth'
require 'securerandom'
require 'uri'
require 'cgi'
require 'socket'

require 'optparse'

def authenticate_in_web_application(client_id, client_secret, port)
  callback_uri = sprintf('http://localhost:%s', port)

  # Create an anti-forgery state token as described here:
  # https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
  state = SecureRandom.hex(16)

  client_id = Google::Auth::ClientId.new(client_id, client_secret)

  # This example does not store credentials, so no TokenStore is needed.
  user_authorizer = Google::Auth::UserAuthorizer.new(
      client_id, SCOPE, nil, callback_uri)

  authorization_url = user_authorizer.get_authorization_url(state: state)
  printf("Paste this url in your browser:\n%s\n", authorization_url)
  printf("Waiting for authorization and callback...\n")
  printf("Listening at %s\n", callback_uri)
  response_params = get_authorization_code(port)

  # Confirm that the state in the response matches the state token used to
  # generate the authorization URL.
  unless state == response_params['state'][0]
    raise StandardError,
        'State returned from callback does not match the expected state'
  end

  user_credentials = user_authorizer.get_credentials_from_code(
      code: response_params['code'][0])
  printf("Your refresh token is: %s\n", user_credentials.refresh_token)

  printf("Copy your refresh token above into your google_ads_config.rb in your "\
      "home directory or use it when instantiating the library.\n")
end

def get_authorization_code(port)
  authorization_code = nil
  server = TCPServer.open(port)
  client = server.accept
  callback_request = client.readline
  # Use a regular expression to extract the request line from the first line of
  # the callback request, e.g.:
  #   GET /?code=AUTH_CODE&state=XYZ&scope=... HTTP/1.1
  matcher = /GET +([^ ]+)/.match(callback_request)
  response_params = CGI.parse(URI.parse(matcher[1]).query) unless matcher.nil?

  client.puts("HTTP/1.1 200 OK")
  client.puts("Content-Type: text/html")
  client.puts("")
  client.puts("<b>")
  if response_params['code'].nil?
    client.puts("Failed to retrieve authorization code.")
  else
    client.puts("Authorization code was successfully retrieved.")
  end
  client.puts("</b>")
  client.puts("<p>Please check the console output.</p>")
  client.close

  return response_params
end

if __FILE__ == $PROGRAM_NAME
  SCOPE = 'https://www.googleapis.com/auth/adwords'

  # To fill in the values below, generate a client ID and client secret from the
  # Google Cloud Console (https://console.cloud.google.com) by creating
  # credentials for a Web application. Set the "Authorized redirect URIs" to:
  #   http://localhost:[PORT]

  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:client_id] = 'INSERT_CLIENT_ID_HERE'
  options[:client_secret] = 'INSERT_CLIENT_SECRET_HERE'
  options[:port] = 'INSERT_PORT_HERE'

  OptionParser.new do |opts|
    opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__))

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-I', '--client-id CLIENT-ID', String, 'Client ID') do |v|
      options[:client_id] = v
    end

    opts.on('-S', '--client-secret CLIENT-SECRET', String,
        'Client Secret') do |v|
      options[:client_secret] = v
    end

    opts.on('-p', '--port PORT', String, 'Port') do |v|
      options[:port] = v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  authenticate_in_web_application(options[:client_id], options[:client_secret],
      options[:port])
end

Send feedback about...

Google Ads API Beta
Google Ads API Beta
Need help? Visit our support page.