Let's talk about OAuth, shall we?
The authorization code grant, as described in the OAuth V2 spec (RFC 6749), is intended for use with 3rd-party client apps. "3rd party" means, an app that is not written by the same party that produces the API. The app is therefore not implicitly trusted, and OAuth says that a user should grant consent to the app, to allow it interact with the APIs on behalf of the user. This consent is embodied in the access token issued to the app, via the authorization code grant.
With the authorization code grant, both the user and the app credentials must be verified before the authorization server issues a token. In the Apigee Edge model, an external Identity Provider verifies the user credentials, and Apigee Edge itself verifies the application (client) credentials.
Beyond authenticating the user, the authorization grant should also check the user consent. In a typical login-and-consent user experience, the user first authenticates, and then consents to the app (client) receiving a token with the stated scopes. Some login-and-consent experiences may collapse those two steps into one interaction). Login-and-consent is almost always performed via a trusted user agent, in other words the web browser builtin to the client platform. In some cases a different interaction model can be used to grant consent, like an SMS exchange. Eg, "Do you consent to App X obtaining scope Y? Reply YES to allow this."
After consent, login-and-consent returns the code to the client (app) via a 302 redirect, sent to the redirect address registered for that client. The client POSTs that code to the /token endpoint to receive an access token.
All of that is just standard authorization code grant. aka "The 3-legged OAuth dance." Proof Key for Code Exchange, also known as PKCE (RFC 7636) extends that basic model to add a code challenge and code verifier to the protocol, in this way:
In the GET /authorize call, the client passes the CHALLENGE.
When exchanging the code for a token (POST /token), the client passes the VERIFIER.
The Authorization server (Apigee Edge in this case) is responsible for retaining the challenge, and during exchange-code-for-token, checking that the verifier matches the challenge. The idea is to eliminate the possibility for a malefactor to intercept the code, and then be able to obtain a bonafide token with it. You can look at the write-up by Okta for a more detailed description of the motivation for PKCE.
PKCE is on the standards track from IETF. It's an open protocol at this point. Can Apigee Edge dispense tokens using the PKCE extension to OAuth2 3-legged grants?
YES. Out of the box, the OAuthV2 policy (and the GenerateAccessToken Operation) does not include support for PKCE. But, it's really straightforward to add this into your own token dispensing proxy, if you want it.
The way to do it:
It's that simple.
OK, it's not absolutely simple, because OAuthV2 authorization code grants are not simple. There are numerous interactions to handle and you need to consider all of them in the design of the token dispensing proxy. But Apigee Edge handles those grants quite easily. Adding PKCE to the mix requires a very simple enhancement to the normal token dispensing proxy that uses authorization code grant.
AND, the good news is that I have put together s a screencast showing how PKCE works in Apigee Edge:
And here is a repo that contains all the proxy configuration, as well as some tools, so you can use the same thing in your own Apigee Edge organization.
https://github.com/DinoChiesa/Edge-OAuthV2-PKCE-Proxy
I'd love to hear your comments on this.
Instead of using Cache policy, can we use custom attributes like code challenge, code challenge type etc embbeded into the auth code and then checking them back during the POST request to generate the token using auth code, code verifier?
Thanks,
Anand
@Dino @Dino-at-Google Can we use the above approach, highlighted by @Anand Gururaja , where code_challenge store as a custom attribute in code flow and later /token flow just use attribute (code_challenge) to verify ?
Please suggest
Yes, I think that is a good idea. ...
@Dino-at-Google I just browsed through the repo implementation, and stumbled over:
<Flow name="token"> <!-- The app uses this request to exchange the code for a token. Example: POST /devjam3/oauth2-ac/token? grant_type=authorization_code &client_id=wlq93FiqTw1si09wsocM7AjOBSbyi4 &client_secret=78djdkdjdkjd<br>
And indeed, I can't get it to work if I don't pass a client_secret. However, if I get RFC 7636 correctly, the whole point of PKCE is to avoid distributing client secrets to public clients (where PKCE provides means to protect specific attacks).
Hence, I tried setting ExternalAuthorization to true in the OAuth2 policy (following the docs) in order to avoid the client secret (that I don't want to include in the request) being checked. Due to some magic that still seems to happen, I keep ending up with HTTP 500 and the following error:
{"fault":{"faultstring":"Invalid client identifier {0}","detail":{"errorcode":"oauth.v2.InvalidClientIdentifier"}}}<br>
I double-checked that the client_id parameter is still passed and a valid API key. If I set oauth_external_authorization_status to true, the error I'm receiving is upgraded to:
{"fault":{"faultstring":"Invalid access token","detail":{"errorcode":"oauth.v2.InvalidAccessToken"}}}
Is there any specific setting that skips client authentication (using client_secret), but still generates an access token? I think that would be a plausible addition to the example repo.
Br,
Dominik
Yes, good point.
PKCE attempts to avoid the requirement to pass secrets. And in fact the client_secret here need not be secret. It's redundant.
Unfortunately, The Apigee OAuthV2 policy requires a client_secret when Operation=GenerateAccessToken. There is no way, currently, to configure the Apigee policy to use PKCE which does not require the client_secret.
In the non-PKCE flow, the client app passes the client_secret and the policy within the API Proxy just references it through a context variable. In the PKCE flow, we don't NEED it. My implementation was ... ok, let me just say it... sloppy.
The way to correct this in the implementation is, within the API Proxy, to use an AccessEntity policy to retrieve the client_secret for the client_id that is passed in. Then reference THAT client_secret.
I'll modify the repo. and post-back when it's ready.
Thanks for the quick reply! I worked around it meanwhile by setting the oauth_external_authorization_status variable to true in an AssignMessage policy, and the
ExternalAuthorizationCode element to request.formparam.code in the OAuthV2 policy. Since the auth code is dumped anyway (as opposed to ExternalAccessToken), it doesn't really hurt.
Using AccessEntity to fetch the client_secret would have been my fallback solution :-)
OK, I've updated the repo so that redemption of code-for-token does not require the client_secret. Check it out.
(I have not updated the screencast)
Configuring Apigee to dispense OAuth tokens - either opaque or JWT
Apigee as OAuth Provider - PingFederate as IdentityProvider : using OpenID Connect Flow
Screencast: Issuing OAuth tokens in Apigee Edge using Password Grant, and verifying same
Calling the Apigee Management APIs before and after enabling SSO
Automating Access to the Apigee Management APIs using Machine User Credentials
POP - Using JWT to prove possession of a key
Oauth2.0 Access Token Verification throws “Invalid API call as no apiproduct match found” error