Authorization management using Apigee Edge and Identity Platform

Hi everyone,

I'm developing a multi-tenant architecture based on Google Identity Platform and I need to manage authorization on specific API operations. 

The sign-in is performed on the client side of the application using Firebase and I would like to implement a process on Apigee Edge able to:

  • verify the provided token
  • refresh the token if expired

How can I achieve this goal?
Thanks in advance,

Giorgio

Solved Solved
0 9 783
1 ACCEPTED SOLUTION

Hi

As I understand, the sign-in generates an {ID token, refresh token} pair.  You want to do 2 things: verify the ID token, and refresh the ID token.

Let's take the first case: verifying the ID Token. The ID token is simply a signed JWT. The payload would look something like this: 

 

  {
    "iss":"accounts.google.com",
    "at_hash":"HK6E_P6Dh8Y93mRNtsDB1Q",
    "email_verified":"true",
    "sub":"10769150350006150715113082367",
    "azp":"1234987819200.apps.googleusercontent.com",
    "email":"jsmith@example.com",
    "aud":"1234987819200.apps.googleusercontent.com",
    "iat":1353601026,
    "exp":1353604926
  }
 

 

This JWT can be verified by any party that can use the JWKS published by google at this endpoint.  Apigee has a built-in VerifyJWT policy, which can verify signed JWT via JWKS. The necessary policy configuration to verify a token issued by Google Identity is something like this: 

 

<VerifyJWT name='JWT-VerifyGoogleIdToken'>
  <Algorithm>RS256</Algorithm>

  <!-- you may have to modify this to accommodate your needs -->
  <Source>gauth_id_token</Source>

  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uri='https://www.googleapis.com/oauth2/v3/certs'/>
  </PublicKey>
  <!-- 
  Not sure about issuer in your case.
  <Issuer>accounts.google.com</Issuer>
  -->
</VerifyJWT>

 

Attach that policy into your Apigee proxy. After your proxy executes that policy, you can be assured that the ID Token is valid, was issued by Google, is not expired, and so on.  The policy will extract all the claims from the payload (email, sub, possibly given name, etc) into context variables, which are accessible to subsequent policies in the API proxy flow.

If you want to validate that the iss claim is a particular value in the token (I am not sure if multi-tenant google identity uses a different issuer for each tenant or not), you can insert the Issuer element.  If you want to validate the aud claim has a particular value, use the Audience element.  Consult the documentation for the policy for more details on that. 

OK That checks that the ID token is valid. If the token is expired, or not signed by google, or not present in the Source you specify, etc.... then VerifyJWT will fail, will throw a fault, and you can configure your API proxy to return an appropriate error message back to the caller in that case.

As for the second case, refreshing the token. It's my understanding that to refresh a token, your app must send a POST like this: 

 

POST https://securetoken.googleapis.com/v1/token?key=[API_KEY]
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=[REFRESH_TOKEN]

 

 And of course you can configure ServiceCallout to do that sort of thing. 

But to make that happen, your API Proxy needs access to both the firebase API Key and the user's Refresh Token. 

Effectively you are making the Apigee API proxy "the firebase client" in this case. At this point APIGEE would have a new ID token for the user, and you'd need to relay that back to the original client application I suppose.  

 

View solution in original post

9 REPLIES 9

Hi

As I understand, the sign-in generates an {ID token, refresh token} pair.  You want to do 2 things: verify the ID token, and refresh the ID token.

Let's take the first case: verifying the ID Token. The ID token is simply a signed JWT. The payload would look something like this: 

 

  {
    "iss":"accounts.google.com",
    "at_hash":"HK6E_P6Dh8Y93mRNtsDB1Q",
    "email_verified":"true",
    "sub":"10769150350006150715113082367",
    "azp":"1234987819200.apps.googleusercontent.com",
    "email":"jsmith@example.com",
    "aud":"1234987819200.apps.googleusercontent.com",
    "iat":1353601026,
    "exp":1353604926
  }
 

 

