OAuth with external IdP, with Apigee in the role of client application

Not applicable

I'm having trouble fitting our authentication/authorization use case into the OAuth flows as envisaged by the Apigee documentation.

I believe this is because as we envision our use case, Apigee would need to be playing the OAuth role of the client app, with our back-end APIs in the role of resource servers. In addition, we are using an external OAuth identity provider, and it's not entirely clear from the Apigee documentation concerning external OAuth tokens how we'd configure what we want to do (or if we need to create our own custom policies).

Here is the auth code flow we would like to implement:

1. Browser makes a request for an endpoint protected by Apigee. I.e., the resource owner's user agent makes a request to the client application (Apigee) for Apigee to access on behalf of the resource owner a protected resource (the back-end API) and send back the resource to the browser.

2. Apigee sees that this browser session is not authenticated and redirects the browser to the external identity provider (auth server).

3. The resource owner authenticates with the identity provider and authorizes Apigee to access the APIs on behalf of the resource owner.

4. The auth server redirects the browser back to Apigee with an auth code.

5. Apigee uses the auth code to make a back-channel request to the identity provider to obtain an access token. (This is signed JWT containing claims, but Apigee doesn't look at this information.)

6. Apigee makes an upstream request to the resource server, to exchange the IdP's token for a resource server token. [I understand that this is not part of the standard auth code flow.]

7. Apigee stores the token received from the resource server.

8. Apigee redirects the browser back to the originally requested endpoint.

9. Thereafter (for the duration of the browser session) when accessing upstream resources in response to requests from the browser, Apigee adds the resource server's token to the appropriate header.

What's important to us here is that, because the identity provider and resource server tokens contain claims and other information we don't want exposed (even if encrypted), we don't want them to be sent back to the browser or visible to external user agents.

I've looked at the Apigee OAuth documentation, particularly with regard to using third-party OAuth tokens, and have also studied the musicapi-oauth-delegated-authentication tutorial (https://github.com/dzuluaga/apigee-tutorials/tree/master/apiproxies/musicapi-oauth-delegated-authentication). However, the exact meaning of the configurations for the OAuthV2 policy are not specified clearly enough for me to understand how to manage the configuration in this particular case, nor to understand what I might need to implement in a custom policy. Might someone help me with this?

EDIT

I think I'm beginning to piece this together. Using the example of the musicapi-oauth-delegated-authentication tutorial, I think this is how the story would go:

1. All the back-end endpoints protected by Apigee would be protected by an OAuthV2 VerifyAccessToken policy, which would result in a redirect to the identity provider if the token is invalid or absent.

2. After login, the identity provider would redirect back to Apigee's /generatetokens endpoint with the auth code

3. The part that is missing in the example on GitHub is that service callouts that need to be performed before the OAuthV2 Store External Token policy is called. In the example on GitHub, before this policy is called there is an AssignMessage policy that sets the oauth_external_authorization_status flow variable to true. In real life, what has to happen instead is to have a service callout which POSTs the Identity Provider's auth code to its token endpoint in order to receive back an auth token. Only if this succeeds should oauth_external_authorization_status be set to true, otherwise it should be false (which means that the access token won't be generated and saved, which it shouldn't be).

4. For the flow I described in my original question, I will also need a second service callout to the upstream resource server's token endpoint to receive the upstream token. In that case, oauth_external_authorization_status shouldn't be set to true unless this service callout also successfully returns a token.

5. Once this token is returned, I need to save it so that it can be added to the headers when subsequent requests come in from the user. This part I'm not clear about.

6. Now the OAuthV2 Store External Token policy is invoked to generate an Apigee access token and store it.

7. This token is supplied to the user agent and is used in subsequent requests.

8. Requests with a valid Apigee access token are passed through to the back-end API with an added header containing the upstream token.

Does that sound right?

If so, the only things I'm vague about now are:

1. Saving the upstream token

2. Handling token refresh -- the Identity Provider's access token expires after a certain amount of time; the Identity Provider supplies a refresh token along with the access token. It's still not entirely clear to me how this fits into the flow. It's looking to me like I'm going to have to manually code refreshing the token from the identity provider; is that right? I imagine that the token issued by Apigee also expires, so I'm not sure how best to handle the interaction between these expirations.

EDIT #2

OK, so I am experiencing one additional difficulty. As a result of the success or failure of the service callouts I need to set oauth_external_authorization_status. I'm not sure how to do that with an Extract Variables policy, since servicecallout.RequestAuthToken.failed is boolean so it won't match a text pattern, and I don't know how to create a boolean value in an Extract Variables policy either. How do I accomplish this?

2 19 4,820
19 REPLIES 19

Not applicable

I think I'm beginning to piece this together. Using the example of the musicapi-oauth-delegated-authentication tutorial, I think this is how the story would go:

1. All the back-end endpoints protected by Apigee would be protected by an OAuthV2 VerifyAccessToken policy, which would result in a redirect to the identity provider if the token is invalid or absent.

2. After login, the identity provider would redirect back to Apigee's /generatetokens endpoint with the auth code

3. The part that is missing in the example on GitHub is that service callouts that need to be performed before the OAuthV2 Store External Token policy is called. In the example on GitHub, before this policy is called there is an AssignMessage policy that sets the oauth_external_authorization_status flow variable to true. In real life, what has to happen instead is to have a service callout which POSTs the Identity Provider's auth code to its token endpoint in order to receive back an auth token. Only if this succeeds should oauth_external_authorization_status be set to true, otherwise it should be false (which means that the access token won't be generated and saved, which it shouldn't be).

4. For the flow I described in my original question, I will also need a second service callout to the upstream resource server's token endpoint to receive the upstream token. In that case, oauth_external_authorization_status shouldn't be set to true unless this service callout also successfully returns a token.

5. Once this token is returned, I need to save it so that it can be added to the headers when subsequent requests come in from the user. This part I'm not clear about.

6. Now the OAuthV2 Store External Token policy is invoked to generate an Apigee access token and store it.

7. This token is supplied to the user agent and is used in subsequent requests.

8. Requests with a valid Apigee access token are passed through to the back-end API with an added header containing the upstream token.

Does that sound right?

If so, the only things I'm vague about now are:

1. Saving the upstream token

2. Handling token refresh -- the Identity Provider's access token expires after a certain amount of time; the Identity Provider supplies a refresh token along with the access token. It's still not entirely clear to me how this fits into the flow. It's looking to me like I'm going to have to manually code refreshing the token from the identity provider; is that right? I imagine that the token issued by Apigee also expires, so I'm not sure how best to handle the interaction between these expirations.

> We don't want to relay this information through the user agent, so we want to use the auth code flow and not the implicit flow. This means that it's technically not correct to consider the client as an app running on the user's mobile device; from the point of view of the OAuth auth code flow, the only thing on the the resource owner's side is a user agent.

yes, correct. If I mis-stated that or was unclear, I apologize. The way it works is: there is a login-and-consent app, which uses the IdP to authenticate and collect consent from the user. It's not simply a login, because we want to also ask the user "do you consent to allow this third-party app to have access to X, Y, and Z?" The login-and-consent app, after it has authenticated the user and collected consent, sends in the claims about the user to Apigee Edge, to request a code. Edge generates an authorization code, attaching all the claims returned by the L-and-C app, and then returns the new code to the Login-and-consent app. The login and consent app returns the code to the user agent via 302.

The user-agent then follows the 302 to Apigee Edge, to exchange the code for a token. Apigee Edge generates the token and associates all the claims from the one-time-use code, to the unique token. Some of those claims *may* optionally be returned to the client with the token, but that's not necessary. It's your option whether you'd like to keep those claims private or not.

Here's a 9-minute video showing this.

Not applicable

OK, so I am experiencing one additional difficulty. As a result of the success or failure of the service callouts I need to set oauth_external_authorization_status. I'm not sure how to do that with an Extract Variables policy, since servicecallout.RequestAuthToken.failed is boolean so it won't match a text pattern, and I don't know how to create a boolean value in an Extract Variables policy either. How do I accomplish this?

Hi Pblair,

Normally, Apigee Edge acts as an OAuth token dispensary and validator. This situation is typical:

  • There's a backend system; it doesn't "speak OAuth".
  • There's an Identity provider. It basically validates user credentials, and provides metadata about authenticated users. Group affiliation, roles for a user, email address, and so on. The IdP is an LDAP-accessible directory, or a service that looks kinda like an LDAP-accessible directory. It's not a token dispensary.

The way Apigee Edge is normally used -- and I think this is what Diego's Delegated Authentication example does - it delegates *just the authentication* to the IdP. Then Apigee creates a token, which contains the metadata (the claims, if you like) obtained from the IdP. This is done through the 3-legged OAuth 2.0 flow, with the redirection that I think you already understand. The token is normally opaque to the user and the client app (let's say, an app running on a mobile device). The metadata attached to the token can be delivered back to the client app with the token, or can be hidden from the client app. In other words, Apigee Edge can respond with something like this:

