Single API, two different OAuth access methods

I'd like to create API supporting two different use cases. How should the authentication, proxy etc. to configure in APIgee Edge?

1) API is consumed by application where end-user is authenticated in third-party identity provider. I'm not interested how authentication has been done in the app, but app has anyway got access token from identity provider. When request comes to APIgee platform with this external access token, I just want to make sure the token is valid and forward request to backend.

2) Same API than earlier, but now the API consumer is application where end-user is not authenticated and can't do that. We can provide OAuth 2.0 client credential authentication internally in APIgee for consuming application. App is authenticated in APIgee and it provides access_token to app.

So, API request can have external or internal access token depending on the client. Can I somehow provide single API endpoint that could somehow handle both use cases? At least I don't want to implement two totally separate APIs.

Solved Solved
2 1 678
1 ACCEPTED SOLUTION

Well sure, you can do that in Apigee Edge. The key is to insert a condition that examines the type of authentication. You will need a way to distinguish between requests that carry an "external access token" and an Apigee-issued token. I guess the form of both will be to pass a token in the Authorization header in the normal fashion, like this:

Authorization: Bearer TOKEN_GOES_HERE

By the way, I am inferring from your description that when you say "external access token", you are referring to a token that Apigee Edge does not store. The reason I point this out: it is possible for Apigee Edge to "ingest" an externally-generated access token and to then store it. Validation of this externally-generated access token would work the same way as validation for a token that is generated by Apigee Edge directly. I want to clarify that this is not the scenario you are referring to.

I am imagining that the "external access token" is a JWT. You said "I just want to make sure the token is valid". Supposing it is a JWT, and you have the public key that allows you to validate the token, you can use the VerifyJWT policy to validate it. For an Apigee-generated (opaque) OAuth token, you would use the OAuthV2/VerifyAccessToken policy to verify.

In either case after verification your API Proxy can pass through the request.

The only tricky bit is distinguishing the JWT from the opaque token.

The way I would do this, look for the JWT format - a string consisting of 3 segments separated by dots. If you see this, then assume it's the externally-provided JWT you want to accept; in this case call VerifyJWT. If not, then treat it as an opaque token; call VerifyAccessToken.

The flow looks like this:

    <Flow name="flow1">
      <Request>
        <Step>
          <Name>RaiseFault-MissingAuthentication</Name>
          <Condition>request.header.Authorization = null</Condition>
        </Step>


        <Step>
          <Name>VerifyJWT</Name>
          <Condition>request.header.Authorization ~~ "[Bb]earer *[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}"</Condition>
        </Step>


        <Step>
          <Name>OAuthV2_VerifyToken</Name>
          <Condition>NOT(request.header.Authorization ~~ "[Bb]earer *[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}")</Condition>
        </Step>


      </Request>
      <Response>
        <Step>
          <Name>AM-Response</Name>
        </Step>
      </Response>
      <Condition>(proxy.pathsuffix MatchesPath "/t1") and (request.verb = "GET")</Condition>
    </Flow>


To be clearer and comply with the DRY principle, you could evaluate that regex once, and store the result in a variable, and then just test the variable. like this:

    <Flow name="flow1">
      <Request>
        <Step>
          <Name>RaiseFault-MissingAuthentication</Name>
          <Condition>request.header.Authorization = null</Condition>
        </Step>


        <Step>
          <Name>AssignVariable_UsingJWT</Name>
          <Condition>request.header.Authorization ~~ "[Bb]earer *[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}"</Condition>
        </Step>


        <Step>
          <Name>VerifyJWT</Name>
          <Condition>using_jwt != null</Condition>
        </Step>


        <Step>
          <Name>OAuthV2_VerifyToken</Name>
          <Condition>using_jwt = null</Condition>
        </Step>

      </Request>
      <Response>
        <Step>
          <Name>AM-Response</Name>
        </Step>
      </Response>
      <Condition>(proxy.pathsuffix MatchesPath "/t1") and (request.verb = "GET")</Condition>
    </Flow>


This bit of hieroglyphics

request.header.Authorization ~~ "..."

