I had a question recently from someone who wanted to explore using Apigee to issue different kinds of OAuth tokens: either opaque tokens or JWT.
If you're not clear on the difference between Opaque tokens and JWT, then maybe read this first.
Apigee can generate either kind of token. Today, you need to use a distinct policy type, depending on the type of token you want to issue to the client.
But it's not as simple as using either one policy or the other. I'll explain.
In the simple case, the request-for-token looks like this:
POST $tokenserver/token Content-Type: application/x-www-form-urlencoded Authorization: Basic Zm9vOmJhcg== grant_type=client_credentials
In other words, the client passes in credentials, as well as a form payload saying "this is a request for token for the grant_type of client_credentials".
In Apigee, the OAuthV2 policy with Operation=GenerateAccessToken will implicitly validate the client credentials provided in a Basic Auth header. The OAuth spec says "The authorization server MUST authenticate the client." and a good way to do that in Apigee is to use the OAuthV2 policy.
Normally the OAuthV2/GenerateAccessToken policy just generates the token response, and you're done. The flow looks like this:
<Flow name='token1a'> <Description>token endpoint #1a</Description> <Condition>(proxy.pathsuffix MatchesPath "/token1a") and (request.verb = "POST")</Condition> <Request> <Step> <!-- Validation of the inbound request. Is the required form param present? --> <Condition>request.formparam.grant_type != "client_credentials"</Condition> <Name>RF-InvalidGrantType</Name> </Step> </Request> <Response> <Step> <!-- This policy implicitly validates the client credentials, and if valid, generates a token, and then generates a response containing that token, immediately. --> <Name>OAuthV2-GenerateAccessToken-CC</Name> </Step> </Response> </Flow>
Very simple!
The response payload looks something like this:
HTTP/1.1 200 OK Date: Wed, 27 Jan 2021 01:13:31 GMT Content-Type: application/json Content-Length: 335 Connection: keep-alive { "issued_at": 1611710011557, "client_id": "yzh0A4y6g9GsnR0MNIoAyiR17yXjVQnBF1eetkDAT9", "access_token": "lGU8R7nt5djo8VsnBa3GRZK99CGqAFkp8zkiu8Aw6MxGJPG1", "grant_type": "client_credentials", "expires_in": 1799, "issued": "2021-01-27T01:13:31.557Z", "expires_at": 1611711810557, "expires": "2021-01-27T01:43:30.557Z" }
If you know anything about JWT, you can immediately recognize that the access_token in the above payload is not a JWT. It is not a dot-separated string, for one thing, and for another, it's not long enough. That is an opaque token. It can be validated only by Apigee.
OK, now what if you want to generate a JWT rather than an opaque token? In this case we use GenerateJWT, but... we also still need the OAuthV2/GenerateAccessToken. The reason is the requirement to authenticate the client. The OAuthV2 policy does that. It *also* generates a token, and for this use case, we don't need the token. But that's ok. With the OAuthV2/GenerateAccessToken, we accomplish our purpose of authenticating the client credentials.
After authenticating the client, we want Apigee to execute the GenerateJWT policy, and generate a signed JWT with the payload claims we need. And then "manually" embed that JWT into a response. The "flow" for this in Apigee looks like this:
<Flow name='token2'> <Description>token endpoint #2</Description> <Condition>(proxy.pathsuffix MatchesPath "/token2") and (request.verb = "POST")</Condition> <Request> <Step> <!-- Validation of the inbound request. Is the required form param present? --> <Condition>request.formparam.grant_type != "client_credentials"</Condition> <Name>RF-InvalidGrantType</Name> </Step> </Request> <Response> <Step> <!-- This policy implicitly validates the client credentials, and if valid, generates a token, and then stores that token and other information about the token in a set of flow variables. We can then generate a JWT containing that information. --> <Name>OAuthV2-GenerateAccessToken-CC-NoResponse</Name> </Step> <Step> <Name>AM-SigningKeys</Name> </Step> <Step> <Name>GenerateJWT-RS256</Name> </Step> <Step> <Name>AM-Explicit-JWT-Response</Name> </Step> </Response> </Flow>
While the request for a JWT uses EXACTLY the same shape as a request for an opaque token, the response is different. It might look something like this:
HTTP/1.1 200 OK Date: Wed, 27 Jan 2021 01:19:55 GMT Content-Type: application/json Content-Length: 846 Connection: keep-alive { "access_token" : "eyJraWQiOiJyc2EtMjAyMTAxMjYtMTMxOCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJvcGFxdWUtdG9rZW4iOiJtT2lybVM5RXY5RmxjOUcxbmoxbFI4YVgyTG1FNEw5S0ZhWjhNbnNKR1o5bGV1cGciLCJzdWIiOiJ5emgwQTR5Nmc5R3NuUjBNTklvQXlpUjE3eVhqVlFuQkYxZWV0a0RBVDkiLCJpc3MiOiJodHRwczpcL1wvZ2FjY2VsZXJhdGUzLXRlc3QuYXBpZ2VlLm5ldFwvb2F1dGgyLWNjLWFuZC1qd3RcL2Rpc3BlbnNhcnlcL3Rva2VuMiIsImV4cCI6MTYxMTcxMjE5NSwiaWF0IjoxNjExNzEwMzk1LCJqdGkiOiI0NGFlNjFmMC0xMzUyLTQyODgtYTI0MS1kNDEzOTUyOTAyM2EifQ.AG_y8bG7mFXzZEKBCLFXcZtPw3fL2P0zURG7tK9Vq6uynJ_Y86fOwWilDphyAhiqntq6TrqOFtTliMi2uKDW5QNr7C1BlPBoRvPtHz3AShpuovQzVpO4xTBCYE9rbugfxM-JkATJSJn39Ui5A9UrtLEzkcVhpS_xOdDVIn-bn1QEcI0-gspG5aFDqsHXtA9wFl1vbyRPTvh8aqm8-Rpo5ib4UBWvgOUKaTfI_a5uHINLFbVkP3zxzlx9zzIrCX_ehL7veEuvjLqplJMtUfff6yaHpmznxTMVXMM9EP9NIY6WMZLnlUgxEZUcbJSVdyFHbEFBOiPhLTHV5zNIsgEWeA", "token_type" : "Bearer" }
Yes, a much longer token string there. That is a signed JWT. You can see the dots denoting the three distinct parts of the JWT. If you want to decode that particular JWT, you can click here.
----
If you want to try this yourself, I've got a GitHub repository with all the sample code, and a README to guide you along.
And finally, a Youtube screencast where I walk through this same information.
I hope this is helpful! Let me know if you have questions.
Dispensing tokens via OAuthV2 with PKCE (RFC 7636)
POP - Using JWT to prove possession of a key
Screencast: Issuing OAuth tokens in Apigee Edge using Password Grant, and verifying same
How to store RSA keys for JWT into KVM? & retrieve them from within API Proxies?
Calling the Apigee Management APIs before and after enabling SSO
Enhancements coming soon in JWT Policies
Automating Access to the Apigee Management APIs using Machine User Credentials
Connecting to Salesforce APIs with Apigee using JWT auth. and mutual TLS