{ 
  "access_token" : "ABCDEFGHIJK",
  "expires" : 1468368243,
  "expires_readable" : "2016/07/12-17:04:03",
  "refresh_token" : "XYZ1234456"
}

Or, alternatively, with something like this:

{ 
  "access_token" : "ABCDEFGHIJK",
  "expires" : 1468368243,
  "expires_readable" : "2016/07/12-17:04:03",
  "refresh_token" : "XYZ1234456",
  "roles" : [ "reader", "employee", "admin" ],
  "user_email" : "chris@example.org",
  "authentication_mechanism" : "pw"
  ...
}

And if you choose this latter option, the list of "custom" attributes in the token response is up to you. When Apigee Edge is the token dispensary, You design the token response the way you like. Regardless whether you choose a minimalistic token response, or a more verbose token response, in either case it is possible to attach metadata to the token that is not shared with the token requester, and not present in the token response.

When the app makes a new request, it presents the opaque access token in some way to Apigee Edge, usually in the Authorization header. In other words, just the string "ABCDEFGHIJK". Apigee Edge then validates that the token is known, is valid for the requested operation, is not expired (and so on). If you like, you can configure Apigee Edge to validate that the token has particular claims - for example that the authenticated user possesses certain roles. All of the metadata that was "attached" to the token at the time it was created in Apigee Edge will be available to Edge when the token is validated.

