user3772108
user3772108

Reputation: 874

Google API Oauth2 Refresh token bad request 400

having an issue refreshing an existing token by using the Google API with Xamarin C#. I guess something is wrong with my code, but I cannot find what.

(Note: I added newline characters at the & to make the requests more readable. Hope everyone will be cool with that.)

Request Auth URI

https://accounts.google.com/o/oauth2/v2/auth?
scope=https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events&
include_granted=true&
response_type=code&
redirect_uri=com.googleusercontent.apps.MYOAUTHCLIENTID:/oauth2redirect&
client_id=MYOAUTHCLIENTID.apps.googleusercontent.com&
access_type=offline

Redirect URI

com.googleusercontent.apps.MYOAUTHCLIENTID:/oauth2redirect

Response Authentication Code

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Request token URI

https://oauth2.googleapis.com/token?
code=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&
redirect_uri=com.googleusercontent.apps.MYOAUTHCLIENTID:/oauth2redirect&
client_id=MYOAUTHCLIENTID.apps.googleusercontent.com&
grant_type=authorization_code

Response token

{
  "access_token": "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
  "expires_in": 3599,
  "refresh_token": "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
  "scope": "https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events",
  "token_type": "Bearer"
}

Now I can use the "access_token" in my HTTP header requests and work with the calendar without problems. I also can revoke the token, but not refreshing it.

Try to refresh token

var dict = new Dictionary<string, string>();
dict.Add("Content-Type", "application/x-www-form-urlencoded");
var contentHeader = new FormUrlEncodedContent(dict);

HttpClient refreshTokenClient = new HttpClient();

// Doing this because someone wrote that this helped in his case. Did not help :/
System.Net.ServicePointManager.SecurityProtocol |=
    SecurityProtocolType.Tls12 |
    SecurityProtocolType.Tls11 |
    SecurityProtocolType.Tls;

// uriTokenRequest has the type URI with this value
// {https://oauth2.googleapis.com/token?
// code=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&
// client_id=MYOAUTHCLIENTID.apps.googleusercontent.com&
// grant_type=refresh_token&
// refresh_token=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ}

using (var result = await refreshTokenClient.PostAsync(uriTokenRequest, contentHeader))
    if (result.IsSuccessStatusCode) // This will be FALSE
    {
        ...
    }

The response

{StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Accept-Ranges: none
  Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
  Cache-Control: no-store, must-revalidate, no-cache, max-age=0
  Date: Sun, 11 Jul 2021 13:46:09 GMT
  Pragma: no-cache
  Server: scaffolding
  Server: on
  Server: HTTPServer2
  Transfer-Encoding: chunked
  Vary: X-Origin
  Vary: Referer
  Vary: Origin
  Vary: Accept-Encoding
  X-Android-Received-Millis: 1626011170088
  X-Android-Response-Source: NETWORK 400
  X-Android-Selected-Protocol: http/1.1
  X-Android-Sent-Millis: 1626011170050
  X-Content-Type-Options: nosniff
  X-Frame-Options: SAMEORIGIN
  X-XSS-Protection: 0
  Content-Type: application/json; charset=utf-8
  Expires: Mon, 01 Jan 1990 00:00:00 GMT
}}

The Google API documentation https://developers.google.com/identity/protocols/oauth2/native-app#offline does say about a "client_secret". But with the response_type "code" I never received such. I've read the documentation now so many times that I am blind to it.

Or does "code" not require a token refresh?

Any ideas?

EDIT: "code" does require a token refresh, since the token does expire, as the result property "expires_in" already tell.

EDIT 2: As by Cherry Bu - MSFT suggested I peeked at the Xamarin.Auth source code and found some differences, that I adapted. Unfortunately I am still not able to succeed. This is my latest try:

var queryValues = new Dictionary<string, string>();
//queryValues.Add("Content-Type", "application/x-www-form-urlencoded");
queryValues.Add("client_id", Constants.OAuthClientId);
queryValues.Add("code", _authenticationCode);
queryValues.Add("refresh_token", _refresh_token);
queryValues.Add("grant_type", "refresh_token");

var httpContent = new FormUrlEncodedContent(queryValues);

HttpClient refreshTokenClient = new HttpClient();

//if (!string.IsNullOrEmpty(_accessToken) && !string.IsNullOrEmpty(_accessTokenType))
//{
//    refreshTokenClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(_accessTokenType, _accessToken);
//}

using (var response = await refreshTokenClient.PostAsync("https://accounts.google.com/o/oauth2/token", httpContent).ConfigureAwait(false))
{
    if (response.IsSuccessStatusCode) --> This will be false
    {
        ...
    }
}

Upvotes: 0

Views: 3694

Answers (2)

user3772108
user3772108

Reputation: 874

Fixed it. 2 issues:

1 The DefaultRequestHeader did not work.

HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(GoogleOAuth.AccessToken, GoogleOAuth.AccessTokenType);

Even when the object itself did look alright it did not work. I replaced it with that:

HttpClient client = new HttpClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Post, calenderUri);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(GoogleOAuth.AccessTokenType, GoogleOAuth.AccessToken); // both are strings. ("Bearer", "yaa...")
requestMessage.Content = data;
var response = await client.SendAsync(requestMessage);

if (!response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
throw new Exception(content);
}

After that I only had to do one more thing

2 Removed "code" and "client_secret" from the request data. The result:

using (HttpClient client = new HttpClient())
{
    var googleData = new Dictionary<string, string>();
    googleData.Add("client_id", GoogleOAuth.ClientId);
    googleData.Add("refresh_token", GoogleOAuth.RefreshToken);
    googleData.Add("grant_type", "refresh_token");

    var data = new FormUrlEncodedContent(googleData);

    var requestMessage = new HttpRequestMessage(HttpMethod.Post, Constants.GoogleTokenUri);
    requestMessage.Headers.Authorization = new AuthenticationHeaderValue(GoogleOAuth.AccessTokenType, GoogleOAuth.AccessToken);
    requestMessage.Content = data;
    var response = await client.SendAsync(requestMessage);

    if (response.IsSuccessStatusCode)
    {
        // do something
    }
}

For weeks I tried to get it running and was close to insanity. Hope this saves someones nerves.

Upvotes: 0

Linda Lawton - DaImTo
Linda Lawton - DaImTo

Reputation: 117196

The correct http end point to refresh an access token can be found below

HTTP POST  https://accounts.google.com/o/oauth2/token
client_id={ClientId}&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token

I actually have an example of using the google .net client library with Xamarin i had to adapt the authorization to it. I will have a look around see if i can find the code.

Upvotes: 1

Related Questions