0
votes

We're trying to setup Custom Authentication so that an Android application can leverage our existing Auth system to get access to a Firebase Database.

The mobile app authenticates against an .NET WebAPI endpoint. We send back a firebase auth token in the response that the app can then use to authenticate in the Firebase SDK.

We thought this would be pretty simple according to the documentation. Using the FirebaseTokenGenerator package from Nuget, we generate this token from the secret that is found in the Firebase Console > Project > Manage > Database > Database secrets.

However we see this exception come back from the Android SDK when we use the token generated here:

com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The custom token format is incorrect. Please check the documentation.
at com.google.android.gms.internal.zzafd.zzes(Unknown Source)
at com.google.android.gms.internal.zzafa$zzg.zza(Unknown Source)
at com.google.android.gms.internal.zzafl.zzet(Unknown Source)
at com.google.android.gms.internal.zzafl$zza.onFailure(Unknown Source)
at com.google.android.gms.internal.zzafg$zza.onTransact(Unknown Source)

Looking through the documentation has confused me even more because it is not clear what the issue might be. There seems to be many candidate solutions, but I am not able to figure out which is correct. Following are some things that are talked about in the docs:

  • Creating a service account, downloading the JWT json file, including that in the .NET WebApi project and some how using that with the FirebaseTokenGenerator
  • Using 3rd party JWT libraries to generate the token from the secret

These two strategies seem to be getting at the same thing, but don't appear to be related in any way.

Am I using the wrong secret? Do I need to do some more setup on my WebAPI project to get it generating the correct token?

For reference, this is the method that generates my token:

public string GetSignedFirebaseAuthToken(Agent agent, DateTime expires)
{
    var tokenGenerator = new Firebase.TokenGenerator(BusinessLogicResources.FirebaseSecret);
    var authPayload = new Dictionary<string, object>()
    {
        { "uid", agent.AgentId.ToString() },
        { "UserName", agent.FullName },
        { "AgencyId", agent.AgencyId },
        { "AgencyName", agent.AgencyName }
    };
    return tokenGenerator.CreateToken(authPayload, new Firebase.TokenOptions(expires:expires));
}

Update: This is the new code I'm using to mint my firebase token.

public string GetSignedFirebaseAuthToken(Agent agent, DateTime expires)
    {
        // I set the expiry time to 3600 because this is the maximum number of seconds
        // allowed according the the Firebase documenation.
        var expiryTime = 3600;
        // This claims object contains some additional information that Telenotes would
        // like to track for each user of Firebase. This is used for reporting and
        // security rules in Firebase.
        var claims = new Dictionary<string, object>()
        {
            { "UserName", agent.FullName },
            { "AgencyId", agent.AgencyId.ToString() },
            { "AgencyName", agent.AgencyName }
        };
        // In order for the cryptography algorithms to be happy, I need to put our
        // keys into stream objects.
        var memoryStream = new MemoryStream();
        var streamWriter = new StreamWriter(memoryStream);
        streamWriter.Write(BusinessLogicResources.FirebaseSecret.Replace(@"\n", "\n"));
        streamWriter.Flush();
        memoryStream.Position = 0;
        var streamReader = new StreamReader(memoryStream);
        // BouncyCastle takes care of the cryptography stuff, so we'll hand our
        // secret over to BouncyCastle to read and encode.
        var bouncyCastleReader = new PemReader(streamReader);
        var rsaParams = (RsaPrivateCrtKeyParameters) bouncyCastleReader.ReadObject();
        // Now that the secret is packed up all nicely in rsaParams, I need to
        // put together the rest of the payload that Firebase needs in my auth token.
        var tokenPayload = new Dictionary<string, object>()
        {
            { "claims", claims },
            { "uid", agent.AgentId },
            { "iat", (DateTime.Now).SecondsSinceEpoch() },
            { "exp", (DateTime.Now).AddSeconds(expiryTime).SecondsSinceEpoch() },
            { "aud", BusinessLogicResources.FirebasePayloadAud },
            { "iss", BusinessLogicResources.FirebasePayloadISS },
            { "sub", BusinessLogicResources.FirebasePayloadSUB },
            { "alg", "RS256" }
        };

        // Lastly, we need to put it all together using JOSE JWT (see https://github.com/dvsekhvalnov/jose-jwt)
        // and BouncyCastle encoding magic.
        var rsaKey = DotNetUtilities.ToRSA(rsaParams);
        return JWT.Encode(tokenPayload, rsaKey, JwsAlgorithm.RS256);
    }
1
Since you're using .NET, you're looking for Create custom tokens using a third-party JWT library. The other sections of the doc are for supported SDKs. What are the values of BusinessLogicResources.FirebasePayload*? How have you verified those exactly match your service account email, identity url, etc? Can you share the values in an obfuscated way to help verify?Kato
Also, what is the value of agent.AgentId? Where is the error generated? At the client when you try to call auth? How have you verified the token being used there matches what you output here? The basic approach here looks correct insomuch as I understand anything .NET, so it's probably in the details.Kato
The error is generated in the client, in an Android application. We have verified that the token being output here comes over the wire to the client correctly.aaronlangford31
agent.AgentId is an int field. Let me know if that is not enough information for you.aaronlangford31
BusinessLogicResources.FirebasePayload values... iss and sub: [email protected]aaronlangford31

1 Answers

1
votes

A couple of key things to understand:

Issue #1

I was using FirebaseTokenGenerator 2.0.0 as a part of my project. If you check out the GitHub project, it clearly states that this package is not compatible with Firebase 3.x.x.

Therefore, if you are trying to use an existing auth system in .NET, you will need to use a 3rd party to put together your Firebase tokens. An excellent example of how to do this is found on this post.

If you don't care for BouncyCastle or JOSE JWT (libraries that will help you encode tokens from your Firebase secret), you can go over the specs that Google provides here and find another way to make this all work for your system.

Issue #2

The correct token to use in the above mentioned process is not your Database secret. The Firebase team wants you to set up your server with a Service Account. Details on how to accomplish this are found here.

From this process, you should get a JSON document (as a file download). In this JSON document, you will find a property called "private_key_id". Even though it looks a little funky ("-----BEGIN PRIVATE KEY-----\n..."), you need this entire thing in the key generation process mentioned above. So no need for any string parsing or anything like that. You will also need other values from that document, but those details are laid out in the links already provided.

It sounds like things won't be this way forever and a nice library will come out in the next few months or so to make this process more straightforward.