If that token is valid, not expired, and passes any/all the other custom checks, then Apigee Edge proxies the request to the backend system. In some cases, there's an extra flourish, in which Apigee Edge simply injects a header for the backend to use. This might be the user's identity, user uuid, user roles, or any other information that is attached to the token in the token store within Apigee Edge.

The backend can then trust the injected header, and modify its operation based on that information.

The above is typical. There are options. For example, some customers want an OpenID Connect flow to generate the token, and they want the token to be a JWT. Apigee Edge can generate JWT as well, according to the same flow described above. In this case, the token is not opaque, and when it is sent back to the client app, the client app can inspect the token and see the claims. a JWT is nice if you need the token to be validated by something other than Apigee Edge. In other words if you'd like federated authorization.

If for some reason you would like to have some claims be visible to the client app, and some claims to NOT be visible, then you can combine the two. In other words you can create a JWT, and use the JTI (unique identifier of the JWT) to be an opaque OAuth token. When the JWT is presented, Apigee Edge can verify the signature, then extract the JTI, then retrieve an extra set of hidden claims associated to the JTI.

What you are imagining is not the above.

I think you are imagining a scenario in which a separate token dispensary exists in the network. And somehow you want to knit that together with Apigee Edge, and use tokens from both parties. It seems somewhat complicated, and I'd advise against it if at all possible. You don't need a separate token dispensary if you have Apigee Edge, and an IdP.

