JWT Policies in Apigee Edge

Hey there, Apigee Community friends!

Time for another video. This one is a bit longer, and covers the new JWT policies that are available in Apigee Edge. YES! Apigee Edge now has builtin policies that can generate or verify JWT, according to your configuration. Here's a sample configuration for VerifyJWT:

<VerifyJWT name="Verify-JWT-1">
    <Algorithm>RS256</Algorithm>
    <Source>inbound.jwt</Source>
    <PublicKey>
        <Value ref='publickey'/>
    </PublicKey>
    <Audience>urn://Apigee</Audience>
</VerifyJWT>

And here is a sample policy configuration for Generating a JWT:

<GenerateJWT name="Generate-JWT-1">
    <Algorithm>RS256</Algorithm>
    <PrivateKey>
        <Value ref="private.privatekey"/>
    </PrivateKey>
    <Subject ref="request.formparam.subject"/>
    <Issuer>urn://apigee-edge-JWT-policy-demonstration</Issuer>
    <Audience ref="request.formparam.audience"/>
    <ExpiresIn>60m</ExpiresIn>
    <OutputVariable>output-jwt</OutputVariable>
</GenerateJWT>

I've just reprised this Screencast to provide some additional information. I hope you'll click and enjoy.

Click to view the screencast . BTW, the source-code accompanying the screencast is available on Github.

6585-screenshot-20180315-180622.png

FAQ

Q. How does this new set of policies differ from the Java callout that has been available for some time?

A. The new policies are a supported part of the Apigee Edge product. They'll be built, tested, and maintained by Apigee engineers according to the product standards. There is official documentation and support for these policies.

The Java callout is an open-source project, and is not part of the supported Apigee Edge product. There's no official documentation and support was done through a volunteer, best-effort basis. Since there is no way to add a custom policy into the user interface, the Java callout could never appear in the policy-selector UI.

JWT of course is an open standard, and there's nothing technically wrong with using your own custom code to parse or generate JWT. But we think there are advantages in the developer experience with delivering a supported policy.

Q. I've been using that Java callout for some time, it works great. Should I convert to using this builtin policy?

Probably. The Java callout works just fine, but we think the new builtin policies will be more efficient, especially at scale. They'll also be easier to maintain, with better documentation and support.

Converting from the Java callout configuration to the config for one of the builtin policies will be basically mechanical. Renaming elements and so on. It will be a low-effort affair. You should be able to convert a policy in about 60-90 seconds.

Q. I've been doing JWT in a nodejs target for some time, it works great. Should I convert to using this builtin policy?

Probably. You may be depending on one of the various nodejs libraries for JWT, like jsonwebtoken, or node-jwt. The nodejs target in Apigee Edge is based on Trireme, which is a JavaScript interpreter that itself runs inside the JVM. The cpu efficiency of JS code running on top of that stack is poor, compared to directly compiled Java code. This matters especially with RSA algorithms, but it also has an effect on HMAC algorithms. If you have high load, you will want to use the more efficient option, and that's the built-in policy types.

Q. Do the JWT policies enforce any particular format on the JWT they generate or verify?

No. Apigee Edge can verify JWT generated by Azure, Google Signin, Salesforce, Ping, Paypal, or other systems. When generating a JWT, you can embed payload claims of any type into the token. Also, Apigee Edge can generate JWT that can be verified by any other system.

Q. Will the VerifyJWT policy directly implement RFC7523?

No, but you can implement RFC7523 grants in your API Proxy. Consult the prior article describing the technique; all you need to do is replace the Java callout in that example with the builtin Apigee Edge policy.

Q. Will Apigee Edge provide more support for OpenID Connect?

OpenID Connect wraps user authentication around the OAuth 2.0 three-legged protocol. There is a prior community article discussing OpenID Connect and Apigee Edge. The basics of that article still apply, now that there is a builtin GenerateJWT policy.

Apigee is investigating deeper support for OpenID Connect, but we have nothing to announce at this time.

Q. I see the policy supports RSA and HMAC signing. When will other algorithms be supported, like ECDSA?

ECDSA is on the roadmap, but we don't have a firm schedule for this. Soon! Expected Summer 2018.

Q. What about encrypted tokens, JWE? Will Apigee support that with a builtin policy?

We have no immediate plans to ship a policy that does JWE, but we are always interested in customer requirements. For now you may have to rely on custom Java code for this technical use case.

Q. I'd like to verify JWT using certificates stored in the Apigee truststore. But there is no way for the API Proxy to directly read from the truststore. Will Apigee allow this in the future?

We are investigating that possibility. Right now we don't view this limitation as severe, as there is a pretty simple workaround: load the certs and keys into the KVM.

Q. Why does Apigee force me to use a private. variable for configuring Private keys or secret keys in the JWT policy configuration?

