0

Some Background Information

I am building a game in Unity, using C#. Since I am using Firebase and the ready-made Unity Firebase SDK won't work in my case1, I have resorted to interfacing with Firebase's REST API through C#'s HttpClient class (which comes with System.Net.Http).

I am currently struggling with the Firebase Authentication APIs. For those unfamiliar with OAuth APIs, when I call the Sign Up or Sign In endpoints, I get both an ID Token and a Refresh Token. ID Tokens expire; refresh tokens do not. When the ID Token expires, I must call another endpoint, called the Token Exchange, to get a new ID token using my refresh token.

1 There is a rumored Google Identity Toolkit C# library, but the first result returned by that search I found it by leads to a 404 error instead of documentation.

What Does Work

Following the Firebase guides and the underlying Identity Toolkit guides, I have been successful so far interfacing the token exchange endpoint with a cURL command from bash:

curl 'https://securetoken.googleapis.com/v1/token?key=[MY FIREBASE API KEY]' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'grant_type=refresh_token&refresh_token=[MY REFRESH TOKEN]'

of course, I replace [MY FIREBASE API KEY] with my Firebase Web API key, and [MY REFRESH TOKEN] with the refresh token returned from the sign in/sign up endpoints.

However, despite my many attempts, I have not been able to replicate this cURL command in C#!

My Failed Attempts

1.

Here's my original code that didn't work.

public async Task<bool> ExchangeToken()
    {
        FormUrlEncodedContent body = new FormUrlEncodedContent(new Dictionary<string, string>() {
            {"grant_type", "refresh_token" },
            {"refresh_token", Uri.EscapeDataString(user.refreshToken) }
        });
        HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://securetoken.googleapis.com/v1/token?key=" + apiKey);
        message.Content = body;
        message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
        HttpResponseMessage res = await client.SendAsync(message);
    }

Unfortunately, I get this 401 (Unauthorized) error response:

{
  "error": {
    "code": 401,
    "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
    "status": "UNAUTHENTICATED"
  }
}

This is quite strange, considering that I get a 2XX (OK) response from what should be an equivalent cURL command...

2.

Thanks to a very nice website I just discovered, I was able to "convert" my cURL command into C# code. However, this did not work. I got the exact same error as attempt #1.

public async Task<bool> ExchangeToken()
    {
        using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://securetoken.googleapis.com/v1/token?key=[I WOULD INSERT MY KEY HERE]"))
        {
            request.Content = new StringContent("grant_type=refresh_token&refresh_token=[INSERT REFRESH TOKEN HERE]", Encoding.UTF8, "application/x-www-form-urlencoded");

            var res = await client.SendAsync(request);
        }
    }

Possible Leads

  1. All of my other requests to all of the other endpoints work. There are two notable differences: 1) the API is technically not Firebase's, but Google Identity Toolkit's. 2) This is the only endpoint that I'm using that uses a Content-Type header of application/x-www-form-urlencoded instead of application/json.

My Question / TL;DR

How do I interface with the Google Identity Toolkit API's Token Exchange endpoint using C#? (Though I'm currently using the HttpClient class, I'm totally open to other solutions! They just have to be compatible with Unity3D.)

Thanks in advance!

yummypasta
  • 1,398
  • 2
  • 17
  • 36

3 Answers3

0

Google wants you to be using Firebase for auth. While Firebase does not have a .NET client SDK shipped by Google, there are other options.

If you don't use Firebase and perform the requisite tasks with oAuth/OpenID Connect endpoints yourself, that works too. There is a Google .NET SDK that helps you with this...a little bit . The SDK lists Google APIs that are "supported" by the SDK; Google Cloud Identity v1 and v1beta APIs are both on the list. So you could call Google Cloud Identity endpoints via this .NET SDK. You do have to understand the implications of various auth flows whereas in Firebase much of that machinery is abstracted away from you as the app developer.

identigral
  • 3,920
  • 16
  • 31
  • Hello, and thanks so much for your answer. The REST API I am using is, in fact, an officially supported method of interfacing Firebase services (see firebase.google.com/docs/reference/rest/auth). I'll take a look at the .NET SDK and possibly contact firebase. – yummypasta Aug 12 '19 at 21:53
0

After playing around with the code a little more, I remembered that I was setting default authorization on my HttpClient:

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", idToken);

This was included for all of the other API calls, as they require authentication with the ID Token. However, this seemed to be confusing the token exchange endpoint; it must 1) look for an authorization header, then 2) use the refresh token supplied in the message contents.


How to fix this issue

  1. Instead of setting the default header on your HttpClient (Which is supposed to be reused for all of your HTTP requests), do the following:
var req = new HttpRequestMessage(/* HttpMethod enum value, endpoint URL/IP */);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", user.idToken); //Set the authorization per-request, not globally
var res = await client.SendAsync(req);
  1. Then just simply call the Token Exchange endpoint!

From the Firebase REST API Documentation:

You can refresh a Firebase ID token by issuing an HTTP POST request to the securetoken.googleapis.com endpoint.

https://securetoken.googleapis.com/v1/token?key=[API_KEY]

Just provide a refresh_token parameter in the body (along with the mandatory grant_type with value refresh_token). DO NOT provide an authorization header.


Hope this helps anyone with the same problem!

yummypasta
  • 1,398
  • 2
  • 17
  • 36
0

As an alternative, you can also use https://github.com/google/apis-client-generator to generate the c# client from the discovery docs. I had some naming conflicts when I generated mine, but after resolving those it works.

Kelvin
  • 574
  • 3
  • 13