This JWT can be verified by any party that can use the JWKS published by google at this endpoint.  Apigee has a built-in VerifyJWT policy, which can verify signed JWT via JWKS. The necessary policy configuration to verify a token issued by Google Identity is something like this: 

 

<VerifyJWT name='JWT-VerifyGoogleIdToken'>
  <Algorithm>RS256</Algorithm>

  <!-- you may have to modify this to accommodate your needs -->
  <Source>gauth_id_token</Source>

  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uri='https://www.googleapis.com/oauth2/v3/certs'/>
  </PublicKey>
  <!-- 
  Not sure about issuer in your case.
  <Issuer>accounts.google.com</Issuer>
  -->
</VerifyJWT>

 

Attach that policy into your Apigee proxy. After your proxy executes that policy, you can be assured that the ID Token is valid, was issued by Google, is not expired, and so on.  The policy will extract all the claims from the payload (email, sub, possibly given name, etc) into context variables, which are accessible to subsequent policies in the API proxy flow.

If you want to validate that the iss claim is a particular value in the token (I am not sure if multi-tenant google identity uses a different issuer for each tenant or not), you can insert the Issuer element.  If you want to validate the aud claim has a particular value, use the Audience element.  Consult the documentation for the policy for more details on that. 

OK That checks that the ID token is valid. If the token is expired, or not signed by google, or not present in the Source you specify, etc.... then VerifyJWT will fail, will throw a fault, and you can configure your API proxy to return an appropriate error message back to the caller in that case.

As for the second case, refreshing the token. It's my understanding that to refresh a token, your app must send a POST like this: 

 

POST https://securetoken.googleapis.com/v1/token?key=[API_KEY]
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=[REFRESH_TOKEN]

 

 And of course you can configure ServiceCallout to do that sort of thing. 

But to make that happen, your API Proxy needs access to both the firebase API Key and the user's Refresh Token. 

Effectively you are making the Apigee API proxy "the firebase client" in this case. At this point APIGEE would have a new ID token for the user, and you'd need to relay that back to the original client application I suppose.  

 

Thank you Dino, your suggestions solved my problem. 

However, for sake of completeness, I would like to share the complete VerifyJWT policy used, since some parameters are slightly different (as you already guessed).

Here the complete policy:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT async="false" continueOnError="false" enabled="true" name="Verify-JWT">
    <DisplayName>Verify JWT</DisplayName>
    <Algorithm>RS256</Algorithm>
    <Source>request.header.authorization</Source>
    <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    <PublicKey>
        <Certificate>
            -----BEGIN CERTIFICATE-----
            MIIDHDCCAgSgAwIBAgIIMlfQEsxzLgcwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE
            AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMjIw
            NDI1MDkzODM5WhcNMjIwNTExMjE1MzM5WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl
            bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
            ggEPADCCAQoCggEBANJxaMf81WKjMxCyUqZPXeM+m6oFjNxZT5EsRMUZ+eA0RgIs
            b6F+dusqO+GDgaYmBaYmS4xsSCj5tFUs2dD8SgXBN/RwjJtY0d8WPmylt/YT9W9a
            KAqPr1Ho8Z7E2np5JNOA0FZVvhaifHb3+OyxcqHSFK0y8ukUsvAwHpWfdEHLNgWv
            jWcNOIaxEWgpJKvYX8ZcxUY8+0aF0ZAkhW0KnO1PEvuECgAsP3dRavP3dqT4VPfb
            vRqpH49tx6ocZtGpbSkne9Z97SqMeEojnVPurR/lUKSPOTPJdP17fGnLnmhC1JuV
            o4oJno4KpVA0PK0tU/lRoOQblrDaY33fUMyTlQsCAwEAAaM4MDYwDAYDVR0TAQH/
            BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ
            KoZIhvcNAQEFBQADggEBACaBIevpf9WWwNLl0DlLPVFg4l75/KYiZcALvOx6O1jf
            8A9XnRVlV2+mrosza5I50uyp70ip4PKxQhqfRdtijT9pltWmheK6xECW+MQRLQlP
            DX8/YgBS0myTg3hf1GO/hfLPon+c7lUUJjRgQOqThtDNoWqP7DwdIghRWId6b+St
            +gEXKr2NuJnKgFPU3P22gp9dosNiTkbfYLWdJsrw8Bow6LrljxbuxNASv4X/p9Wo
            iPtTRysIk4RAiQ5mw45wrxWMzIP36Wbb2Ut6B3uW5YJj7tDwxZDoKDDpS3wgy1Mm
            LY0bN645n7XuAxb9m8/FWRV9n/i8yng6r8mQTabhwXY=
            -----END CERTIFICATE-----
        </Certificate>
    </PublicKey>
    <Issuer>https://securetoken.google.com/<my-project-id></Issuer>