We are encouraging good security practices by enforcing the use of a private variable. You cannot key in secrets like passwords or PEMs for private keys into the policy configuration files. We feel the right place for this information is in a secure store, such as the Encrypted KVM which is part of Apigee Edge. Of course you can embed public keys directly into the policy configuration, if you wish. Public keys aren't secrets!

Q. OK, I see the merit in that. How do I load my privatekey.pem file into the KVM?

You should do it via the administrative API. Today the Admin UI does not handle newlines very well. (bug ref b/64808634 ) There's a bash script available here that can help. Or if you prefer nodejs, check here. I haven't created an example for Powershell, let me know if you want one. The key point: You need to be careful escaping newlines.

Comments
sjm2000
Silver 1
Silver 1

Just what the doctor ordered, wonderful and thank you

meghdeepbasu
New Member

Great news. Any idea when is it expected ? We are eagerly waiting for this.

Feroz_Mahammad
New Member

Good news. How do we get those new policies on to the existing on-prem Apigee instance?

DChiesa
Staff

These policies will roll to OPDK in a future release. We haven't announced specifically. 18.01 would be the next logical candidate. You will need to upgrade your installation in order to get them.

UPDATE: Yes, the JWT policies are available in 18.01

DChiesa
Staff

These policies are available as Alpha in Edge SaaS. You can request enablement from Apigee Support. Current plan is to make them generally available to everyone in January.

meghdeepbasu
New Member

Thanks @Dino,

We got it in our orgs. One query: when I am trying to assign an array to our custom claims, the decoded jwt has "/", like

"software_redirect_uris": "[\"https://prime.amazon.com/cb\"]"<br>

Although the in the previous java script policy and in the JWT generate policy, we are able to see proper value:

redirectUrisArray ["https//prime.amazon.com/cb"]

Any pointer on this ?

Not applicable

Thank you. Does it support kid?

DChiesa
Staff

Yes.

  • On Generation, of course you can configure the policy to embed a kid claim in the JWT header. And that claim can hold any value.
  • On Verification (probably what you were asking about), you can specify a JWKS. If the JWT to be verified contains a kid in the header, the JWT policy will extract the appropriate key from the JWKS based on the kid, and will verify the JWT as appropriate.

Does this answer your question?

DChiesa
Staff

Yes - there is a way in the JWT Alpha to specify that your custom claim (software_redirect_uris) should contain an array. You need to use type='string' and array='true'. We are working to make this simpler in the general release, so that the policy "does the right thing", basically a JSON.parse() of the value used for the claim.

Not applicable

I just cannot find where I can configure jwt headers in GenerateJWT policy.

DChiesa
Staff

Ah yes, it is not possible to do that using the Alpha policy. This is a feature people have asked for. We have added it to the policy, and it will be possible when we release in January.

EDIT - this is available now. Use the AdditionalHeaders element. Example:

<GenerateJWT name="Generate-JWT-RS256-AdditionalClaims">
    <Algorithm>RS256</Algorithm>
    <PrivateKey>
        <Value ref="private.privatekey"/>
        <Id>unique-identifier-for-privatekey-here</Id>
    </PrivateKey>
    <Id/> <!-- generate a unique identifier -->
    <AdditionalHeaders>
      <Claim name='whatever' ref='apiproxy.name'/>
    </AdditionalHeaders>
    <Subject ref="request.formparam.subject"/>
    <Issuer>urn://apigee-edge-JWT-policy-demonstration</Issuer>
    <Audience ref="request.formparam.audience"/>
    <ExpiresIn>10m</ExpiresIn>
    <AdditionalClaims>
      <Claim name='apigee-proxy' ref='apiproxy.name'/>
      <Claim name='messageid' ref='messageid'/>
      <Claim name='request-path' ref='request.path'/>
      <Claim name='apigee-org' ref='organization.name'/>
      <Claim name='apigee-env' ref='environment.name'/>
    </AdditionalClaims>
    <OutputVariable>output-jwt</OutputVariable>
</GenerateJWT>
Not applicable

How do I specify modulus and exponent in the VerifyJWT Policy instead of Public Key? There was a way to do that in the callout. Just looking for the format to specify.

DChiesa
Staff

Do it with the JWKS. You need the modulus and exponent wrapped in a JWKS, and then the JWT-to-be-verified must refer to the kid.

There is no way to directly specify the modulus and exponent.

Not applicable

Thanks Dino. I should have watched your video until the end :).

Not applicable

Hey Dino, I was trying to validate the jwt generated from apigee edge in Azure Functions and I found out that when you assign the value of private key in the PEM format, you need to remove the BEGIN PRIVATE KEY and END PRIVATE KEY related lines from the file. If you don't do that, GenerateJWT policy seems to work without any problems but the signature cannot be validated. I am hoping that this will save time for somebody else.

