Search code examples
c#firebasehttpoauthgoogle-identity-toolkit

Google Identity Toolkit API in C#


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!


Solution

  • 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!