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);
}