To answer your specific question about how to set the variable oauth_external_authorization_status, you do not do this with ExtractVariables. You would do it with AssignMessage.

Thanks, Dino -- not sure how I missed that section on flow variables. This helps a lot.

Not applicable

@Dino, this is still a little unclear to me. If just the authentication is delegated to the IdP, I'm assuming that Apigee still needs to receive some sort of response from the IdP containing the claims. We don't want to relay this information through the user agent, so we want to use the auth code flow and not the implicit flow. This means that it's technically not correct to consider the client as an app running on the user's mobile device; from the point of view of the OAuth auth code flow, the only thing on the the resource owner's side is a user agent.

As I understand it the auth code flow would look like this:

1. User agent requests resource from Apigee without an Apigee-generated auth token.

2. Apigee redirects to IdP for authentication.

3. IdP redirects back to Apigee's /generatetoken endpoint with an auth code.

4. Apigee makes back-channel request to IdP with the auth code using a service callout.

5. If the auth code is valid, IdP responds back. Here what we get from our IdP out-of-the-box is a JWT containing an access token, a refresh token, an ID token, claims, and other metadata.

6. Apigee generates a token and stores something-or-other. My impression is that Apigee stores the token it generates, in which case we'd need also to store the claims returned by the IdP somehow.

7. User agent receives the Apigee token and uses it in subsequent requests.

8. Apigee proxies subsequent requests to the back-end with a new header, containing user ID and roles.

Is this the flow you are describing?

If so, in step 6 what does Apigee store, and how do I get the IdP claims back for step 8? Do we take the metadata off the IdP response and add it as token metadata that is not returned to the user agent? The Delegated Authentication example doesn't seem to give us any guidance here; in fact, I don't see any implementation of steps 4 and 5 at all.

I take it that since Apigee plays the role of token dispenser in this scenario, that we then just ignore the access and refresh tokens generated by the IdP? Is there any token refresh at all under the scenario you describe?

You're correct that the upstream system doesn't speak OAuth, but it does issue its own tokens. We can't currently make modifications to the upstream system, so we'll still have to obtain an upstream token and store it. This would mean adding a step to obtain the upstream token, and figuring out how to refresh it. But I think that if I understand the basic flow then adding this extra wrinkle may not be too hard.

Not applicable

Thinking about this more, I'm thinking that using Apigee Edge as the token dispensary only adds unnecessary complexity here. A simpler flow would be:

1. User agent requests resource from Apigee without an authenticated session

2. Apigee redirects to IdP/auth service for authentication

3. IdP redirects back to originally requested Apigee URL, with an auth code

4. Apigee sees an auth code query parameter and an unauthenticated session. Apigee makes a back-channel request to the auth service for an access token.

5. If Apigee receives a valid token, Apigee stores the token and marks the session as authenticated.

6. Apigee then serves the requested resource and subsequent requests for resources from the same session.

I believe this is the way the authorization code flow operates with OpenID connect.

Here the question I'd have is, how do we implement the concepts of "authenticated session" and "store the token"? Would this be done using the cache?

> Apigee Edge as the token dispensary only adds unnecessary complexity here

The flow you describe is exactly the flow that would apply with Edge as the token dispensary, except that the redirects and callbacks are reversed. No additional complexity.

re: OpenID Connect, here's a replay of a webcast that I did last year showing an OpenID Connect flow with Apigee Edge. It can use *any* IdP.

> Here the question I'd have is, how do we implement the concepts of "authenticated session" and "store the token"? Would this be done using the cache?

No - if you are going to import a token into Edge, then you'd use OAuthV2/GenerateAccessToken and specifically the ExternalAccessToken element in that policy.

See here for more details.

