Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google OAuth 2.0 Server to Server: Bad request

I've been banging my head against a wall for 3 days now trying to get this to work.

POST /oauth2/v3/token HTTP/1.1
Host: www.googleapis.com
Content-length: 495
Content-type: application/x-www-form-urlencoded
Authorization: Bearer ya29.cgEcY6meBrvaH6oe0nD_PtsFyMVqskiUYi7iJxapKHeEgPoIw8gMt0BJdIvRn1MfcEgzTS3_gTwI1w
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5MDgyOTgxNjA1NTktc2R1bGFpbWhsaGpxOTY5M2s1Z2E4c25pZjhh%0D%0ANzhlZ3BAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0%0D%0AdHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvYW5hbHl0aWNzLnJlYWRvbmx5%0D%0AIiwiYXVkIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL3Rv%0D%0Aa2VuIiwiZXhwIjoxNDMxNTE0MDUyLCJpYXQiOjE0MzE1MTEwNTJ9.[Cert]

HTTP/1.1 400 Bad Request
Content-length: 67
X-xss-protection: 1; mode=block
X-content-type-options: nosniff
Expires: Wed, 13 May 2015 10:08:00 GMT
Vary: Origin,X-Origin
Server: GSE
Cache-control: private, max-age=0
Date: Wed, 13 May 2015 10:08:00 GMT
X-frame-options: SAMEORIGIN
Content-type: application/json; charset=UTF-8
{
  "error_description": "Bad Request", 
  "error": "invalid_grant"
}

So, I'm creating this in PL/SQL. Oracle 12c. I've managed to get the JWT header and and JWT Claim set to produce an output the same as the google documentation. When i create the cert I think is were the problem occurs.

  1. Do I need the "[-----BEGIN PRIVATE KEY-----" and -----END PRIVATE KEY-----\n] as part of the SHA-256 encryption. Should I do anything with the "New lines"? \n Should I surround it with brackets?

  2. At what point should I be URL encoding?

  3. Is the code in the following example sufficient for the encryption?: http://jastraub.blogspot.co.uk/2009/07/hmacsha256-in-plsql.html

  4. I've attached the a function below to see if you can identify any issues?

Thanks for your help!

  FUNCTION get_JWT (p_token_id ga_app_user.ID_TOKEN%TYPE)
  RETURN VARCHAR2
  IS
  --Plain text
  baseJWTheader   VARCHAR2 (20000);
  baseclaimSet    VARCHAR2 (20000);
  baseSigKey      VARCHAR2 (20000);

  --Seconds

  sysSeconds      NUMBER;

  --Base64 Encoded
  JWTheader       VARCHAR2 (20000);
  claimSet        VARCHAR2 (20000);
  sigKey          VARCHAR2 (20000);
  sigContent      VARCHAR2 (20000);


  --Returned value
  output          RAW (20000);
   BEGIN
    SELECT JWT_HEADER, JWT_CLAIM_SET, SIGNATURE
      INTO baseJWTheader, baseclaimSet, baseSigKey
      FROM dwman.ga_app_user au
     WHERE AU.ID_TOKEN = p_token_id;

   --DBMS_OUTPUT.PUT_LINE ('Base claim Set ' || baseclaimSet);
   JWTheader :=
     TRANSLATE (
        UTL_RAW.cast_to_varchar2 (
           UTL_ENCODE.BASE64_ENCODE (UTL_RAW.CAST_TO_RAW (baseJWTheader))),
        '+/',
        '-_');

  SELECT   (  SYSDATE
            - TO_DATE ('01-01-1970 00:00:00', 'DD-MM-YYYY HH24:MI:SS'))
         * 24
         * 60
         * 60
    INTO sysSeconds
    FROM DUAL;

  baseclaimSet :=
     REPLACE (baseclaimSet, '#EXPIRE#', ROUND (sysSeconds + 3000));
  baseclaimSet := REPLACE (baseclaimSet, '#START#', ROUND (sysSeconds));

  --DBMS_OUTPUT.PUT_LINE ('Claim Set ' || baseclaimSet);

  claimSet := UTL_RAW.cast_to_varchar2 (
           UTL_ENCODE.BASE64_ENCODE (UTL_RAW.CAST_TO_RAW (baseclaimSet)));

  sigKey := baseSigKey;

  sigContent := JWTheader || '.' || claimSet;

  --DBMS_OUTPUT.PUT_LINE('Sig Content '||sigContent);

  sigContent := REPLACE (sigContent, CHR (10), '');
  sigContent := REPLACE (sigContent, CHR (13), '');
  /*
  FOR V_TR in 1..length(sigContent)
  LOOP
    DBMS_OUTPUT.PUT_LINE (substr(sigContent,V_TR,1)||'='||to_char(ASCII(substr(sigContent,V_TR,1))));

  END LOOP;
  */      
  sigContent :=
        sigContent
     || '.'
     || google_signature (sigContent, sigKey);
  RETURN UTL_URL.ESCAPE(sigContent, TRUE, 'UTF-8');
 END get_JWT;
