Daniel F Daniel F - 9 days ago 5
Python Question

Migrating Python backend from Gitkit to to Firebase-Auth with python-jose for token verification

Over on GitHub a helpful Google dev told me that


to create a user session, your python backend server only needs a JWT
library to verify the Firebase Auth token (signature and audience) in
the request and extract the user info from the token payload.


I am having trouble with verifying the token.

This is where I'm at; In order to start the migration I proceeded as follows:


  1. I added Firebase-Auth to the Android App, while still having Gitkit in the App until Firebase-Auth works. Now I have two sign-in buttons, one which signs in into Firebase and one for the "almost deprecated" Gitkit.

  2. On firebase.com I imported the Google project into a new Firebase Project, so the user database is the same. I've already managed to use Firebase-Auth in the Android App, am able to log-in as a known user and I can successfully retrieve the token which I will need for my backend server by calling
    mFirebaseAuth.getCurrentUser().getToken(false).getResult().getToken()
    . It contains the same
    user_id
    as the GitKit token.



Now I'm attempting to replace the
identity-toolkit-python-client
library with
python-jose
. Since I'm currently not sending the Firebase token to the backend, but only the Gitkit token, I want to test this
python-jose
library on the Gitkit token.

On the backend, before calling
GitKit.VerifyGitkitToken()
i'm now printing out the results of
jose.jwt.get_unverified_header()
and
jose.jwt.get_unverified_claims()
in order to check if I get to see what I expect. The results are good, I am able to view the content of the Gitkit token just as expected.

My problem comes with the verification. I'm unable to use
jose.jwt.decode()
for verification because I don't know which key I need to use.


jose.jwt.decode(token, key, algorithms=None, options=None, audience=None, issuer=None, subject=None, access_token=None)



I know the algorithm from the header and an 'aud' field is also stored in the claims, if that is of any help.

Getting back to the engineers comment


verify the Firebase Auth token (signature and audience)


How do I do that with the info I have avaliable? I guess audience is the 'aud' field in the claims, but how do I check the signature?

Once I've removed the Gitkit dependency on the server, I will continue with the migration.

From what I've seen, the GitKit library apparently makes an "RPC" call to a Google server for verification, but I may be wrong.

So, which will be the key for Gitkit token verification as well as the one for the Firebase token verification?

Answer

The keys can be obtained

for Firebase at https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com

and for Gitkit at https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys

Using Googles oauth2client library makes the verification very easy.

But if you want to use python-jose, then you first need to convert the PEM certificate into an RSA public key. This public key is then the key that needs to be used. And the audience should not be extracted from the JWT header, but hard coded into the source code, where in Firebase the audience is the project id and in Gitkit it is one of the OAuth 2.0 client IDs which can be found in the Google Developer Console Credentials section.

The kid in the JWT header is used to select the appropriate certificate which will be used to obtain the key used to perform the verification.

  # firebase
  # target_audience = "firebase-project-id"
  # certificate_url = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'

  # gitkit
  target_audience = "123456789-abcdef.apps.googleusercontent.com" # (from developer console, OAuth 2.0 client IDs)
  certificate_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys'

  response = urllib.urlopen(certificate_url)
  certs = response.read()
  certs = json.loads(certs)
  print "CERTS", certs
  print ''
  print ''

  # -------------- verify via oauth2client
  from oauth2client import crypt
  crypt.MAX_TOKEN_LIFETIME_SECS = 30 * 86400 # according to https://github.com/google/identity-toolkit-python-client/blob/master/identitytoolkit/gitkitclient.py
  print "VALID TOKEN", crypt.verify_signed_jwt_with_certs(idtoken, certs, target_audience)  
  print ''
  print ''

  # -------------- verify via python-jose
  from jose import jwt
  unverified_header = jwt.get_unverified_header(idtoken)
  print "UNVERIFIED HEADER", unverified_header
  print ''
  print ''
  unverified_claims = jwt.get_unverified_claims(idtoken)
  print "UNVERIFIED CLAIMS", unverified_claims
  print ''
  print ''
  from ssl import PEM_cert_to_DER_cert
  from Crypto.Util.asn1 import DerSequence
  pem = certs[unverified_header['kid']]
  der = PEM_cert_to_DER_cert(pem)
  cert = DerSequence()
  cert.decode(der)
  tbsCertificate = DerSequence()
  tbsCertificate.decode(cert[0])
  rsa_public_key = tbsCertificate[6]
  print "VALID TOKEN", jwt.decode(idtoken, rsa_public_key, algorithms=unverified_header['alg'], audience=target_audience)