</VerifyJWT>

Note that the issuer must end with the project-id of interest and instead of using the public key provided at 'https://www.googleapis.com/oauth2/v3/certs', the certificates available at 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com' should be used, according to the Firebase VerifyIDToken guide.

Thanks again Dino! 

GREAT

I am glad that helped you.

I understand the need to change the Issuer element in the VerifyJWT policy.

I see why you resorted to using the hard-coded Certificate element. The URL at googleapis.com , /robot/v1/metadata/x509/securetoken@system.gserviceaccount.com , returns a JSON list of certificates, each one in PEM format (with the -----BEGIN CERTIFICATE----- framing, etc). But there is an alternative URL that returns a standard JWKS: /robot/v1/metadata/jwk/securetoken@system.gserviceaccount.com. Basically just replace the x509 in the URL path with jwk. 

Which means your VerifyJWT policy configuration can be like this:

 

<VerifyJWT async="false" continueOnError="false" enabled="true" name="Verify-JWT">
    <DisplayName>Verify JWT</DisplayName>
    <Algorithm>RS256</Algorithm>
    <Source>request.header.authorization</Source>
    <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    <PublicKey>
      <JWKS uri='https://www.googleapis.com/robot/v1/metadata/jwk/securetoken@system.gserviceaccount.com'/>
    </PublicKey>
    <Issuer>https://securetoken.google.com/MY-PROJECT-ID</Issuer>
</VerifyJWT>

 

I think that is nicer. If Firebase ever updates their certs, you will get the right one.

Awesome, I was looking for a solution that would not require any intervention if the certificate expires, but I didn't know the valid URL (it was pretty obvious actually looking at your solution 😄 ).

Thanks for this additional suggestion!

What is JWKS uri of identity platform GCP, thanks ?

This is my config:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT continueOnError="false" enabled="true" name="check-jwt">
<DisplayName>check-jwt</DisplayName>
<Algorithm>RS256</Algorithm>
<Source>request.header.token</Source>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
<PublicKey>
<JWKS uri="https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"/>
</PublicKey>
<Issuer>https://securetoken.google.com/gogroup</Issuer>
</VerifyJWT>

 

 

When i test api, always response:

{
"fault": {
"faultstring": "Invalid Key configuration : policy(check-jwt) element(PublicKey)",
"detail": {
"errorcode": "steps.jwt.InvalidKeyConfiguration"
}
}
}

 

Do you know resolve this issue? Thanks.

Hi, can you post your config verify jwt policy for authen identity platform GCP,  i have same issues. Thanks !

Hi

the error you are seeing, "Invalid Key configuration " is telling you that the VerifyJWT policy cannot find a key at the JWKS URI you provided, which can be used to verify the signature on the JWT you provided.

can you post your config verify jwt policy for authen identity platform GCP,

The configuration shown above works for Firebase. I am not an expert on GCIP, but I think from this documentation the URL you want for your JWKS is

https://www.gstatic.com/iap/verify/public_key-jwk

And of course you will want to change the Issuer element, too. According to that doc it should be https://cloud.google.com/iap. And according to the documentation you need to use Algorithm ES256 If you show an example of your JWT token header and payload I could be more confident with all of this.