Not applicable

Has anyone else seen this behavior :

From time to time the 'Decode JWT' policy sets the variable name suffix to unexpected values. For example, I have seen jwt.xxx.custom_claims.scp and jwt.xxx.claims.scp used. I'm looking for the values of these variables, so when the name changes my 'Raise Value' conditions get all messed up.

I know that this policy is in 'Beta', so I understand that this may just be a development bug. Does anyone have thoughts and/or comments?

brentstains
New Member

Thank you for the examples! The have been quite helpful. I noticed that when running the examples that I wasn't getting some of the values from the inbound JWT token into the outbound token.

For some reason, I had to add ".claim" in SOME of the variables. I assume there's a reason for it and it's beta, but may want to update the examples? Not sure, but sharing all the same.

<Payload contentType="application/json">

{ "status" : "ok", "algorithm" : "{jwt.Verify-JWT-2.claim.algorithm}",

"claim_names" : "{jwt.Verify-JWT-2.payload-claim-names}",

"subject" : "{jwt.Verify-JWT-2.claim.subject}",

"issuer" : "{jwt.Verify-JWT-2.claim.issuer}",

"audience" : "{jwt.Verify-JWT-2.claim.audience}",

"expiry" : "{jwt.Verify-JWT-2.expiry_formatted}",

"seconds_remaining" : {jwt.Verify-JWT-2.seconds_remaining},

"out.now" : "{outbound.now}" }

</Payload>

Not applicable

when there will be a tutorial step by step for dummies in the Apigee Docs?

sidd-harth
Bronze 1
Bronze 1

Hi @Rafael Obara, for step by step tutorial, follow below Github project by Dino,

https://github.com/DinoChiesa/ApigeeEdge-JWT-Demonstration

Not applicable

@Siddharth Barahalikar thanks for the link,

I dit that demo tutorial yesterd, but i think it's very basic.

If i have a client app registered in my organization.

How do i generate a jwt token for a existing client app registered in a product?

Where do i inform the consumer id and consumer secret, grant_type, scopes?

I need to insert a specific claim in my token based in the consumer id, how do i do this dynamically?

Sorry for asking this kind of thing, i'm new at Apigee and i'm having hard time with Oauth2, Apigee and JWTtokens

Not applicable

Trying to follow Dino's JWT Policies in Apigee Edge sample solution. Great video, useful same code. Worked for me. We are running private cloud 4.18.01. I need to run loadPemIntoKvm.js to upload private key to encrypted KVM in an org in private cloud. This script uses apigee.com as default in a few input parameters. I can override the mgmtServer input parameter, but don't know how to override the "loginBaseUrl" and "urlBase" since they do not seem to be provided in the options list. Also, the option "-M" showed twice, once for mgmtserver, and once for napname arguement.

Usage:
node loadPemIntoKvm.js [OPTION]

Options:
-M, --mgmtserver=ARG the base path, including optional port, of the Edge mgmt server. Defaults to https://api.enterprise.apigee.com .
-u, --username=ARG org user with permissions to read Edge configuration.
-p, --password=ARG password for the org user.
-n, --netrc retrieve the username + password from the .netrc file. In lieu of -u/-p
-o, --org=ARG the Edge organization.
-v, --verbose
-h, --help
-e, --env=ARG required. the Edge environment for which to store the KVM data
-M, --mapname=ARG optional. name of the KVM in Edge for keys. Will be created if nec. Default: PrivateKeys
-E, --encrypted optional. if creating a KVM, set it as encrypted. Default: not.
-F, --pemfile=ARG required. name of the file containing the pem-encoded key.
-N, --entryname=ARG required. name of the entry in KVM to store the PEM.
-T, --notoken optional. do not try to get a authentication token.

dchiesa1
Staff

whoops!

The repeat of the -M option was an oversight. You can use the long form option --mapname and --mgmtserver to disambiguate. I'll fix it in a future version.

The loginBaseUrl and urlBase .. I think those are present in the apigee-edge-js module, which is a dependency of that tool. The loginBaseUrl is intended to be used with SSO on the Edge SaaS. You should never need that. The urlbase should just work. That's an internal implementation detail, from what I can tell.

I haven't tried the loadPemIntoKvm.js tool against an OPDK. It is intended to "just work". If it does not, then we need to log a bug against the apigee-edge-js module. (And I'll fix THAT).

You said you need to run the script... and then offered some observations about the internals of the script But did you try it? What results did you see?