My point is that there's no point to using Apigee Edge as the token dispensary, because our IdP already is a token dispensary, and authenticating with the IdP already generates another token. Having Edge generate yet another token isn't particularly useful.

With regard to OpenId Connect, I've watched the video, but OpenId Connect is more than JWT; it's not at all clear how that presentation is pertinent to the scenario I'm discussing. In particular, our scenario is one in which the JWT is not returned to the user agent. It is returned to the client, but the client is not necessarily running in the user agent, and in our case it isn't.

I've been over the "Using third-party OAuth tokens" page numerous times, and the problem is that the documentation isn't clear. For example, the page says "Upon finding the external token or code, Edge goes ahead and mints either an access token or an authorization code of its own....Set the <StoreToken> element in OAuthV2 to true. If this is false, then the token will not be persisted." Which token is persisted? How is it persisted? How do I later access the claims that were returned with the external token?

ok, maybe *this* will be helpful? 9 minutes showing OpenID Connect in Apigee Edge. In your case, you could have the JWT generated by your login-and-consent app returned to Apigee Edge (with all of its claims). At that point Edge will generate a code, return it to the login-and-consent app, and that L-and-C app sends a 302 to the user-agent, which exchanges the code for an opaque token. Only Edge knows the relationship between the opaque token and all the claims.

Regarding the persistence of the various tokens. I believe what is persisted is the text value of the ExternalAccessToken element.

To access claims, you need to use the custom Attributes capability in Edge, to attach attributes to the persisted token, at the time you use GenerateAccessToken. These can be individual claims, or, if you like, the JSON serialization of ALL of the claims.

Later, at the time you call VerifyAccessToken, these custom attributes will all be retrieved from the token store implicitly when the token is validated. They will be inserted into context variables.

Thanks for the video. From what I understand, the login-and-consent app the video illustrates is something custom-built, not an off-the shelf product as it is in our case.

My impression is that it's a common case these days for vendors to offer identity management solutions that bundle together the roles of identity provider and OAuth auth server. (This is the case we're dealing with.) The flow these products provide is one in which they serve as the identity provider, the login-and-consent application, and the token dispenser. As far as I know, there is no standard protocol whereby such an application would call out to a separate token dispenser.

So far from what I can tell the only viable path for us to use such a product is the flow I described, where Apigee does a back-channel request to the auth server for the token using a service callout, and maintains it in the session. Is there some problem with this approach that I'm not seeing?

Yes, that login-and-consent app is custom, and I think it needs to be, because.... the consent part is in addition to authentication.

If the IdP does OpenID Connect, ... then it already has a login-and-consent User Experience, but... if the IdP does not do OIDC, then... somehow, before issuing a token to the user, some system will need to get consent from the user. That's what the custom login-and-consent does.

There's nothing wrong with the approach you're proposing to follow. It's just not the normal, mainstream way I see customers doing it. Apigee Edge would not manage information in a "session"; instead the information is attached to the token, which is persisted.

Not applicable

This identity management system handles consent as well; I didn't include consent in order not to complicate things further. Client apps are registered in the IdP, with scopes, etc. But what I don't think there's any way to do is to have it call Apigee to get an auth code.

Login using the IdP will give me an auth code which I can pass to Apigee, which can then do a service callout to get the IdP auth token response. But then I'm a little vague on what would happen next -- I need to pass back something to the user agent to serve as a "session identifier" for subsequent requests, and a way to look up the response returned by the IdP when a request is submitted with that identifier.

This seems like the point at which I need to ask Apigee to generate a token to return to the user agent, which the user agent would then submit in the Authorization header on future requests. Then I could persist the IdP response information with that token.

However, if I use an OAuth2 policy with VerifyAccessToken at the beginning of the flow to control access, I believe there's a check to see that the Apigee-generated token has not expired. If it has expired, however, I don't necessarily want the user to reauthenticate -- if I have associated an IdP response with that token, what I really want is for the refresh token in the stored IdP response to be used to refresh the token with the IdP and then have the Apigee token refreshed correspondingly. I would have thought I could have the OAuth2 VerifyAccessToken continueOnError, but I don't see an error code that tells me that the token has expired. Is there some way for me to detect an expired token?