like image 525
Fudztown Avatar asked May 13 '15 10:05

Fudztown


People also ask

Why OAuth is bad for authentication?

The problem stems from the OAuth 2.0 system not having a serious verification mechanism, thus allowing almost anyone to register an app with a provider. Once registered, the app can use the OAuth 2.0 authentication/authorization mechanism to request consented access to a user's data.

How do I fix OAuth error?

When a user tries to login after the session id is expired, the system throws the OAuth error. Solution: Typically, clearing the browser or device cache fixes the problem.

What is a OAuth 2.0 redirect URI?

The redirect URL is the endpoint for your application or web page that processes the seller authorization response and manages the seller's OAuth tokens. You need to add this URL to your application using the Developer Dashboard.


1 Answers

Your code snippet might only be a portion of what you did, but it seemed to be lacking much of the OAUTH steps required by Google to connect.

You can get more detail on what those steps are by looking at this URL: https://developers.google.com/accounts/docs/OAuth2WebServer#offline

For the remainder of this answer, I have described my own experiences, doing something similar (downloading data from GA, and uploading it to a database using SQL Statements).

Start by getting a consumerKey and consumerSecret for your Google project. You'll need to have a URL that Google can redirect to, both when you request your consumer Key, but also to supply to Google during your OAuth calls. They have to match.

The next step is to send a GET request to Google. Here is an example in c# which you can build using SQL string concatenation.

String URL_AUTH_FIRST = "https://accounts.google.com/o/oauth2/auth";
String URL_TOKEN_ENDPOINT_SECOND = "https://accounts.google.com/o/oauth2/token";
String url = String.Format(
    "{0}?client_id={1}&redirect_uri={2}&access_type=offline&scope={3}&response_type=code&state={4}&approval_prompt=force",
        URL_AUTH_FIRST,_consumerKey_web_app,redir_url,scope,state);

You will need to have an embedded browser to do this. Google.com will redirect that browser to a site that's under their control so that the user has to be able to login or refuse to authorize your app. After Google has the information they need, they redirect back to your embedded browser. You can do some of the steps using copy/paste in your own browser, but at some point (described below) you have to POST back some data which I wouldn't know if you can do that from a browser application.

Google will respond by redirecting your embedded browser to a url. The URL has data on it. You have to parse the parameters on the URL and look for the parameter "code". If you get a url with "code" as a parameter, you have to POST a format in a certain format back to Google.

WebClient client = get_WebClient(); // proprietary to include things like proxy info
try {
    NameValueCollection values = new NameValueCollection();
    values.Add("client_id", _consumerKey_web_app);
    values.Add("client_secret", _consumerSecret_web_app_offline);
    values.Add("grant_type", "authorization_code");
    values.Add("redirect_uri", URL_GOOGLE_REDIRECTS_TO_THIS_URL_AFTER_URL_AUTH);
    values.Add("code", authorization_code);
    Byte[] responseBytes = client.UploadValues(URL_TOKEN_ENDPOINT_SECOND, values);
}

Google will return "responseBytes" which would be a json formatted string, resembling:

{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
                  "expires_in":3920,
                  "token_type":"Bearer",
                  "refresh_token":"1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ"
                }

The access_token gets appended to your REST API calls.

You can supply a GA Query via REST API, get the data back, and upload it into your database using SQL statements. That's what my App does.

You can save that refresh_token and supply it in future connections. Indeed, this whole sequence needs to be done with a browser or browser control, interactively with a user logging in. After it's done, and you've received the refresh_token, then your SQL can use and re-use the refresh_token basically in-definitely, at least until the user's password changes.

Google will also return an error 401 on a regular basis. This just means you have to re-request your access token by Posting a new set of values to Google:

NameValueCollection values = new NameValueCollection();
                    values.Add("client_id", _consumerKey_web_app);
                    values.Add("client_secret", _consumerSecret_web_app_offline);
                    values.Add("refresh_token", refresh_token);
                    values.Add("grant_type", "refresh_token");
                    Byte[] responseBytes = client.UploadValues(URL_TOKEN_ENDPOINT_SECOND, values);
like image 106
Ward Yaternick Avatar answered Dec 10 '22 20:12

Ward Yaternick