title: Google Oauth 400 error when trying to get auth tokens
body: <p>I need to fix a PHP Google Auth problem. I am trying to exchange Auth token after receiving a code fails with a 400 bad request.</p>
  
  <p>For the life of me I can't figure it out after reading docs and poring over the code again and again.</p>
  
  <p>Is there something wrong with my url?</p>
  
  <pre><code>$url =  'https://accounts.google.com/o/oauth2/token?&amp;grant_type=authorization_code&amp;code='.$_GET['code']."&amp;client_id=".G_CLIENT_ID."&amp;client_secret=".G_CLIENT_SECRET."&amp;redirect_uri=".REDIRECT_URI;
  $context = stream_context_create(
              array( 'http' =&gt; 
                  array('method' =&gt; 'POST',
                  )
              )
          );          
  
  echo "&lt;Br&gt; url to fetch  : ". $url;
  echo "&lt;Br&gt; context to fetch  : ". $context;
  
  $response = file_get_contents($url, false, $context);
  
  echo "&lt;Br&gt; fetch response : ". $response;   
  </code></pre>
  
  <hr>
  
  <p>Is code reuse forcing Google to reject my auth token retrieval attempts?</p>
  
  <p>Google doesn't return much info with their 400  error - surely they should give more info?</p>
  
  <p><strong>Edit 1</strong></p>
  
  <p>Request header returned by print_r(apache_request_headers ()) --></p>
  
  <pre><code>  Array
        (
        [Accept] =&gt; text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
        [Accept-Encoding] =&gt; gzip,deflate,sdch
        [Accept-Language] =&gt; en-US,en;q=0.8
        [Connection] =&gt; keep-alive
        [Cookie] =&gt; PHPSESSID=ec0b5ff920282245f7ce6d194ba36bd1; _ga=GA1.2.1973782149.1384923620
        [Host] =&gt; lxxxxxxr.com
        [User-Agent] =&gt; Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
       )
  </code></pre>
  
  <p>Response header returned by print_r(apache_response_headers()) --></p>
  
  <pre><code>  Array
    (
    [X-Powered-By] =&gt; PHP/5.4.20
    [Expires] =&gt; Thu, 19 Nov 1981 08:52:00 GMT
    [Cache-Control] =&gt; no-store, no-cache, must-revalidate, post-check=0, pre-check=0
    [Pragma] =&gt; no-cache
    [Content-type] =&gt; text/html
    )
  </code></pre>
  
  <p>response Body --> </p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
     &lt;html lang=en&gt;
     &lt;meta charset=utf-8&gt;
     &lt;meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width"&gt;
     &lt;title&gt;Error 400 (Bad Request)!!1&lt;/title&gt;
      &lt;style&gt; ***some style info***    &lt;/style&gt;
       &lt;a href=//www.google.com/&gt;&lt;img src=//www.google.com/images/errors/logo_sm.gif               alt=Google&gt;&lt;/a&gt;
       &lt;p&gt;&lt;b&gt;400.&lt;/b&gt; &lt;ins&gt;That’s an error.&lt;/ins&gt;
         &lt;p&gt;Your client has issued a malformed or illegal request.  &lt;ins&gt;That’s all we know.&lt;/ins&gt;
  </code></pre>
  
  <p><strong>Edit 2 - Solution :</strong></p>
  
  <hr>
  
  <p>Well I have a solution after comparing the output of the google-api-php-client example that worked with mine.</p>
  
  <p>In simple words my url is constructed wrong. It was constructed as a GET query( which is how Linkedin does its Oauth inspite of their docs claiming that they want POST)</p>
  
  <p>Since this is a POST request my url should have been</p>
  
  <pre><code>https://accounts.google.com/o/oauth2/token
  </code></pre>
  
  <p>Then I needed to put the query params as part of the content header. So the main working code snippet is</p>
  
  <pre><code>$params = array('code' =&gt; $_GET['code'],
          'grant_type' =&gt; 'authorization_code',
                  'redirect_uri' =&gt; 'http://carrotleads.com/',
                  'client_id' =&gt; G_CLIENT_ID,
                  'client_secret' =&gt; G_CLIENT_SECRET,
                  );  
  $url = 'https://accounts.google.com/o/oauth2/token';
  $postBody = http_build_query($params);
  $requestHttpContext["content"] = $postBody;
  $requestHttpContext["method"] = 'POST';
  
  $options = array( 'http' =&gt; $requestHttpContext );
  $context = stream_context_create( $options );       
  
  echo '&lt;Br&gt;Your request: &lt;pre&gt;'. print_r($url, true) . '&lt;/pre&gt;';  
  echo '&lt;Br&gt;Your options: &lt;pre&gt;'. print_r( $options, true) . '&lt;/pre&gt;';
  
  $response = file_get_contents($url, false, $context);
  
  echo 'Your response_data: &lt;pre&gt;'. print_r($response, true) . '&lt;/pre&gt;';
  </code></pre>
  
  <hr>
  
  <p>Other Info </p>
  
  <p>Google's lib sets lot more headers ( from the Google_HttpStreamIO.php ). The important code snippets paraphrased :)</p>
  
  <pre><code>$DEFAULT_HTTP_CONTEXT = array(
   "follow_location" =&gt; 0,
   "ignore_errors" =&gt; 1,
  );
  $DEFAULT_SSL_CONTEXT = array(
   "verify_peer" =&gt; true,
  );
  $default_options = stream_context_get_options(stream_context_get_default());
  $requestHttpContext = array_key_exists('http', $default_options) ?
      $default_options['http'] : array();
  
  $url = 'https://accounts.google.com/o/oauth2/token';
  $params = array('code' =&gt; $_GET['code'],
          'grant_type' =&gt; 'authorization_code',
                  'redirect_uri' =&gt; 'http://carrotleads.com/xxxxxxxxxx',
                  'client_id' =&gt; G_CLIENT_ID,
                  'client_secret' =&gt; G_CLIENT_SECRET,
                  );  
  $postBody = http_build_query($params);
  $postsLength = strlen($postBody);
  $requestHeaders = array_merge( array('content-type' =&gt; 'application/x-www-form-urlencoded'), array('content-length' =&gt; $postsLength));
  $headers = "";
  foreach($requestHeaders as $k =&gt; $v) {
     $headers .= "$k: $v\n";
  }
  $requestHttpContext["header"] = $headers;
  $requestHttpContext["content"] = $postBody;
  $requestHttpContext["method"] = 'POST';
  
  $requestSslContext = array_key_exists('ssl', $default_options) ?
      $default_options['ssl'] : array();
  
  if (!array_key_exists("cafile", $requestSslContext)) {
    $requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem';
  }
  $options = array("http" =&gt; array_merge(self::$DEFAULT_HTTP_CONTEXT,
                                               $requestHttpContext),
                   "ssl" =&gt; array_merge(self::$DEFAULT_SSL_CONTEXT,
                                        $requestSslContext));
  
  $context = stream_context_create($options);
  $response_data = file_get_contents($url,
                                     false,
                                     $context);
  </code></pre>
  
  <hr>
  
  <p>All these additional headers are not needed to exchange Auth token but if you want to do it to standards, that's the way to go, including sending across a certificate file.</p>
  
  <p>If you need lots of to and fro Google API requests you are better off using the well-written Google lib, if not it may be overkill and the above code snippets should help out.</p>
  
  <p>In hindsight I feel silly for not picking up the POST vs GET request difference, live and learn; after 2 days of poring over this, I am relieved.</p>