1
votes

I have been working on a project where I have had to implement OAuth across 7 different platforms, but for some reason Google's OAuth process has tripped me up.

The project has been developed in .NET MVC, and I am using RestSharp to make outgoing Http requests.

I am retrieving an OAuth request to access the GoogleAnalytics API, and on the first attempt I am obtaining a valid access token which I am able to use to successfully retrieve data from the user's GA account. However, although I specified access_type=offline in the auth request URL, when attempting to refresh the token after it expires, I am seeing the following error:

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

My request URL looks like this:

https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https://www.googleapis.com/auth/analytics.readonly&include_granted_scopes=true&response_type=code&state=8f692f0f-b177-4b0b-aa89-a757da9432e3&redirect_uri=https://localhost:44338/GoogleAnalytics&client_id=xxxx

My token request function called when the user is redirected back along with the code:

public OAuth2Token GetToken(string code)
{
    var client = new RestClient(_tokenUri);
    var request = new RestRequest(Method.POST);
    request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
    request.AddParameter("undefined", $"client_id={_clientId}&client_secret={_secret}&code={code}&redirect_uri={_redirectUri}&grant_type=authorization_code", ParameterType.RequestBody);
    var response = client.Execute<OAuth2Token>(request);
    return response.Data;
}

Access & refresh tokens are returned, and stored in the database then used later in my refresh function, where the tokenUri here is https://www.googleapis.com/oauth2/v4/token:

public OAuth2Token RefreshToken()
{
    var client = new RestClient(_tokenUri);
    var request = new RestRequest(Method.POST);
    request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
    request.AddParameter("undefined", $"client_id={_clientId}&client_secret={_secret}&refresh_token={_refreshToken}&grant_type=refresh_token", ParameterType.RequestBody);
    var response = client.Execute<OAuth2Token>(request);
    if (!response.IsSuccessful)
        if (response.ErrorException == null)
            throw new Exception(response.Content); 
        else
            throw response.ErrorException;
    return response.Data;
}

Another point to note is that my Google App here is in testing mode, and I have added users to the list of test users. Any suggestions as to what I'm doing wrong here?

Cheers

1
Any reason your not using the google .net client library?DaImTo
@DaImTo the decision was made for code readability and consistency, given that each API across the project uses the same OAuth2 industry standard, but none share the same library. In theory this shouldn't matter as long as they are following the standard.Alex Koumoundouros

1 Answers

1
votes

The post requests expect to see the values in the post body. You are sending them as parameters.

  request.AddParameter("undefined", $"client_id={_clientId}&client_secret={_secret}&refresh_token={_refreshToken}&grant_type=refresh_token", ParameterType.RequestBody);

You should probably be using request.AddBody

 request.AddBody("undefined", $"client_id={_clientId}&client_secret={_secret}&refresh_token={_refreshToken}&grant_type=refresh_token", ParameterType.RequestBody);

detailed explanation.

Let's start from the top.

Google Authorization or actually all Oauth2 server preform what is called the Oauth dance, there are three steps to this dance.

Request consent

The first URL you need is the one which will cause the authorization server to present a consent screen to the user.

GET https://accounts.google.com/o/oauth2/auth?client_id={clientid}&redirect_uri={redirectURI}&scope={scope}&response_type=code

This will return to you an authorization code note the response type is response_type=code

Exchange authorization code for an access token and refresh token.

This call is a HTTP Post and the body is in the form of a HTTP request string.

POST https://accounts.google.com/o/oauth2/token
code={AUTHORIZATION CODE}&client_id={ClientId}&client_secret={ClientSecret}&redirect_uri={REDIRECT URI}&grant_type=authorization_code

The response here will be in Json format and will be a refresh token and an access token. note the grant_type here is authorization code.

{
"access_token" : "ya29.1.AADtN_VSBMC2Ga2lhxsTKjVQ_ROco8VbD6h01aj4PcKHLm6qvHbNtn-_BIzXMw",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/J-3zPA8XR1o_cXebV9sDKn_f5MTqaFhKFxH-3PUPiJ4"
}

As your access token will expire in an hour you will need to refresh it using the refresh token

Refresh access token.

This is also a HTTP POST call and the body is again in the form of a query string. note that the grant type here is refresh token

POST https://accounts.google.com/o/oauth2/token
client_id={CLIENT ID}&client_secret={ClientSecret}&refresh_token={REFRESH TOKEN}&grant_type=refresh_token

Then you have a fresh access token.

{
"access_token" : "ya29.1.AADtN_XK16As2ZHlScqOxGtntIlevNcasMSPwGiE3pe5ANZfrmJTcsI3ZtAjv4sDrPDRnQ",
"token_type" : "Bearer",
"expires_in" : 3600
}