> I need to pass back something to the user agent to serve as a "session identifier" for subsequent requests, and a way to look up the response returned by the IdP when a request is submitted with that identifier.

I thnk that "session identifier" ought to be an OAuth token. I think there are two possibilities

  • If the token that is returned by the IdP is opaque, and is ok to return as-is to the app client, then you can use the OAuthV2/GenerateAccessToken policy, with and specify that "external" (w.r.t. Edge) access token via the ExternalAccessToken element in the policy configuration. When you do this (and I think you need StoreToken = true as well), then Apigee Edge "imports" the token that it received from the external system. Edge can then return this externally-originated token to the app client. You also need to attach any claims received from the IdP as custom attributes to this token.
  • If the token returned by the IdP is a JWT, and you do not wish to return that to the client app, then use OAuthV2/GenerateAccessToken. Here, similar to the above, you would store the JWT, or some subset of the claims from the JWT, as a custom attribute on the token that will be generated by Edge. You get a new opaque token, known only to Edge, and not known to the external IdP. Return that to the app client.

In either case, the app client presents the token to Edge in the normal OAuth way. Your Edge proxy should then use the OAuthV2/VerifyAccessToken policy to verify that the token is genuine and not expired. Then your proxy will implicitly retrieve into context variables, the custom attributes that you attached to the token at the time the token was generated.

At this point you can make extra authorization decisions in the proxy based on those attributes, or you can pass some subset of them to the backend.

I'm sorry if I have confused things or have missed things or made this more complicated than they need to be. This would have been a 10-15 min conversation at a whiteboard!

> I would have thought I could have the OAuth2 VerifyAccessToken continueOnError, but I don't see an error code that tells me that the token has expired. Is there some way for me to detect an expired token?

I think you need fault.name. When you say "i don't see an error code...", I think you may be running into the limitation in the Trace UI for Apigee Edge, that the fault.name set by a policy is not shown in the Trace UI, unless your API proxy explicitly reads or references it. We're going to fix that so that the fault.name gets displayed, but for now, to work around that limitation, insert a Step like this:

<Step>
  <Name>Any-Policy-Type--Must-Exist-but-will-not-Execute</Name>
  <Condition>fault.name = 'dino'</Condition> <!-- always false -->
<Step>

...and then the Trace UI will show the fault.name.

Then you can replace that step with one that refreshes, and uses the appropriate value for fault.name.

Thanks, Dino; this should get me off the ground. I appreciate the help. Does fault.name correspond to the errors for OAuth2 here: http://docs.apigee.com/api-services/content/oauthv2-policy#errorcodes or does it correspond to the HTTP OAuth error codes here: http://docs.apigee.com/api-services/reference/oauth-http-status-code-reference ? In the former case I don't see a code that would tell me that the token had expired. In the latter case I'm not sure how to get a handle on the JSON response -- so far as I've been playing with it, I have the VerifyAccessToken in the PreFlow and I don't have access to a response object.

One other thing I'm vague on is what the effect of setting/turning off GenerateResponse is in OAuthV2. If I set it to false (and continueOnError to true) in VerifyAccessToken, is there some way to know what flow variables I'll get? And if it's set, is there somewhere that documents what the response looks like--say for GenerateAccessToken operation? All I see in the HTTP status code reference is the error responses.

> in VerifyAccessToken, is there some way to know what flow variables I'll get?

yes, see here.

> is there somewhere that documents what the response looks like for GenerateAccessToken operation?

It looks like this:

