GoogleCloudPlatform / iap-gcip-web-toolkit

Apache License 2.0
50 stars 23 forks source link

Firebase UI: ES256 required over RS256 (or vice-versa?) #35

Open ccancellieri opened 3 years ago

ccancellieri commented 3 years ago

Dear all, I'm trying to validate the received token (x-goog-iap-jwt-assertion) against Firebase in a python application over a Compute Engine which is configured as following:

requirements.txt

firebase_admin==2.2.0 (imposed by the environment)

Snippet of code to configure firebase_admin

import firebase_admin
from firebase_admin import auth

firebase_admin.initialize_app()

Which successfully configure the admin library over the VM (properly configured in the IAM).

Here https://github.com/GoogleCloudPlatform/iap-gcip-web-toolkit/blob/2a5d8808e07d29c13cdbac8f7a40af4da02512d2/sample/app/server/verify-iap-jwt.js#L19

I see that you are explicitly asking for an ES256 JWT token while here: https://github.com/firebase/firebase-admin-python/blob/f2b4f19e233d199b6c0200b9696fbf15a73dd015/firebase_admin/_token_gen.py#L303

I see that the python library is forcing RS256 so on my application trying to validate the received token (x-goog-iap-jwt-assertion) I'm getting:

Firebase ID token has incorrect algorithm. Expected "RS256" but got "ES256". See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.

The IAP-gcip application is deployed AS IS using cloud Run and a single tenant configuration. imageDigest: gcr.io/gcip-iap/authui@sha256:11a871958769d45544794db8c22af958279817a213717e4f793993cc6433bc99

Is this an issue?

Thanks, C.

bojeil-google commented 3 years ago

I think you are confusing the IAP signed JWT with the GCIP/Firebase Auth tokens. The latter is exchanged for the former. The IAP gated app will receive the former and not the latter.

If you are verifying IAP signed JWTs, use this documentation. Multiple samples in various languages are also available.

ccancellieri commented 3 years ago

Thanks bojeil for pointing this out, I'm trying to use the IAP proxy as an endpoint actually, let me explain why:

I've a DOMAIN/SERVICE application which is intended for free usage but clicking on a 'login' button my python backend Compute Engin application starts the challenge redirecting to an nginx instance (on a different machine) which is 'protected' by the IAP at that point the real challenge starts between the external nginx and the IAP (I'm redirected to the login page and, on success, back to the nginx proxy which redirects to my application (shipping the x-goog-iap-jwt-assertion header)

So actually I see an RS256 token Issued by the IAP and passed to my external nginx proxy called: x-iap-3p-token but that's hidden by the proxy and I'm receiving only the x-goog-iap-jwt-assertion

Questions:

  1. I'd love to be able to redirect from my application directly to the IAP proxy but the protocol looks unclear: apikey known mode=login state=? State parameter looks mandatory but it has an encrypted body and I don't know how to generate the right value for that. Using firebase_admin looks like there is no direct call (like in oauth2 libraries) to start the challenge getting back an usable tokenid.... If firebase_admin does not provide that functionnality Is it possible to successfully implement the protocol (documented?) or use another library in python?

  2. Do you have any hints or suggestions to use the IAP as endpoint rather than a proxy (limiting access/identifying over the service on-demand)?

  3. Looking at the received token (x-goog-iap-jwt-assertion) I'm able to Identify the user and verify the token (signed) but I'm missing the renewal of the token on expiration or grant withdrawal. I've though to check the token expiration and redirect to the auth loop again but I would prefer to be able to explicitly call the Firebase backend and ask for token_validation (which may also renew it). How to do that in python only having the x-goog-iap-jwt-assertion token?

I think that centralizing the interface and the control of the tenants and IdentityProvider over a cloud Run application is really scalable and give us the possibility to do not maintain multiple interfaces and configurations (one for each application) but I think that the proxy pattern is limiting the usage of the IAP-UI for the descripted scenario but maybe I'm misunderstanding again....

thanks for any hint you would like to share. Carlo

bojeil-google commented 3 years ago

You can't manually construct the sign-in URL. There are security required parameters and cookies that need to be generated by IAP before the redirect.

The firebase_admin sdk has no relation at all with IAP and only provides utilities to manage GCIP users and nothing more for this use case.

The JWT is renewed every hour. Our documentation explains how this works and how to auto-renew it. If you need to call an API behind IAP, please refer to this documentation.

ccancellieri commented 3 years ago

Very useful hints! So no api to manage the authentication as an endpoint is available in python. I will use redirection to renew the token as explained by the documentation, thanks for your availability. It's definitely not an issue, apologize for noise, we can close.

bojeil-google commented 3 years ago

BTW, you can make parts of your GCE based app protected and parts unprotected. You can have multiple backend services behind on single GCLB with different path forwarding rules, eg. foo.com/a -> backend service A, foo.com/b -> backend service B and IAP is only enabled on backend service A. This is equivalent to enabling IAP on path /a. I don't know if this is relevant to your use case.

ccancellieri commented 3 years ago

Thanks, we're using that for all of our component, it is easy to configure and works very well. The challenge is to be able to go to foo.com/a as un'authenticated user and clicking on a login button come back to the same application foo.com/a with an identified user having also some additional permissions on the service.

We addressed that as explained above, (nginx etc) the session will be held by the backend and the user is also created automatically on the 'a' service to provide specific permissions on the domain objects.

What I was missing was the token renewal/withdrawal, I thought it was possible to address that task in behind using a library but the same can now be addressed using redirection which is not so transparent to the users but will works I hope ;)

Many thanks again for your interest, my best, Carlo

ccancellieri commented 3 years ago

The JWT is renewed every hour. Our documentation explains how this works and how to auto-renew it.

Sure the RSA token issued by: u'iss': u'https://securetoken.google.com/XXXX' has 1h expiration but the IAP signed token provided after the login expires after 10 minutes, example:

Signed JWT DECODED Example:

{
 u'aud': u'/projects/XXXXXXXX/global/backendServices/XXXXXX',
 u'iss': u'https://cloud.google.com/iap',
 u'gcip': {
  u'picture': u'XXX/photo.jpg',
  u'user_id': u'XXXX', 
  u'name': u'Carlo Cancellieri', 
  u'email_verified': True, 
  u'firebase': {
   u'sign_in_provider': u'google.com', 
   u'identities': {
      u'google.com': [u'XXXXXXXXX'], u'email': [u'carlo.cancellieri@XXXX']
   }
 },
 u'auth_time': 1603305582,
 u'email': u'carlo.cancellieri@XXX', 
 u'sub': u'XXXXX'}, 
 u'exp': 1603363314,
 u'iat': 1603362714,
 u'email': u'securetoken.google.com/XXXXXXX:carlo.cancellieri@XXXX',
 u'sub': u'securetoken.google.com/XXXXXXX:YYYYY'
}

Now if you check the exp and the iat we get exp: Thu Oct 22 2020 10:41:54 iat: Thu Oct 22 2020 10:31:54

Is there a way to configure the expiration time of the signed token? (can't find anything in the documentation)

My application is partially stateful and a redirection between a content update (POST) can be catastrophic so I'd like to extend it (I know it should be short-lived but 10 min is too short to edit data with no pain).

bojeil-google commented 3 years ago

This works as intended. IAP will issue a signed JWT on every request with an expiration of 10 mins as long as there is a valid GCIP ID token. The IAP JWT is verified on request. I am not sure why you need it beyond the 10mins.