3

I am working on a project where we are going to be using different services in a microservice architecture, and we would like to also use some Firebase services. I am working on an auth server that is going to mint custom JWT's for use in both Firebase, as well as the other API projects.

We would like to use the Firebase Auth SDK to easily integrate with FB, Google, Twitter etc, but we need to enrich the user's token with more data. Therefore, my thought process is that I'd create a Node.JS auth server that uses the Firebase Admin SDK to do this. The flow would be as follows:

  1. User logs in with favourite provider on client
  2. If login is succesful, the user receives a JWT from Firebase. This is sent to the auth server for validation
  3. If the auth server can validate the token using the admin SDK, create a new custom token enriched with more data, and return this new custom token to the client
  4. Have client re-authenticate with the new custom token, and use it for communication with both Firebase as well as our other API projects (which will mainly be in .NET Core)

Step 1-3 works fine. The problem arises when trying to verify the custom token on the other services.

TL;DR : There are two questions inhere:

  1. When validating custom tokens issued using the Firebase Node.JS Admin SDK, what should I use as the public key? A key extracted from Google's exposed JWK's, or a key extracted from the private key that is used to sign?
  2. In case of the JWK approach, how should I construct the custom token with a kid header?

First, I am in doubt of the proper way to verify it. (Please excuse me, I'm not that experienced creating OAuth flows.) The algorithm used is RS256, so I should be able to verify the token using a public key. As I see it, there are two ways to get this key:

  1. Extract the public key from the private key and verify using this. I can do this and verify successfully on a test endpoint on my auth server, however I feel this is the incorrect way to do it
  2. The other, and more correct way I think, is to use the values from the token to find the JWK's on Google's "/.well-known/openid-configuration/" endpoint for my project, , i.e.

https: //securetoken.google.com/[PROJECT ID]/.well-known/openid-configuration

to retrieve the exponent and modulus for the correct kid (key ID) and create the public key from those.

The token generated from the admin SDK by doing

admin.auth().createCustomToken(uid, additionalClaims).then(function(customToken)

with some custom claims looks something like this:

headers:

{
  "alg": "RS256",
  "typ": "JWT"
}

payload:

{
  "claims": {
    "premiumAccount": true,
    "someRandomInnerObject": {
      "something": "somethingRandom"
    }
  },
  "uid": "<uid for the user>",
  "iat": 1488454663,
  "exp": 1488458263,
  "aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
  "iss": "firebase-adminsdk-le7ge@<PROJECT ID>.iam.gserviceaccount.com",
  "sub": "firebase-adminsdk-le7ge@<PROJECT ID>.iam.gserviceaccount.com"
}

I can't seem to get method 2 to work, though. One problem is that the generated token does not have a kid header, and so does not conform to the OpenID spec (AFAIK), which leads to one of two options:

  1. Go with the first approach above. This leads to problems though - if I for some reason need to revoke or reset the private key on the auth server, I need to do it and deploy the changes on all the other services too, making the solution less dynamic and more error-prone.
  2. Generate a similar token manually using one of the libs mentioned at jwt.io, and add the kid from the original Firebase ID token to it's headers.

Problems with number 2:

  • What should I put as iss, aud and sub, then? The same values as the admin SDK does? If so, isn't that 'cheating', as they are no longer the issuer?
  • I've tried it (generating a similar copy of the token, but adding the kid of the original token), and I can't seem to verify the generated token using the created PEM key for the kid.

The way I do the latter is this (following a blog guide on the subject):

  1. Go to https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com and retrieve the modulus (n) and exponent (e) for the relevant kid

  2. Generate the public key using a lib (rsa-pem-from-mod-exp)

  3. Use the key to verify using the 'official' jwt lib

    The above results in a public key as such:

    -----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAxXpo7ChLMnv1QTovmm9DkAnYgINO1WFBWGAVRt93ajftPpVNcxMT MAQI4Jf06OxFCQib94GyHxKDNOYiweVrHVYH9j/STF+xbQwiPF/8L7+haC2WXMl2 tkTgmslVewWuYwpfm4CoQFV29OVGWCqwEcbCaycWVddm1ykdryXzNTqfzCyrSZdZ k0yoE0Q1GDcuUl/6tjH1gAfzN6c8wPvI2YDhc5gIHm04BcLVVMBXnC0hxgjbJbN4 zg2QafiUpICZzonOUbK6+rrIFGfHpcv8mWG1Awsu5qs33aFu1Qx/4LdMAuEsvX9f EmFZCUS8+trilqJbcsd/AQ9eOZLAB0BdKwIDAQAB -----END RSA PUBLIC KEY-----

Two things seem to be wrong. One is that the key is different from the one I can extract from the private key. The other is that the one I extract from the private key has these comments instead:

-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----

with no 'RSA'. Does this matter? In any case, it doesn't verify.

Finally, did I misunderstand the OpenID flow completely? Are the JWKs generated from a private key that I need as well to verify my JWTs? Should I expose my own JWKs on my auth server for the other services to contact and use instead of Google's? I'm a bit confused as to what the Firebase Admin SDK does and doesn't do, I think :-)

I know this is a lot of questions, but I think they're all related.

Some resources I've relied on in my research (besides the official admin sdk docs ofcourse):

Community
  • 1
  • 1
jhm
  • 4,379
  • 5
  • 33
  • 49
  • Do you need to actually verify the custom token? After logging in with the custom token on a client device, your client will now have ID tokens which contain the desired claims. These ID tokens are what you should use to verify requests made to your different microservices. The reasons for this are twofold: (1) there is clear documentation on how to do this and the Admin SDKs can do this for you and (2) your custom tokens will expire after an hour, while ID tokens will automatically be refreshed for you every hour. Does that make sense? – jwngr Mar 04 '17 at 20:39
  • Hey @jacobawenger - Thanks for your reply. It is my understanding that when I re-authenticate the client with the custom token, then the original ID token is thrown away and deactivated, is this wrong? Anyway, the purpose of the custom tokens for me is to enrich it with custom claims from my authentication server, which means that these are the ones I want to use on both Firebase (security rules) as well as my other microservices. As such, these are the ones I want to verify. Or did I misunderstand? :-) – jhm Mar 08 '17 at 09:57
  • 1
    You are close to having the full picture. When you re-authenticate with the custom token, we actually generate a new ID token with the claims from the custom token. So yes, your original ID token is discarded, but a new one is created in its place. And that ID token will be automatically refreshed every hour. So, you should be able to just call `user.getToken()` to get a valid ID token whenever you need it. That method handles all the caching on your behalf. – jwngr Mar 09 '17 at 03:59
  • @jacobawenger - "When you re-authenticate with the custom token, we actually generate a new ID token with the claims from the custom token" Eureka! :-) There it is, everything works now. I didn't get that from the documentation - don't know if I'm the only one? Anywho, would you like to write out an official answer for some points, or should I do it Q&A style? :-) Finally, thanks a bunch, have a great weekend – jhm Mar 17 '17 at 10:14
  • Great, glad you are unblocked! I posted an official answer. – jwngr Mar 17 '17 at 15:03

1 Answers1

2

After re-authenticating the Firebase client SDK with the custom token, the client actually generates a new ID token with the claims from the custom token. This ID token is what you should use to verify requests made to your different microservices (documented here). So yes, your original ID token is discarded, but a new one is created in its place. And that ID token will be automatically refreshed every hour. So, you should be able to just call user.getToken() to get a valid ID token whenever you need it. That method handles all the caching on your behalf.

jwngr
  • 4,284
  • 1
  • 24
  • 27