If it does not work specifying --mgmtserver , then maybe you can try the -T option (notoken), which tells the tool to not try to obtain a token from the token endpoint. (This should work also in OPDK, but not sure if you've got that set up properly)

dchiesa1
Staff

Yes, thank you, the examples have been updated.

dchiesa1
Staff

Generate a JWT using the GenerateJWT policy.

<GenerateJWT name="Generate-JWT-HS256-Basic">
    <Algorithm>HS256</Algorithm>
    <SecretKey>
        <Value ref="private.secretkey"/>
        <Id>optional-unique-identifier-for-secretkey-here</Id>
    </SecretKey>
    <Id/> <!-- this tells Apigee to generate a unique identifier -->
    <Subject ref="request.formparam.subject"/>
    <Issuer>urn://Apigee-edge-JWT-policy-demonstration</Issuer>
    <Audience ref="request.formparam.audience"/>
    <ExpiresIn>60m</ExpiresIn>
    <OutputVariable>output-jwt</OutputVariable>
</GenerateJWT>

You can set the claims to be whatever you like. If you want to use a client_id, then use a client_id as the subject. Or whatever. It's up to you.

dchiesa1
Staff

Thanks for that tip. Not sure why that would be. But I'm glad it's working for you.

Not applicable

Yes, the use of -mgmtserver worked. Run the command as suggested and here is the error received.

node ./tools/loadPemIntoKvm.js --mgmtserver $MGMT_URL -u $USER_NAME -p $PASSWD -v -o $ORG -e $ENV -E --mapname secrets -N key1 -F ./tools/keys/private-pkcs8.pem
Apigee Edge PEM KVM-loader tool, version: 20171213-1645
Node.js v8.9.1

[2018-Jun-06 09:47:47] start
[2018-Jun-06 09:47:47] connect: {"orgname":"xxxmyorgname","user":"xxxmyuserid","loginBaseUrl":"https://login.apigee.com","mgmtServer":"https://xxxmy-server-domain","urlBase":"https://xxxmy-server-domain/v1/o/xxxmy-org","requestHeaders":{"accept":"application/json"},"verbosity":true}

[2018-Jun-06 09:47:47] found no stashed token.
[2018-Jun-06 09:47:47] POST https://login.apigee.com/oauth/token

C:\MyWorkspace\Download\ApigeeEdge-JWT-Demo\node_modules\apigee-edge-js\lib\common.js:29
utility.logWrite('status: ' + response.statusCode );
^

TypeError: Cannot read property 'statusCode' of undefined
at Request._callback (C:\MyWorkspace\Download\ApigeeEdge-JWT-Demo\node_modules\apigee-edge-js\lib\common.js:29:48)
at self.callback (C:\MyWorkspace\Download\ApigeeEdge-JWT-Demo\node_modules\request\request.js:185:22)
at emitOne (events.js:116:13)
at Request.emit (events.js:211:7)
at Request.onRequestError (C:\MyWorkspace\Download\ApigeeEdge-JWT-Demo\node_modules\request\request.js:877:8)
at emitOne (events.js:116:13)
at ClientRequest.emit (events.js:211:7)
at ClientRequest.onConnect (C:\MyWorkspace\Download\ApigeeEdge-JWT-Demo\node_modules\tunnel-agent\index.js:168:23)
at Object.onceWrapper (events.js:319:30)
at emitThree (events.js:136:13)

dchiesa1
Staff

Can you maybe try the -T option (notoken)? It tells the tool to not try to obtain a token from the token endpoint.

Not applicable

Same error message when use -T option.

Not applicable

An interesting observation. Even though I used the management server option to point to our OPDK in loadPem call, the

"loginBaseUrl":"https://login.apigee.com" still pointed to login.apigee.com. Not sure whether this matters. But as you can see in the log posted above, the call seems to actually POST to apigee.com rather than

our OPDK. Here is the console log:

[2018-Jun-06 09:47:47] found no stashed token.

[2018-Jun-06 09:47:47] POST https://login.apigee.com/oauth/token


dchiesa1
Staff

yes, the -T option is supposed to avoid that code path. It shouldn't attempt to obtain a token.

Can you do 2 things:

1. git pull the repository again?

2. show me the exact command line you use?

When I use -T, it does not try to obtain a token. It does not look for a stashed token. I don't understand how that code gets executed when you pass -T.

nitin-kotian
New Member

Hey Dino,

Any updates on this? I am getting the same error and can't load my private key into KVM.

dchiesa1
Staff

Hi - just seeing this now. If you are still having problems, please ask a NEW QUESTION. In general ask new questions with a new question, rather than attaching it as a comment to a long old stream (last update more than a year ago).

kvrsaicharan11
Bronze 1
Bronze 1

hi dino when ever i am passing jwt token in headers its showing unable to parse key(verify). i tried by keeping jwks value in kvm , and also use service callout policy to call that jwks url still it is showing like unable to parse key(verify) . what was the reason for that

dchiesa1
Staff

Can you please post a new question? Does it work for you to use "Ask a Question" ?

10668-ask-a-question.png

Version history
Last update:
‎11-07-2017 05:33 PM
Updated by: