jhm
jhm

Reputation: 4539

Node.js authentication server for Firebase Admin SDK - JWT validation issue

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:

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/[email protected] 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):

Upvotes: 3

Views: 2405

Answers (1)

jwngr
jwngr

Reputation: 4422

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.

Upvotes: 2

Related Questions