is just testing the Authorization header against a regular expression. The regex I used is pretty simple and it tests for the form of a JWT. The AssignVariable_UsingJWT would have to be something like this:

<AssignMessage name='AssignVariable_UsingJWT'>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <AssignVariable>
    <Name>using_jwt</Name>
    <Value>true</Value>
  </AssignVariable>
</AssignMessage>

One or the other Verify policy will execute, and will throw a fault if the token is invalid.

View solution in original post

1 REPLY 1

Well sure, you can do that in Apigee Edge. The key is to insert a condition that examines the type of authentication. You will need a way to distinguish between requests that carry an "external access token" and an Apigee-issued token. I guess the form of both will be to pass a token in the Authorization header in the normal fashion, like this:

Authorization: Bearer TOKEN_GOES_HERE

By the way, I am inferring from your description that when you say "external access token", you are referring to a token that Apigee Edge does not store. The reason I point this out: it is possible for Apigee Edge to "ingest" an externally-generated access token and to then store it. Validation of this externally-generated access token would work the same way as validation for a token that is generated by Apigee Edge directly. I want to clarify that this is not the scenario you are referring to.

I am imagining that the "external access token" is a JWT. You said "I just want to make sure the token is valid". Supposing it is a JWT, and you have the public key that allows you to validate the token, you can use the VerifyJWT policy to validate it. For an Apigee-generated (opaque) OAuth token, you would use the OAuthV2/VerifyAccessToken policy to verify.

In either case after verification your API Proxy can pass through the request.

The only tricky bit is distinguishing the JWT from the opaque token.

The way I would do this, look for the JWT format - a string consisting of 3 segments separated by dots. If you see this, then assume it's the externally-provided JWT you want to accept; in this case call VerifyJWT. If not, then treat it as an opaque token; call VerifyAccessToken.

The flow looks like this:

    <Flow name="flow1">
      <Request>
        <Step>
          <Name>RaiseFault-MissingAuthentication</Name>
          <Condition>request.header.Authorization = null</Condition>
        </Step>


        <Step>
          <Name>VerifyJWT</Name>
          <Condition>request.header.Authorization ~~ "[Bb]earer *[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}"</Condition>
        </Step>


        <Step>
          <Name>OAuthV2_VerifyToken</Name>
          <Condition>NOT(request.header.Authorization ~~ "[Bb]earer *[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}")</Condition>
        </Step>


      </Request>
      <Response>
        <Step>
          <Name>AM-Response</Name>
        </Step>
      </Response>
      <Condition>(proxy.pathsuffix MatchesPath "/t1") and (request.verb = "GET")</Condition>
    </Flow>


To be clearer and comply with the DRY principle, you could evaluate that regex once, and store the result in a variable, and then just test the variable. like this:

    <Flow name="flow1">
      <Request>
        <Step>
          <Name>RaiseFault-MissingAuthentication</Name>
          <Condition>request.header.Authorization = null</Condition>
        </Step>


        <Step>
          <Name>AssignVariable_UsingJWT</Name>
          <Condition>request.header.Authorization ~~ "[Bb]earer *[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}\.[-_a-zA-Z0-9]{2,}"</Condition>
        </Step>


        <Step>
          <Name>VerifyJWT</Name>
          <Condition>using_jwt != null</Condition>
        </Step>


        <Step>
          <Name>OAuthV2_VerifyToken</Name>
          <Condition>using_jwt = null</Condition>
        </Step>

      </Request>
      <Response>
        <Step>
          <Name>AM-Response</Name>
        </Step>
      </Response>
      <Condition>(proxy.pathsuffix MatchesPath "/t1") and (request.verb = "GET")</Condition>
    </Flow>


This bit of hieroglyphics

request.header.Authorization ~~ "..."

is just testing the Authorization header against a regular expression. The regex I used is pretty simple and it tests for the form of a JWT. The AssignVariable_UsingJWT would have to be something like this:

<AssignMessage name='AssignVariable_UsingJWT'>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <AssignVariable>
    <Name>using_jwt</Name>
    <Value>true</Value>
  </AssignVariable>
</AssignMessage>

One or the other Verify policy will execute, and will throw a fault if the token is invalid.