{
  "issued_at" : "1467841035013",
  "scope" : "read",
  "application_name" : "e31b8d06-d538-4f6b-9fe3-8796c11dc930",
  "refresh_token_issued_at" : "1467841035013",
  "status" : "approved",
  "refresh_token_status" : "approved",
  "api_product_list" : "[Product1, nhl_product]",
  "expires_in" : "1799",
  "developer.email" : "edward@slalom.org",
  "token_type" : "BearerToken",
  "refresh_token" : "rVSmm3QaNa0xBVFbUISz1NZI15akvgLJ",
  "client_id" : "Adfsdvoc7KX5Gezz9le745UEql5dDmj",
  "access_token" : "AnoHsh2oZ6EFWF4h0KrA0gC5og3a",
  "organization_name" : "cerruti",
  "refresh_token_expires_in" : "0",
  "refresh_count" : "0"
}

But not all tokens will have exactly that form. Also, you can modify it, even after it is generated. For example, I have in my token dispensary proxy endpoint, something like this:

  <PostFlow name='PostFlow'>
      <Request/>
      <Response>
        <Step>
          <Name>JS-GroomTokenResponse</Name>
          <Condition>(request.formparam.grant_type = "password") OR (request.formparam.grant_type = "client_credentials")</Condition>
        </Step>
      </Response>
  </PostFlow><br>

And that policy, JS-GroomTokenResponse, deserializes the JSON, and modifies it, then re-serializes it.

For example, if I want the "issued_at" time to be human-readable, I will replace or augment it with a formatted timestamp. if I want to omit the token status (approved), then I can delete those properties.

Not applicable

I've managed to get this all working; thanks again for the help.

One thing I learned, which may be useful to know: In order to have Apigee mint its own token instead of using the external token, it's necessary to use the client_credentials OAuth flow. (Maybe the password flow will work too; I didn't check.) The auth code flow won't work here because Apigee is going to require that you supply an auth code that it has generated itself, and the implicit flow isn't supported.

When using external authorization, Apigee verifies the client ID even if the client ID and secret aren't used in generating the token, so it's necessary to have Apigee generate a client ID and secret for the application when registering the application. I was then able to get Apigee to mint a token by supplying that client ID in the client_credentials flow. (It wasn't necessary to supply the client secret, presumably because of the external auth.)

Glad to hear you got it working.

> The auth code flow won't work here because Apigee is going to require that you supply an auth code that it has generated itself, and the implicit flow isn't supported.

Yes, I think there's no way to explicitly import an externally-generated code into Apigee Edge. However, you could simulate the same by reversing the communications. Here's what I mean:

  1. the login-and-consent app generates its own code and directly sends that to the client, with a 302 redirect, pointing to the callback_url for the app. The client extracts that code somehow. (user cut/paste, or auto-retrieve, or some other mechanism)
  2. The client POSTs to the /token endpoint hosted on Edge, sending the code, the client_id + client_secret, and grant_type=authorization_code.
  3. Apigee Edge receives the code and then inquires the status of the code with the external login-and-consent app.
  4. Upon a successful response from that external login-and-consent service, indicating that the code is valid and not expired, Apigee Edge can then either:
    1. call OAuthV2/GenerateAuthorizationCode to generate a code known to Edge, without sending it to the client. Then immediately call OAuthV2/GenerateAccessToken with grant_type=authorization_code with the internally-known code. Then return that token to the client.
    2. call OAuthV2/GenerateAccessToken with grant_type=client_credentials and attach the user information that was associated with the code via custom attributes.
    3. call OAuthV2/GenerateAccessToken with grant_type=password and attach the user information as above.

Password grant works the same as client_credentials, except there is a user-authentication step that is required, and user information is attached to the generated token.

> Implicit isn't supported

?? Not sure what you mean. Maybe you mean, "implicit grant type is not applicable here." But it's supported in Edge.

Not applicable

Sorry, yes, that's what I meant -- that the implicit grant type isn't applicable in this case.

Under point 4, I've chosen to do #2 for now; however, that does mean that I don't get a refresh token, so I may wind up going with #1.