Best practices for tenant authorization using OAuth2 proxy for multi-tenant API.

Hi Community,

I am creating OAuth2 proxy for our GraphQL API endpoint. The API will be consumed by other services and different partners, our API is multi-tenant, so I need to identify the tenant on the API side. Since I am using OAuth2, on the API side I have access only to access_token - so I can't really identify the tenant.

What I have done so far:

  1. Created proxy for generating OAuth2 access token (GenerateAccessToken)
  2. Created proxy that verifies the access token

User first hits generateAccessToken proxy ("https://example-eval-test.apigee.net/oauth/accesstoken") with key/secret combination they get access_token and then continue to use the API by attaching the access_token on the second proxy ("https://example-eval-test.apigee.net/graphql").

My dilemmas are:

  • Is it a good practice to use key as tenant id?
  • What is the best practice for sending the key together with access_token? Should it be done in a header?
  • Should I use different variable instead, and ask partners to always send "tenant_id" together with the token? If so, what is the best practice to link APP (key) with the tenant?

I also had a look at JWT, Can I combine OAuth2 with JWT and have OAuth2 return JWT "access_token" so then I can easily decrypt that JWT token which will hold tenant data inside the API?

Solved Solved
0 8 2,055
1 ACCEPTED SOLUTION

  • Is it a good practice to use key as tenant id?

No. I am inferring by "key" you mean the API Key, also known as the client ID, also known as the consumer ID. And if that's what you mean, then the answer is no: don't use that. The reason is, this key may rotate, you may wish to expire it or revoke it, if it leaks or is lost. The model in Apigee allows you to configure a Developer, who owns multiple Apps, and for each app there can be multiple credentials (or keys). You want to use the Developer ID as the tenant ID. That way, if you revoke a key or create a new app under that developer, the tenant ID (Developer ID) remains stable.

  • What is the best practice for sending the key together with access_token? Should it be done in a header?

Don't. when you use an Apigee policy like OAuth/VerifyAccessToken, Apigee will load into the message context the metadata you seek, like client id, and from that you can load in the Developer ID, aka Tenant ID, aka partner ID. Do not ask the client to pass that information.

The way you "load in" the Developer ID is via an AccessEntity policy. Check the documentation, look at videos, and look here in community for some examples. It's easy.

  • Should I use different variable instead, and ask partners to always send "tenant_id" together with the token? If so, what is the best practice to link APP (key) with the tenant?

No. see above.

Can I combine OAuth2 with JWT and have OAuth2 return JWT "access_token" so then I can easily decrypt that JWT token which will hold tenant data inside the API?

Yes, you can do that too. I just wrote an article here on community about this. Check it out. I suggest that you don't NEED to use a JWT. You should use a JWT if there are good reasons to do it; Getting access to the tenant ID is not a good reason to use a JWT.

View solution in original post

8 REPLIES 8

Not applicable

I assume if you are using the password grant type then you are validating the user against any identity and access management system before generating token.

1. You can use the consumer key as tenant id.

2. Access token is a Bearer token. So you should send that as Authorization header. You need not to send the key for service request. Once the access token is validated the key will be automatically available in the proxy flow.

3. You need not to send the tenant id if you are using the consumer key as tenant id.

Indeed, access token is sent as Authorization header on the proxy that verifies the token. The token is validated but the key is not automatically available in the flow, at least I can not see it. Can you please point me out how to access the key after access token is validated?

apigee.client_id is the variable that gives the client key after the oauth token validation.

As Dino specified the client id change may impact if you use client id as the tenant id, We have used the same client id as a custom attribute in the developer app as the tenant id or subscriber id, so it stands good in our case.

Still if you have any other Identity and access management system then you can use that as tenant id and validate that during the token generation flow and keep that available in the token using the attribute in the token generation.

Don't use the consumer key as the tenant ID. The consumer key may change, may get revoked, may get leaked. Don't depend on it never changing. See my answer.

  • Is it a good practice to use key as tenant id?

No. I am inferring by "key" you mean the API Key, also known as the client ID, also known as the consumer ID. And if that's what you mean, then the answer is no: don't use that. The reason is, this key may rotate, you may wish to expire it or revoke it, if it leaks or is lost. The model in Apigee allows you to configure a Developer, who owns multiple Apps, and for each app there can be multiple credentials (or keys). You want to use the Developer ID as the tenant ID. That way, if you revoke a key or create a new app under that developer, the tenant ID (Developer ID) remains stable.

  • What is the best practice for sending the key together with access_token? Should it be done in a header?

Don't. when you use an Apigee policy like OAuth/VerifyAccessToken, Apigee will load into the message context the metadata you seek, like client id, and from that you can load in the Developer ID, aka Tenant ID, aka partner ID. Do not ask the client to pass that information.

The way you "load in" the Developer ID is via an AccessEntity policy. Check the documentation, look at videos, and look here in community for some examples. It's easy.

  • Should I use different variable instead, and ask partners to always send "tenant_id" together with the token? If so, what is the best practice to link APP (key) with the tenant?

No. see above.

Can I combine OAuth2 with JWT and have OAuth2 return JWT "access_token" so then I can easily decrypt that JWT token which will hold tenant data inside the API?

Yes, you can do that too. I just wrote an article here on community about this. Check it out. I suggest that you don't NEED to use a JWT. You should use a JWT if there are good reasons to do it; Getting access to the tenant ID is not a good reason to use a JWT.

Thank you for detailed explanation.

I successfully implemented the solution you advised.

So, when access token is verified via OAuth/VerifyAccessToken policy, I load the Developer ID with AccessEntity policy (I added custom attribute for the developers) and then I add that ID on the header to the target endpoint. This works fine.

Can I now verify that the tenant id on the header will never get compromised? Meaning, how can I make sure that tenant with valid access token always sends their tenantId. TenantId header is developer custom attribute, attached by Apigee policies, but still, I don't do any verification on application level and I have concerns that this header might be compromised by user with verified access token.

Can I create an policy that verifies that the tenantId header is always the same with the developer's attribute that is making the request?

Probably this is an edge case, but I would really like to an opinion on this.

Can I now verify that the tenant id on the header will never get compromised? Meaning, how can I make sure that tenant with valid access token always sends their tenantId. TenantId header is developer custom attribute, attached by Apigee policies, but still, I don't do any verification on application level and I have concerns that this header might be compromised by user with verified access token.

It sounds like you are concerned that an attacker might somehow know one or more tenantid's. And the attacker might send requests directly to the upstream, with a tenantid in the header. How will the upstream system be assured that the sender has validated a token, and has validated that the token is authorized for the tenantid? Is that right? Is that what you're concerned about?

There are multiple layers of security you can use to mitigate this risk.

  1. TLS for transport layer security. Ideally the endpoint for the upstream enforces mutual authentication on the TLS session, ensuring that it accepts requests only if they originate from trusted connections - including Apigee. That means even if an attacker knows a tenantid, and even if the attacker knows the endpoint address for the upstream system, the upstream will reject any request from an attacker, during the TLS handshake, because the attacker lacks the appropriate key+cert for the TLS authentication. To make this happen, you will need to provision an x509 cert and a private key for each endpoint: Apigee and the upstream.
  2. Application-level Signing. If you are not satisfied with transport-layer security, then you can also layer in application-layer security. For example, the Apigee proxy could generate a signed JWT, which embeds the tenant ID in the protected payload. The upstream would then need to verify the signature on the JWT, before trusting the tenantid asserted within the JWT. (Maybe this is what you were thinking in your original question, when you mentioned JWT). To make this happen, you will need to provision a public/private key pair for Apigee, and make the public key available to the upstream so that the upstream can verify signatures.
  3. Network-layer restrictions. If possible, it is preferable to physically secure the network link between Apigee and the upstream system. You cannot do this with "classic" Apigee, but it is possible with the recently launched Apigee X. Essentially you are constraining the network connectivity to prevent "external attackers"; any attack would have to originate from the internal network. This eliminates a large attack surface area: The data exchanged between Apigee and the upstream system no longer traverses on a public network. To make this happen, you must provision a VPN or an Interconnect to link between Apigee and the upstream, or you must locate the upstream within a GCP cloud project that is linked to the Apigee gateway via the internal google cloud network.

These options are not mutually exclusive. Network security is most foundational. TLS is REQUIRED, in my opinion. And the application-layer signing may also be valuable if you are concerned about internal attackers exploiting TLS 0-day vulnerabilities.

Can I create an policy that verifies that the tenantId header is always the same with the developer's attribute that is making the request?

I don't understand this part of the question. I think the client in this case sends one thing - the token. Apigee stores the tenantid on the developer app - this is configured in some way, as a one-time action, at the time the client (api key) is provisioned. A good place to make this happen is in the developer portal. When your proxy validates the token, it extracts the tenantid from the developer account. We have certainty that the tenantid is valid for the token.

If you are concerned about leakage of tenantids, and the possibility that some attacker might send a request with a tenantid to the upstream, then you need to take one or more of the above steps to secure the connection between the Apigee proxy and the upstream.