├── images
├── check-for-JWT-policies.gif
└── screenshot-20180316-103908.png
├── proxy-bundles
├── jwt-verify
│ └── apiproxy
│ │ ├── policies
│ │ ├── JS-SetNow.xml
│ │ ├── AM-SecretKey.xml
│ │ ├── AM-ShowSecretKey.xml
│ │ ├── AM-ClearResponseHeaders.xml
│ │ ├── AM-SecretKeyFromQueryParam.xml
│ │ ├── Verify-JWT-HS256-BasicNoAudience.xml
│ │ ├── EV-JWT.xml
│ │ ├── Verify-JWT-HS256-Basic.xml
│ │ ├── KVM-GetPublicKey.xml
│ │ ├── Verify-JWT-RS256-Audience-and-Issuer2.xml
│ │ ├── RF-MissingKey.xml
│ │ ├── RF-MissingHeader.xml
│ │ ├── RF-InvalidToken.xml
│ │ ├── RF-UnknownRequest.xml
│ │ ├── Verify-JWT-RS256-Audience-and-Issuer.xml
│ │ ├── AM-Response-HS256-Basic.xml
│ │ ├── AM-Response-RS256-Basic.xml
│ │ ├── AM-Response-3.xml
│ │ ├── AM-Response-4.xml
│ │ └── Verify-JWT-RS256-Basic.xml
│ │ ├── resources
│ │ └── jsc
│ │ │ └── setNow.js
│ │ ├── jwt-verify.xml
│ │ ├── README.md
│ │ └── proxies
│ │ └── default.xml
├── jwt-generate
│ ├── README.md
│ └── apiproxy
│ │ ├── policies
│ │ ├── AM-SecretKey.xml
│ │ ├── AM-ClearResponseHeaders.xml
│ │ ├── KVM-GetPrivateKey.xml
│ │ ├── RF-MissingParam.xml
│ │ ├── AM-Response-1.xml
│ │ ├── RF-NoPrivateKey.xml
│ │ ├── AM-Diagnostics.xml
│ │ ├── Generate-JWT-HS256-Basic.xml
│ │ ├── RF-UnknownRequest.xml
│ │ ├── Generate-JWT-RS256-Basic.xml
│ │ ├── Generate-JWT-RS256-AdditionalClaims.xml
│ │ └── AM-PrivateKey.xml
│ │ ├── jwt-generate.xml
│ │ ├── README.md
│ │ └── proxies
│ │ └── default.xml
└── jwt-verify-goog
│ └── apiproxy
│ ├── policies
│ ├── AV-GoogleJwks.xml
│ ├── EV-JWT.xml
│ ├── AM-NoContent.xml
│ ├── CacheLookup-GoogleJwks.xml
│ ├── Verify-JWT-1.xml
│ ├── CacheInsert-GoogleJwks.xml
│ ├── RF-MissingHeader.xml
│ ├── SC-RetrieveGoogleJwks.xml
│ ├── RF-UnknownRequest.xml
│ ├── CacheInsert-Nothing.xml
│ └── AM-JWT-Parse-Response.xml
│ ├── jwt-verify-goog.xml
│ ├── proxies
│ └── default.xml
│ └── README.md
├── .gitignore
├── tools
├── .jshintrc
├── keys
│ ├── public.pem
│ ├── README.md
│ ├── private.pem
│ └── private-pkcs8.pem
├── create-HS256.js
├── notused
│ ├── example1.js
│ ├── example3.js
│ └── example2.js
├── package.json
├── decode-token.js
├── 1-create-HS256-with-expiry.js
├── 2-create-RS256-with-expiry.js
├── create-token.js
├── README.md
├── verify-token.js
├── loadPemIntoKvm.js
├── importAndDeploy.js
└── package-lock.json
├── NOTICE
├── LICENSE
└── README.md
/images/check-for-JWT-policies.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DinoChiesa/ApigeeEdge-JWT-Demonstration/HEAD/images/check-for-JWT-policies.gif
--------------------------------------------------------------------------------
/images/screenshot-20180316-103908.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DinoChiesa/ApigeeEdge-JWT-Demonstration/HEAD/images/screenshot-20180316-103908.png
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/JS-SetNow.xml:
--------------------------------------------------------------------------------
1 |
2 | jsc://setNow.js
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | **/Icon*
3 | **/Icon
4 | */Icon
5 | Icon
6 |
7 | **/#*.*#
8 | **/*.*~
9 | **/#*#
10 | **/*~
11 |
12 | **/target/
13 | **/node_modules/
14 |
15 | **/*.class
16 |
17 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/README.md:
--------------------------------------------------------------------------------
1 | # Generate Proxy
2 |
3 | This API Proxy generates JWT.
4 |
5 | ```
6 | curl -i -X POST https://ORG-ENV.apigee.net/jwt-generate/t1 -d 'subject=Subject&audience=A12345'
7 | ```
8 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-SecretKey.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | private.secretkey
4 | Secret123
5 |
6 |
7 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/AM-SecretKey.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | private.secretkey
4 | Secret1234
5 |
6 |
7 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-ShowSecretKey.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | mysecretkey
4 | [private.secretkey]
5 |
6 |
7 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/AM-ClearResponseHeaders.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-ClearResponseHeaders.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-SecretKeyFromQueryParam.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | private.secretkey
4 | [request.queryparam.key]
5 |
6 |
7 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/Verify-JWT-HS256-BasicNoAudience.xml:
--------------------------------------------------------------------------------
1 |
2 | HS256
3 | inbound.jwt
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/AV-GoogleJwks.xml:
--------------------------------------------------------------------------------
1 |
2 | false
3 |
4 | cached.google.jwks
5 | [googJwksResponse.content]
6 |
7 |
8 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/EV-JWT.xml:
--------------------------------------------------------------------------------
1 |
2 | request
3 | inbound
4 |
7 |
8 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/Verify-JWT-HS256-Basic.xml:
--------------------------------------------------------------------------------
1 |
2 | HS256
3 | inbound.jwt
4 |
5 |
6 |
7 | urn://Apigee
8 |
9 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/EV-JWT.xml:
--------------------------------------------------------------------------------
1 |
2 | request
3 | inbound
4 |
7 |
8 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/KVM-GetPublicKey.xml:
--------------------------------------------------------------------------------
1 |
2 | environment
3 | 15
4 |
5 |
6 | key1
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/KVM-GetPrivateKey.xml:
--------------------------------------------------------------------------------
1 |
2 | environment
3 | 15
4 |
5 |
6 | key1
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/AM-NoContent.xml:
--------------------------------------------------------------------------------
1 |
2 | true
3 |
4 |
5 |
6 |
7 |
8 | 204
9 | No Content
10 |
11 |
12 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/CacheLookup-GoogleJwks.xml:
--------------------------------------------------------------------------------
1 |
2 | cache1
3 | cached.google.jwks
4 | Application
5 |
6 | googjwks
7 | 1
8 |
9 |
10 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/Verify-JWT-RS256-Audience-and-Issuer2.xml:
--------------------------------------------------------------------------------
1 |
2 | RS256
3 | inbound.jwt
4 |
5 |
6 |
7 | urn://Apigee
8 | urn://Issuer
9 |
10 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/Verify-JWT-1.xml:
--------------------------------------------------------------------------------
1 |
2 | RS256
3 | inbound.jwt
4 |
5 |
6 |
9 |
10 | https://accounts.google.com
11 |
12 |
--------------------------------------------------------------------------------
/tools/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esversion": 6,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": false,
19 | "trailing": true
20 | }
21 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/CacheInsert-GoogleJwks.xml:
--------------------------------------------------------------------------------
1 |
2 | cache1
3 | googJwksResponse.content
4 | Application
5 |
6 | googjwks
7 | 1
8 |
9 |
10 | 3600
11 |
12 |
13 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/RF-MissingKey.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Missing key queryparam
6 | 400
7 | Bad Request
8 |
9 |
10 | true
11 |
12 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/RF-MissingParam.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Missing form Parameter
6 | 400
7 | Bad Request
8 |
9 |
10 | true
11 |
12 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/RF-MissingHeader.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Missing Authorization Header
6 | 400
7 | Bad Request
8 |
9 |
10 | true
11 |
12 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/RF-MissingHeader.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Missing Authorization Header
6 | 400
7 | Bad Request
8 |
9 |
10 | true
11 |
12 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/SC-RetrieveGoogleJwks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | GET
5 |
6 |
7 | googJwksResponse
8 |
9 |
10 | 2xx
11 |
12 | https://www.googleapis.com/oauth2/v3/certs
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tools/keys/public.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQNvdXmQMHaCj+cKOhaB
3 | SVGMsgElLuAoU4yiHv/NFepQOKt5m3gcyK3t1sE2nMWebTQH1QLC9fANROeXVpJn
4 | 05E57LffR3RFp7bFT8dI6OG7xlpypUcw1KEx6D2uTRQ29GStq2/nM+HNu6RtHJi4
5 | C+Z3dIUsW7nV0FjVZIsCxA1z/fPFVy8rGERaRR+tWHTm5U2jKXEw3ileUv7LGgWM
6 | UMmxuqW2qyrkbVNC+gyI2AKmUV9bo/qLa0BrFxUrK2nRJlxmGnSA09s5CGKix2hP
7 | GxBCvO4wHQ1Wt1PZzDO/fKlkxYiCdALLn8VwKS3JqgInnPUDl1tRi6fDEhL3lKFP
8 | JwIDAQAB
9 | -----END PUBLIC KEY-----
10 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/AM-Response-1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 | {
6 | "status" : "ok",
7 | "jwt" : "{output-jwt}"
8 | }
9 | 200
10 | OK
11 |
12 |
13 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/RF-UnknownRequest.xml:
--------------------------------------------------------------------------------
1 |
2 | true
3 |
4 |
5 | {
6 | "error" : {
7 | "code" : 404.01,
8 | "message" : "that request was unknown"
9 | }
10 | }
11 |
12 | 404
13 | Not Found
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/RF-NoPrivateKey.xml:
--------------------------------------------------------------------------------
1 |
2 | true
3 |
4 |
5 | {
6 | "error" : {
7 | "code" : 500.01,
8 | "message" : "missing configuration. No privatekey found in KVM."
9 | }
10 | }
11 |
12 | 500
13 | Server Error
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/CacheInsert-Nothing.xml:
--------------------------------------------------------------------------------
1 |
2 | cache1
3 | messageid
4 | Application
5 |
6 | googjwks
7 | 1
8 |
9 |
10 |
12 | 1
13 |
14 |
15 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/RF-InvalidToken.xml:
--------------------------------------------------------------------------------
1 |
2 | true
3 |
4 |
5 | {
6 | "error" : {
7 | "code" : 401.01,
8 | "message" : "Unauthorized; no further information is available."
9 | }
10 | }
11 |
12 | 401
13 | Unauthorized
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/RF-UnknownRequest.xml:
--------------------------------------------------------------------------------
1 |
2 | true
3 |
4 |
5 | {
6 | "error" : {
7 | "code" : 404.01,
8 | "message" : "that request was unknown; try a different request."
9 | }
10 | }
11 |
12 | 404
13 | Not Found
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/AM-Diagnostics.xml:
--------------------------------------------------------------------------------
1 |
2 | AM-Diagnostics
3 | false
4 |
5 |
7 |
9 | exposed.privatekey
10 | [private.privatekey]
11 |
12 |
13 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Example JWT code for Apigee Edge
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/Generate-JWT-HS256-Basic.xml:
--------------------------------------------------------------------------------
1 |
2 | HS256
3 |
4 |
5 | optional-unique-identifier-for-secretkey-here
6 |
7 |
8 |
9 | urn://Apigee-edge-JWT-policy-demonstration
10 |
11 | 60m
12 | output-jwt
13 |
14 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/resources/jsc/setNow.js:
--------------------------------------------------------------------------------
1 | var monthNum = {
2 | "Jan" : "01",
3 | "Feb" : "02",
4 | "Mar" : "03",
5 | "Apr" : "04",
6 | "May" : "05",
7 | "Jun" : "06",
8 | "Jul" : "07",
9 | "Aug" : "08",
10 | "Sep" : "09",
11 | "Oct" : "10",
12 | "Nov" : "11",
13 | "Dec" : "12"
14 | };
15 | function nowString() {
16 | var time = (new Date()).toString(),
17 | tstr = time.substr(11, 4) + '-' +
18 | monthNum[time.substr(4, 3)] + '-' + time.substr(8, 2) + 'T' +
19 | time.substr(16, 8) + '+0000';
20 | return tstr;
21 | }
22 |
23 | context.setVariable('outbound.now', nowString());
24 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/jwt-verify-goog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | A proxy that validates JWT signed by Google Sign-in
4 | 1408643295583
5 | DChiesa@google.com
6 | jwt-verify-goog
7 | 1481862518324
8 | DChiesa@google.com
9 |
10 |
11 | default
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tools/create-HS256.js:
--------------------------------------------------------------------------------
1 | // create-HS256.js
2 | // ------------------------------------------------------------------
3 | //
4 | // created: Mon Sep 11 14:47:45 2017
5 | // last saved: <2018-March-15 14:32:16>
6 |
7 | var jwt = require('jsonwebtoken');
8 | var claims = {
9 | iss: 'http://myapp.com/',
10 | sub: 'users/user1234',
11 | aud: 'urn://Apigee',
12 | scope: 'read, add',
13 | roles: ['admin', 'user']
14 | };
15 | var secretPassphrase = 'Secret123';
16 |
17 | // sign with HMAC SHA256
18 | var token = jwt.sign(claims, secretPassphrase, { algorithm: 'HS256'});
19 | console.log('JWT: ' + token);
20 |
21 | var decoded = jwt.decode(token, {complete:true});
22 | console.log('decoded: ' + JSON.stringify(decoded, null, 2));
23 |
--------------------------------------------------------------------------------
/tools/notused/example1.js:
--------------------------------------------------------------------------------
1 | // example1.js
2 | // ------------------------------------------------------------------
3 | //
4 | // created: Mon Sep 11 14:47:45 2017
5 | // last saved: <2017-November-06 12:25:26>
6 |
7 | var jwtnode = require('jwt-node');
8 | var secureRandom = require('secure-random');
9 |
10 | var signingKey = secureRandom(256, {type: 'Buffer'}); // Create a highly random byte array of 256 bytes
11 |
12 | var claims = {
13 | iss: "http://myapp.com/", // The URL of your service
14 | sub: "users/user1234", // The UID of the user in your system
15 | scope: "self, admins"
16 | };
17 |
18 | var jwt = jwtnode.create(claims,signingKey);
19 |
20 | console.log('JWT: ' + jwt);
21 |
22 | //var decoded = jwtnode.create(claims,signingKey);
23 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/RF-UnknownRequest.xml:
--------------------------------------------------------------------------------
1 |
2 | true
3 |
4 |
5 | {
6 | "error" : {
7 | "code" : 404.01,
8 | "message" : "that request was unknown; try a different request.",
9 | "suggestions" : [
10 | "POST /jwt-generate/rs256/1",
11 | "POST /jwt-generate/rs256/2",
12 | "POST /jwt-generate/rs256/3",
13 | "POST /jwt-generate/hs256/1"
14 | ]
15 | }
16 | }
17 |
18 | 404
19 | Not Found
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsonwebtoken-examples",
3 | "version": "1.0.1",
4 | "description": "Some examples of using jsonwebtoken to create JWT",
5 | "main": "create-token.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "demo-rs256": "node ./create-token.js -A RS256 -i Issuer -s Subject -a Audience -C customClaim1:value1",
9 | "demo-hs256": "node ./create-token.js -A HS256 -k Secret1234 -i Issuer -s Subject -a Audience -C customClaim1:value1"
10 | },
11 | "author": "Dino Chiesa ",
12 | "license": "Apache-2.0",
13 | "dependencies": {
14 | "apigee-edge-js": "^0.3.5",
15 | "jsonwebtoken": "^8.2.2",
16 | "node-getopt": "^0.2.4",
17 | "uuid": "^3.2.1"
18 | },
19 | "engines": {
20 | "node": ">=6.0.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/Generate-JWT-RS256-Basic.xml:
--------------------------------------------------------------------------------
1 |
2 | RS256
3 |
4 |
5 | optional-unique-identifier-for-privatekey-here
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | urn://apigee-edge-JWT-policy-demonstration
14 |
15 |
16 |
17 | 60m
18 | output-jwt
19 |
20 |
21 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/Verify-JWT-RS256-Audience-and-Issuer.xml:
--------------------------------------------------------------------------------
1 |
2 | RS256
3 | inbound.jwt
4 |
5 |
6 | -----BEGIN PUBLIC KEY-----
7 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQNvdXmQMHaCj+cKOhaB
8 | SVGMsgElLuAoU4yiHv/NFepQOKt5m3gcyK3t1sE2nMWebTQH1QLC9fANROeXVpJn
9 | 05E57LffR3RFp7bFT8dI6OG7xlpypUcw1KEx6D2uTRQ29GStq2/nM+HNu6RtHJi4
10 | C+Z3dIUsW7nV0FjVZIsCxA1z/fPFVy8rGERaRR+tWHTm5U2jKXEw3ileUv7LGgWM
11 | UMmxuqW2qyrkbVNC+gyI2AKmUV9bo/qLa0BrFxUrK2nRJlxmGnSA09s5CGKix2hP
12 | GxBCvO4wHQ1Wt1PZzDO/fKlkxYiCdALLn8VwKS3JqgInnPUDl1tRi6fDEhL3lKFP
13 | JwIDAQAB
14 | -----END PUBLIC KEY-----
15 |
16 |
17 | urn://Apigee
18 | urn://Issuer
19 |
20 |
--------------------------------------------------------------------------------
/tools/decode-token.js:
--------------------------------------------------------------------------------
1 | // decode-token.js
2 | // ------------------------------------------------------------------
3 | //
4 | // created: Mon Sep 11 14:47:45 2017
5 | // last saved: <2018-March-15 14:09:52>
6 |
7 | var version = '20180315-1409',
8 | jwt = require('jsonwebtoken'),
9 | Getopt = require('node-getopt'),
10 | getopt = new Getopt([
11 | ['t' , 'token=ARG', 'the token in compact form'],
12 | ['h' , 'help', 'display this help']
13 | ]).bindHelp();
14 |
15 | console.log(
16 | 'Example JWT decoder tool, version: ' + version + '\n' +
17 | 'Node.js ' + process.version + '\n');
18 |
19 | var opt = getopt.parse(process.argv.slice(2));
20 | if ( ! opt.options.token) {
21 | console.log('You must provide a token.\n');
22 | getopt.showHelp();
23 | process.exit(1);
24 | }
25 |
26 | var decoded = jwt.decode(opt.options.token, {complete:true});
27 | console.log('\ndecoded:\n' + JSON.stringify(decoded, null, 2));
28 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/policies/AM-JWT-Parse-Response.xml:
--------------------------------------------------------------------------------
1 |
2 | respond with information about the parsed JWT
3 | true
4 |
5 |
6 |
7 |
8 | {
9 | "jwt-kid" : "{jwt.Verify-JWT-1.header.kid}",
10 | "jwt-id" : "{jwt.Verify-JWT-1.claim.id}",
11 | "secondsRemaining" : {jwt.Verify-JWT-1.seconds_remaining},
12 | "timeRemainingFormatted" : "{jwt.Verify-JWT-1.time_remaining_formatted}",
13 | "sub" : "{jwt.Verify-JWT-1.claim.subject}",
14 | "email" : "{jwt.Verify-JWT-1.claim.email}",
15 | "family_name" : "{jwt.Verify-JWT-1.claim.family_name}",
16 | "given_name" : "{jwt.Verify-JWT-1.claim.given_name}"
17 | }
18 |
19 | 200
20 | OK
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tools/1-create-HS256-with-expiry.js:
--------------------------------------------------------------------------------
1 | // 1-create-HS256-with-expiry.js
2 | // ------------------------------------------------------------------
3 | //
4 | // created: Mon Sep 11 14:47:45 2017
5 | // last saved: <2018-May-21 16:10:19>
6 |
7 | const jwt = require('jsonwebtoken'),
8 | uuidv4 = require('uuid/v4'),
9 | expiry = Math.floor(Date.now() / 1000) + 3600, // in seconds
10 | now = Math.floor(Date.now() / 1000);
11 | var claims = {
12 | iss: 'http://myapp.com/',
13 | sub: 'users/user1234',
14 | aud: 'urn://Apigee',
15 | scope: 'read, add',
16 | roles: ['admin', 'user'],
17 | exp: expiry,
18 | nbf: now,
19 | jti: uuidv4()
20 | };
21 | var secretPassphrase = 'Secret123';
22 |
23 | var token = jwt.sign(claims, secretPassphrase, {algorithm: 'HS256'});
24 | console.log('JWT:\n' + token);
25 |
26 | var decoded = jwt.decode(token, {complete:true});
27 | console.log('decoded: ' + JSON.stringify(decoded, null, 2));
28 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-Response-HS256-Basic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 | {
6 | "status" : "ok",
7 | "algorithm" : "{jwt.Verify-JWT-HS256-Basic.header.algorithm}",
8 | "claim_names" : "{jwt.Verify-JWT-HS256-Basic.payload-claim-names}",
9 | "subject" : "{jwt.Verify-JWT-HS256-Basic.claim.subject}",
10 | "issuer" : "{jwt.Verify-JWT-HS256-Basic.claim.issuer}",
11 | "audience" : "{jwt.Verify-JWT-HS256-Basic.claim.audience}",
12 | "expiry" : "{jwt.Verify-JWT-HS256-Basic.expiry_formatted}",
13 | "seconds_remaining" : {jwt.Verify-JWT-HS256-Basic.seconds_remaining},
14 | "out.now" : "{outbound.now}"
15 | }
16 | 200
17 | OK
18 |
19 |
20 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-Response-RS256-Basic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 | {
6 | "status" : "ok",
7 | "algorithm" : "{jwt.Verify-JWT-RS256-Basic.header.algorithm}",
8 | "claim_names" : "{jwt.Verify-JWT-RS256-Basic.payload-claim-names}",
9 | "subject" : "{jwt.Verify-JWT-RS256-Basic.claim.subject}",
10 | "issuer" : "{jwt.Verify-JWT-RS256-Basic.claim.issuer}",
11 | "audience" : "{jwt.Verify-JWT-RS256-Basic.claim.audience}",
12 | "expiry" : "{jwt.Verify-JWT-RS256-Basic.expiry_formatted}",
13 | "seconds_remaining" : {jwt.Verify-JWT-RS256-Basic.seconds_remaining},
14 | "out.now" : "{outbound.now}"
15 | }
16 | 200
17 | OK
18 |
19 |
20 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/Generate-JWT-RS256-AdditionalClaims.xml:
--------------------------------------------------------------------------------
1 |
2 | RS256
3 |
4 |
5 | unique-identifier-for-privatekey-here
6 |
7 |
8 |
9 |
10 | urn://apigee-edge-JWT-policy-demonstration
11 |
12 | 60m
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | output-jwt
21 |
22 |
--------------------------------------------------------------------------------
/tools/notused/example3.js:
--------------------------------------------------------------------------------
1 | // example3.js
2 | // ------------------------------------------------------------------
3 | //
4 | // Description goes here....
5 | //
6 | // created: Mon Sep 11 15:15:47 2017
7 | // last saved: <2017-September-14 16:53:37>
8 |
9 | // sign with default (HMAC SHA256)
10 | var jwt = require('jsonwebtoken');
11 | var passphrase = 'secret1234567890ABCDEFGH';
12 |
13 | function showToken(token) {
14 | console.log(token);
15 | var parts = token.split('.');
16 | parts.forEach(function(part){
17 | console.log(part);
18 | });
19 | console.log();
20 | }
21 |
22 | var additionalHeaderFields = { } ;
23 | var payload = {
24 | 'double-values': [1982922.23039, -920.398398212, 239892.98494803]
25 | };
26 |
27 | var options = { algorithm: 'HS256', noTimestamp:true };
28 | var token = jwt.sign(payload, passphrase, options);
29 | showToken(token);
30 |
31 |
32 | passphrase = 'secret';
33 |
34 | payload = {
35 | name: [1, 2, 3]
36 | };
37 |
38 | var token = jwt.sign(payload, passphrase, options);
39 | showToken(token);
40 |
41 |
42 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/jwt-generate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | jwt-generate
4 | 1510024978294
5 | dchiesa@google.com
6 | Generate a JWT
7 | 1510024978294
8 | dchiesa@google.com
9 |
10 | AM-ClearResponseHeaders
11 | AM-PrivateKey
12 | AM-Response-1
13 | AM-SecretKey
14 | Generate-JWT-1
15 | Generate-JWT-2
16 | RF-MissingParam
17 | RF-UnknownRequest
18 |
19 |
20 | default
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-Response-3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 | {
6 | "status" : "ok",
7 | "algorithm" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer.header.algorithm}",
8 | "claim_names" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer.payload-claim-names}",
9 | "subject" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer.claim.subject}",
10 | "issuer" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer.claim.issuer}",
11 | "audience" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer.claim.audience}",
12 | "expiry" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer.expiry_formatted}",
13 | "seconds_remaining" : {jwt.Verify-JWT-RS256-Audience-and-Issuer.seconds_remaining},
14 | "out.now" : "{outbound.now}"
15 | }
16 | 200
17 | OK
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tools/2-create-RS256-with-expiry.js:
--------------------------------------------------------------------------------
1 | // 2-create-RS256-with-expiry.js
2 | // ------------------------------------------------------------------
3 | //
4 | // created: Mon Sep 11 14:47:45 2017
5 | // last saved: <2018-May-21 15:51:17>
6 |
7 | const jwt = require('jsonwebtoken'),
8 | fs = require('fs'),
9 | path = require('path'),
10 | uuidv4 = require('uuid/v4'),
11 | now = Math.floor(Date.now() / 1000),
12 | oneHourInSeconds = 3600;
13 | var claims = {
14 | iss: 'http://myapp.com/',
15 | sub: 'users/user1234',
16 | aud: 'urn://Apigee',
17 | roles: ['admin', 'user'],
18 | exp: now + oneHourInSeconds,
19 | nbf: now,
20 | jti: uuidv4()
21 | };
22 |
23 | var filename = path.resolve(path.dirname(process.mainModule.filename), 'keys', 'private-pkcs8.pem');
24 | var cert = fs.readFileSync(filename); // get private key
25 | var token = jwt.sign(claims, cert, { algorithm: 'RS256'});
26 | console.log('JWT:\n' + token);
27 |
28 | var decoded = jwt.decode(token, {complete:true});
29 | console.log('decoded: ' + JSON.stringify(decoded, null, 2));
30 |
--------------------------------------------------------------------------------
/tools/keys/README.md:
--------------------------------------------------------------------------------
1 | # Sample Keys
2 |
3 | The keys included here are for demonstration purposes only.
4 | They will be used at runtime, by default, by the create-token and verify-token tools in the parent directory.
5 | These keys are also embedded into the verify and generate API proxies .
6 |
7 | ## Creating a new Key Pair
8 |
9 | To use openssl to create a keypair, follow these three steps. These steps require the openssl tool, which is available on Linux, OSX, and Windows.
10 |
11 | ### generate the key pair in RSA format (PKCS#1) without encryption
12 | ```
13 | openssl genrsa -out private.pem 2048
14 | ```
15 |
16 | ### convert the above to PKCS#8 format
17 |
18 | ```
19 | openssl pkcs8 -topk8 -inform pem -in private.pem -outform pem -nocrypt -out private-pkcs8.pem
20 |
21 | ```
22 | ### extract the public key from that:
23 | ```
24 | openssl rsa -in private.pem -outform PEM -pubout -out public.pem
25 | ```
26 |
27 | After doing this, you will need to update the verify and generate API proxies to reference these new values,
28 | and also load these values into the KVM.
29 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/AM-Response-4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 | {
6 | "status" : "ok",
7 | "algorithm" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer2.header.algorithm}",
8 | "claim_names" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer2.payload-claim-names}",
9 | "subject" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer2.claim.subject}",
10 | "issuer" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer2.claim.issuer}",
11 | "audience" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer2.claim.audience}",
12 | "expiry" : "{jwt.Verify-JWT-RS256-Audience-and-Issuer2.expiry_formatted}",
13 | "seconds_remaining" : {jwt.Verify-JWT-RS256-Audience-and-Issuer2.seconds_remaining},
14 | "out.now" : "{outbound.now}"
15 | }
16 | 200
17 | OK
18 |
19 |
20 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/policies/Verify-JWT-RS256-Basic.xml:
--------------------------------------------------------------------------------
1 |
2 | RS256
3 | inbound.jwt
4 |
5 |
6 | -----BEGIN PUBLIC KEY-----
7 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQNvdXmQMHaCj+cKOhaB
8 | SVGMsgElLuAoU4yiHv/NFepQOKt5m3gcyK3t1sE2nMWebTQH1QLC9fANROeXVpJn
9 | 05E57LffR3RFp7bFT8dI6OG7xlpypUcw1KEx6D2uTRQ29GStq2/nM+HNu6RtHJi4
10 | C+Z3dIUsW7nV0FjVZIsCxA1z/fPFVy8rGERaRR+tWHTm5U2jKXEw3ileUv7LGgWM
11 | UMmxuqW2qyrkbVNC+gyI2AKmUV9bo/qLa0BrFxUrK2nRJlxmGnSA09s5CGKix2hP
12 | GxBCvO4wHQ1Wt1PZzDO/fKlkxYiCdALLn8VwKS3JqgInnPUDl1tRi6fDEhL3lKFP
13 | JwIDAQAB
14 | -----END PUBLIC KEY-----
15 |
16 |
23 |
24 |
25 |
26 |
28 | urn://Apigee
29 |
30 |
--------------------------------------------------------------------------------
/tools/notused/example2.js:
--------------------------------------------------------------------------------
1 | // example2.js
2 | // ------------------------------------------------------------------
3 | //
4 | // Description goes here....
5 | //
6 | // created: Mon Sep 11 15:15:47 2017
7 | // last saved: <2017-September-14 16:53:49>
8 |
9 | // sign with default (HMAC SHA256)
10 | function showToken(token) {
11 | console.log(token);
12 | var parts = token.split('.');
13 | parts.forEach(function(part){
14 | console.log(part);
15 | });
16 | console.log();
17 | }
18 |
19 | var jwt = require('jsonwebtoken');
20 | var passphrase = 'ABCDEFGHIJK0123456789';
21 | var token = jwt.sign({ foo: 'bar' }, passphrase);
22 | showToken(token);
23 |
24 | var header = {
25 | typ: 'JWT',
26 | b64: false,
27 | 'http://openbanking.org.uk/iss' : 'C=UK, ST=England, L=London, O=Acme Ltd.',
28 | 'http://openbanking.org.uk/iat' : '2017-06-12T20:05:50+00:00',
29 | crit : ['b64', 'http://openbanking.org.uk/iss', 'http://openbanking.org.uk/iat']
30 | };
31 | var data = {
32 | potential: 'high',
33 | acctid : 'AA7F9F9B-CDF5-4CD0-949E-829AC879E201'
34 | };
35 |
36 | var risk = {
37 | seven: 5
38 | };
39 | var payload = { iss: 'bar', data:data, risk:risk };
40 | var options = { algorithm: 'HS256', header:header, noTimestamp: false};
41 | var token = jwt.sign(payload, passphrase, options);
42 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/jwt-verify.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1510017627125
5 | dchiesa@google.com
6 | Verify and/or Decode inbound JWT
7 | jwt-verify
8 | 1510018654205
9 | dchiesa@google.com
10 |
11 | AM-ClearResponseHeaders
12 | AM-Response-1
13 | AM-Response-2
14 | AM-Response-3
15 | AM-SecretKey
16 | EV-JWT
17 | JS-SetNow
18 | RF-InvalidToken
19 | RF-MissingHeader
20 | RF-UnknownRequest
21 | Verify-JWT-1
22 | Verify-JWT-2
23 | Verify-JWT-3
24 |
25 |
26 | default
27 |
28 |
29 | jsc://setNow.js
30 |
31 |
32 |
33 |
34 | false
35 |
36 |
--------------------------------------------------------------------------------
/tools/keys/private.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEogIBAAKCAQEAwQNvdXmQMHaCj+cKOhaBSVGMsgElLuAoU4yiHv/NFepQOKt5
3 | m3gcyK3t1sE2nMWebTQH1QLC9fANROeXVpJn05E57LffR3RFp7bFT8dI6OG7xlpy
4 | pUcw1KEx6D2uTRQ29GStq2/nM+HNu6RtHJi4C+Z3dIUsW7nV0FjVZIsCxA1z/fPF
5 | Vy8rGERaRR+tWHTm5U2jKXEw3ileUv7LGgWMUMmxuqW2qyrkbVNC+gyI2AKmUV9b
6 | o/qLa0BrFxUrK2nRJlxmGnSA09s5CGKix2hPGxBCvO4wHQ1Wt1PZzDO/fKlkxYiC
7 | dALLn8VwKS3JqgInnPUDl1tRi6fDEhL3lKFPJwIDAQABAoIBADzoYZ7XmqnAbBkP
8 | FooYGfpSEq0FcX9mWzNqWKqhnE1DIMeRTRHDiInHYRt440v7jK/3UFVmxrEnbHiH
9 | AJngH4WC+Z16tKnuxiBTq9YuFBLxUPkaB72iRoWCzKX1+O/35hOfRbKo4HPd2LdS
10 | /t+cTc/rjVymvcXAUfmqXsSYnWnSLKXF065sgDcI2iijOrfxyByu7Yjp1/x0q2Ip
11 | 0rA07C0JMZE6EK1sRb/FOg0t7EEBlGI3NIKqYG49CZ5QunpDGAk5Ny6IrfTJqpHW
12 | InTxwiot89bB9PmKOcUMBmxfgrpmZJtkQwxhgt/xeOJDPEJx0/FyfTvMNegYB6jF
13 | QXFJyxkCgYEA5DyynLqT6EtY/fx6nVxSRphgfqVAWkUQ/flM/3Ud7jfa34Q2OOyP
14 | va4zqNKaoiNwE2BWBRm3+Dn6xfe/pk09b5IO1sZ5381O8/TD92gMpSDYwAaUU6lR
15 | FrIEXx8v7E+8EjtJfnWU8ElMDNLWqjrlinTPYXymYjhq01luLvVYq+0CgYEA2H3f
16 | VohtYNYj+jcTKjZ7Oxm0HLUeS+fNU9NMZ6mdCES2C7r6DrQJEgZALBd6Gk/v8+FV
17 | TvD3SaTk2MkzOIFD+7mYp8ECuxnv9kGlkrBSwAY4+JWGlog4JIyo3vlXdxrwh0NE
18 | 9tcFel8MVeKWXtplLy7GywMCQTSKRJ2V1xYAzOMCgYAx4mJXhyAUwYMt4CUch7Uj
19 | 4OIWTCAImtff6sE9hGeKTYL1H3yKbInuN0jrOTy6+2vfkEq8yqY/Bs0cv82Pl4o5
20 | /H5pyu+QTttWzsSWKpO487jsH4QD1Rn4D+iMsdBTognFIlgp36Ex85M/qkVo536M
21 | CIPbFtd22EXg88b1VIKQ8QKBgF9J1/nQhsQuvRsvDqJv5IG46IwSNEmYt8mDoYen
22 | IlU3WSGEpPe/ypZCZhCCLN5T/PvNXK0oZs/lYk0BEih2zSOJJ2X/EGKmIfa0IlqU
23 | fXfDxWO0/M5ZGs0LlHDAMv1bwC7pLvuu3Nxl5ZoP57vMSOkhn6JPqrhklc9pxUAs
24 | kSGdAoGAS7f7mASaJHLgubjbMvOLmVqhI6yXpvyYz7JpSRTsUnzFjz3lxcJ6PKOK
25 | qBaT8sVDfazz0wuMCLrFg73119XhwcnOAvR1/Ko4Np59aFSzuwlRCMZq8FDXKEvs
26 | IsmCSIcoFYnoHupbIUuQ46UHOFEIfklySdgoFDWXRRDfMtC9yVw=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/tools/keys/private-pkcs8.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDBA291eZAwdoKP
3 | 5wo6FoFJUYyyASUu4ChTjKIe/80V6lA4q3mbeBzIre3WwTacxZ5tNAfVAsL18A1E
4 | 55dWkmfTkTnst99HdEWntsVPx0jo4bvGWnKlRzDUoTHoPa5NFDb0ZK2rb+cz4c27
5 | pG0cmLgL5nd0hSxbudXQWNVkiwLEDXP988VXLysYRFpFH61YdOblTaMpcTDeKV5S
6 | /ssaBYxQybG6pbarKuRtU0L6DIjYAqZRX1uj+otrQGsXFSsradEmXGYadIDT2zkI
7 | YqLHaE8bEEK87jAdDVa3U9nMM798qWTFiIJ0AsufxXApLcmqAiec9QOXW1GLp8MS
8 | EveUoU8nAgMBAAECggEAPOhhnteaqcBsGQ8WihgZ+lISrQVxf2ZbM2pYqqGcTUMg
9 | x5FNEcOIicdhG3jjS/uMr/dQVWbGsSdseIcAmeAfhYL5nXq0qe7GIFOr1i4UEvFQ
10 | +RoHvaJGhYLMpfX47/fmE59Fsqjgc93Yt1L+35xNz+uNXKa9xcBR+apexJidadIs
11 | pcXTrmyANwjaKKM6t/HIHK7tiOnX/HSrYinSsDTsLQkxkToQrWxFv8U6DS3sQQGU
12 | Yjc0gqpgbj0JnlC6ekMYCTk3Loit9MmqkdYidPHCKi3z1sH0+Yo5xQwGbF+CumZk
13 | m2RDDGGC3/F44kM8QnHT8XJ9O8w16BgHqMVBcUnLGQKBgQDkPLKcupPoS1j9/Hqd
14 | XFJGmGB+pUBaRRD9+Uz/dR3uN9rfhDY47I+9rjOo0pqiI3ATYFYFGbf4OfrF97+m
15 | TT1vkg7WxnnfzU7z9MP3aAylINjABpRTqVEWsgRfHy/sT7wSO0l+dZTwSUwM0taq
16 | OuWKdM9hfKZiOGrTWW4u9Vir7QKBgQDYfd9WiG1g1iP6NxMqNns7GbQctR5L581T
17 | 00xnqZ0IRLYLuvoOtAkSBkAsF3oaT+/z4VVO8PdJpOTYyTM4gUP7uZinwQK7Ge/2
18 | QaWSsFLABjj4lYaWiDgkjKje+Vd3GvCHQ0T21wV6XwxV4pZe2mUvLsbLAwJBNIpE
19 | nZXXFgDM4wKBgDHiYleHIBTBgy3gJRyHtSPg4hZMIAia19/qwT2EZ4pNgvUffIps
20 | ie43SOs5PLr7a9+QSrzKpj8GzRy/zY+Xijn8fmnK75BO21bOxJYqk7jzuOwfhAPV
21 | GfgP6Iyx0FOiCcUiWCnfoTHzkz+qRWjnfowIg9sW13bYReDzxvVUgpDxAoGAX0nX
22 | +dCGxC69Gy8Oom/kgbjojBI0SZi3yYOhh6ciVTdZIYSk97/KlkJmEIIs3lP8+81c
23 | rShmz+ViTQESKHbNI4knZf8QYqYh9rQiWpR9d8PFY7T8zlkazQuUcMAy/VvALuku
24 | +67c3GXlmg/nu8xI6SGfok+quGSVz2nFQCyRIZ0CgYBLt/uYBJokcuC5uNsy84uZ
25 | WqEjrJem/JjPsmlJFOxSfMWPPeXFwno8o4qoFpPyxUN9rPPTC4wIusWDvfXX1eHB
26 | yc4C9HX8qjg2nn1oVLO7CVEIxmrwUNcoS+wiyYJIhygViege6lshS5DjpQc4UQh+
27 | SXJJ2CgUNZdFEN8y0L3JXA==
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/policies/AM-PrivateKey.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | private.privatekey
4 |
5 |
6 | -----BEGIN PRIVATE KEY-----
7 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDBA291eZAwdoKP
8 | 5wo6FoFJUYyyASUu4ChTjKIe/80V6lA4q3mbeBzIre3WwTacxZ5tNAfVAsL18A1E
9 | 55dWkmfTkTnst99HdEWntsVPx0jo4bvGWnKlRzDUoTHoPa5NFDb0ZK2rb+cz4c27
10 | pG0cmLgL5nd0hSxbudXQWNVkiwLEDXP988VXLysYRFpFH61YdOblTaMpcTDeKV5S
11 | /ssaBYxQybG6pbarKuRtU0L6DIjYAqZRX1uj+otrQGsXFSsradEmXGYadIDT2zkI
12 | YqLHaE8bEEK87jAdDVa3U9nMM798qWTFiIJ0AsufxXApLcmqAiec9QOXW1GLp8MS
13 | EveUoU8nAgMBAAECggEAPOhhnteaqcBsGQ8WihgZ+lISrQVxf2ZbM2pYqqGcTUMg
14 | x5FNEcOIicdhG3jjS/uMr/dQVWbGsSdseIcAmeAfhYL5nXq0qe7GIFOr1i4UEvFQ
15 | +RoHvaJGhYLMpfX47/fmE59Fsqjgc93Yt1L+35xNz+uNXKa9xcBR+apexJidadIs
16 | pcXTrmyANwjaKKM6t/HIHK7tiOnX/HSrYinSsDTsLQkxkToQrWxFv8U6DS3sQQGU
17 | Yjc0gqpgbj0JnlC6ekMYCTk3Loit9MmqkdYidPHCKi3z1sH0+Yo5xQwGbF+CumZk
18 | m2RDDGGC3/F44kM8QnHT8XJ9O8w16BgHqMVBcUnLGQKBgQDkPLKcupPoS1j9/Hqd
19 | XFJGmGB+pUBaRRD9+Uz/dR3uN9rfhDY47I+9rjOo0pqiI3ATYFYFGbf4OfrF97+m
20 | TT1vkg7WxnnfzU7z9MP3aAylINjABpRTqVEWsgRfHy/sT7wSO0l+dZTwSUwM0taq
21 | OuWKdM9hfKZiOGrTWW4u9Vir7QKBgQDYfd9WiG1g1iP6NxMqNns7GbQctR5L581T
22 | 00xnqZ0IRLYLuvoOtAkSBkAsF3oaT+/z4VVO8PdJpOTYyTM4gUP7uZinwQK7Ge/2
23 | QaWSsFLABjj4lYaWiDgkjKje+Vd3GvCHQ0T21wV6XwxV4pZe2mUvLsbLAwJBNIpE
24 | nZXXFgDM4wKBgDHiYleHIBTBgy3gJRyHtSPg4hZMIAia19/qwT2EZ4pNgvUffIps
25 | ie43SOs5PLr7a9+QSrzKpj8GzRy/zY+Xijn8fmnK75BO21bOxJYqk7jzuOwfhAPV
26 | GfgP6Iyx0FOiCcUiWCnfoTHzkz+qRWjnfowIg9sW13bYReDzxvVUgpDxAoGAX0nX
27 | +dCGxC69Gy8Oom/kgbjojBI0SZi3yYOhh6ciVTdZIYSk97/KlkJmEIIs3lP8+81c
28 | rShmz+ViTQESKHbNI4knZf8QYqYh9rQiWpR9d8PFY7T8zlkazQuUcMAy/VvALuku
29 | +67c3GXlmg/nu8xI6SGfok+quGSVz2nFQCyRIZ0CgYBLt/uYBJokcuC5uNsy84uZ
30 | WqEjrJem/JjPsmlJFOxSfMWPPeXFwno8o4qoFpPyxUN9rPPTC4wIusWDvfXX1eHB
31 | yc4C9HX8qjg2nn1oVLO7CVEIxmrwUNcoS+wiyYJIhygViege6lshS5DjpQc4UQh+
32 | SXJJ2CgUNZdFEN8y0L3JXA==
33 | -----END PRIVATE KEY-----
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/proxies/default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | /jwt-verify-goog
4 | secure
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 | (proxy.pathsuffix MatchesPath "/t1") and (request.verb = "GET")
22 | Parse / Verify a JWT created by Google Sign-in
23 |
24 |
25 | request.header.authorization = null
26 | RF-MissingHeader
27 |
28 |
29 | EV-JWT
30 |
31 |
32 |
33 |
34 |
35 | CacheLookup-GoogleJwks
36 |
37 |
38 | SC-RetrieveGoogleJwks
39 | cached.google.jwks = null
40 |
41 |
42 | CacheInsert-GoogleJwks
43 | cached.google.jwks = null
44 |
45 |
46 |
47 | AV-GoogleJwks
48 | cached.google.jwks = null
49 |
50 |
51 |
52 |
53 |
54 | Verify-JWT-1
55 |
56 |
57 |
58 | AM-JWT-Parse-Response
59 |
60 |
61 |
62 |
63 |
66 | Clears the cache
67 |
68 | CacheInsert-Nothing
69 |
70 |
71 | AM-NoContent
72 |
73 | (proxy.pathsuffix MatchesPath "/clear-cache") and (request.verb = "POST")
74 |
75 |
76 |
77 |
78 | RF-UnknownRequest
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/tools/create-token.js:
--------------------------------------------------------------------------------
1 | // create-token.js
2 | // ------------------------------------------------------------------
3 | //
4 | // created: Mon Sep 11 14:47:45 2017
5 | // last saved: <2018-March-15 14:53:47>
6 |
7 | var version = '20180315-1453',
8 | jwt = require('jsonwebtoken'),
9 | fs = require('fs'),
10 | path = require('path'),
11 | now = Math.floor(Date.now() / 1000),
12 | oneHourInSeconds = 3600,
13 | defaults = {expiry: oneHourInSeconds, key : path.join(__dirname, 'keys', 'private-pkcs8.pem'), alg: 'HS256'},
14 | supportedAlgorithms = ['RS256','RS384','RS512','HS256','HS384','HS512'],
15 | Getopt = require('node-getopt'),
16 | getopt = new Getopt([
17 | ['A' , 'alg=ARG', 'required. algorithm. One of: ' + JSON.stringify(supportedAlgorithms)],
18 | ['s' , 'sub=ARG', 'optional. subject'],
19 | ['i' , 'iss=ARG', 'optional. issuer'],
20 | ['a' , 'aud=ARG', 'optional. audience'],
21 | ['e' , 'exp=ARG', 'expiry in seconds (default: ' + defaults.expiry + ')'],
22 | ['k' , 'key=ARG', 'for RS256, file containing signing key in PKCS8 format (default: ' + defaults.key + '). For HS256, the secretkey. (no default)'],
23 | ['N' , 'noNbf', 'omit the nbf claim'],
24 | ['I' , 'noIat', 'omit the iat claim'],
25 | ['C' , 'claim=ARG+', 'a custom claim to insert, in the format NAME:VALUE'],
26 | ['h' , 'help', 'display this help']
27 | ]).bindHelp();
28 |
29 | console.log(
30 | 'Example JWT creator tool, version: ' + version + '\n' +
31 | 'Node.js ' + process.version + '\n');
32 |
33 | var opt = getopt.parse(process.argv.slice(2));
34 | if ( ! opt.options.alg) { opt.options.alg = defaults.alg; }
35 |
36 | if (supportedAlgorithms.indexOf(opt.options.alg) < 0) {
37 | console.log('invalid value for algorithm.\n');
38 | getopt.showHelp();
39 | process.exit(1);
40 | }
41 | var key = (opt.options.alg.startsWith('RS')) ?
42 | fs.readFileSync(opt.options.key || defaults.key) : opt.options.key ;
43 | if ( ! key) {
44 | console.log('You must provide a key.\n');
45 | getopt.showHelp();
46 | process.exit(1);
47 | }
48 | var claims = { };
49 | if (! opt.options.noNbf) { claims.nbf = now; }
50 | if (! opt.options.noIat) { claims.iat = now; }
51 | if (opt.options.sub) { claims.sub = opt.options.sub; }
52 | if (opt.options.iss) { claims.iss = opt.options.iss; }
53 | if (opt.options.aud) { claims.aud = opt.options.aud; }
54 | if (opt.options.exp) {
55 | claims.exp = now + parseInt(opt.options.exp);
56 | }
57 | else {
58 | claims.exp = now + defaults.expiry;
59 | }
60 |
61 | var re = new RegExp(':(.+)');
62 | if (opt.options.claim) {
63 | opt.options.claim.forEach((claimString) => {
64 | var parts = claimString.split(re, 2);
65 | if (parts.length === 2) {
66 | claims[parts[0]] = parts[1];
67 | }
68 | else {
69 | console.log('non-parseable custom claim: ' + claimString);
70 | }
71 | });
72 | }
73 |
74 | var token = jwt.sign(claims, key, { algorithm: opt.options.alg });
75 | console.log('JWT=' + token);
76 |
77 | var decoded = jwt.decode(token, {complete:true});
78 | console.log('\ndecoded:\n' + JSON.stringify(decoded, null, 2));
79 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | # Example tools for JWT Demonstrations
2 |
3 | This directory contains an example tools for the command-line. They can be used for creating JWT, or for verifying JWT.
4 | The tools are implemented in nodejs, and require node to be installed on the workstation, in order to run them.
5 |
6 | ## To use: Pre-requisites
7 |
8 | Before using any of the tools here, you need to install the pre-requisites.
9 |
10 | You need node version 6.0 or greater. And you need npm.
11 | To get those, follow the instructions for your platform.
12 | For MacOS, you might look into `brew install node` and `brew install npm`.
13 |
14 | ## Installing the dependencies.
15 |
16 | Make sure you have the dependency modules. These are the node modules the scripts here, depend upon.
17 |
18 | 1. `npm install`
19 |
20 |
21 |
22 | ## Uploading the key into the KVM
23 |
24 | Do this to upload the private or public key into the KVM, so that it can be used by Apigee Edge.
25 |
26 | Example loading the private key into an _encrypted_ map called "secrets":
27 | ```
28 | node ./loadPemIntoKvm.js -v -o $ORG -e $ENV -E -m secrets -N key1 -F keys/private-pkcs8.pem
29 | ```
30 |
31 | Example loading the public key into a map called "non-secrets":
32 | ```
33 | node ./loadPemIntoKvm.js -v -o $ORG -e $ENV -m non-secrets -N key1 -F keys/public.pem
34 | ```
35 |
36 | You need to do this prior to using some of the flows in this demonstration.
37 |
38 | If you are using OPDK, then you may need to use the -T option on that command line:
39 | ```
40 | node ./loadPemIntoKvm.js -v -o $ORG -e $ENV -E -m secrets -N key1 -F keys/private-pkcs8.pem -T
41 | node ./loadPemIntoKvm.js -v -o $ORG -e $ENV -m non-secrets -N key1 -F keys/public.pem -T
42 | ```
43 |
44 | This tells the script to not use the token endpoint when authenticating to the management server.
45 |
46 |
47 |
48 | ## Creating a token
49 |
50 | Do this to create a token that you would like to send to Apigee Edge for verification.
51 |
52 | ```
53 | node ./create-token.js -A RS256 -s subject -i issuer -a audience
54 | ```
55 |
56 |
57 | ## Verifying a token
58 |
59 | Do this to verify a token generated by the Apigee Edge proxy.
60 |
61 | ```
62 | node ./verify-token.js -t token -A RS256
63 | ```
64 |
65 | ### Example
66 |
67 | ```
68 | $ node ../tools/verify-token.js -t eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJBYmJhRGFiYmEiLCJhdWQiOiJBMjk4MjkyOTIiLCJpc3MiOiJ1cm46Ly9hcGlnZWUtZWRnZS1KV1QtcG9saWN5LWRlbW9uc3RyYXRpb24iLCJleHAiOjE1MTAwMjg3MTEsImlhdCI6MTUxMDAyNTExMSwianRpIjoiY2M2NzAzZmItN2M4Yy00YmNhLWFmNTMtNjdmYzcxOGUxNTdkIn0.bnSLyzdQqJn5fHbjFyd4iNPX1q5TIZ5MwmeM7PHzVrwW-yShbx5IG81Lx0j6Pn_6qz4bSqLUJPuzEPNikKVeYV0zlUA3TO5_z412rzo0JkYmQ48l5SCgrPzDXLg6JsUv6FBfupGPnyrtMJT0DhnQXO4m_tkwG2lR_zJGMkI7G-rIOGEHD-RGNQ6Fzoat8V-xJiVQG07vp691UQOyDkq737mWDg_b4Sm_8ZdihR1aI0TfSWFvyi-66IOc00-FmZzOgEyhZBTTIQz4V4WEeaX2_YdnOCO_KvBgZUFNYcp94TcJW1PkBwJcWiAolJexd6PtnTVOnRhoDgmjedhAMrVU_w -A RS256 -s AbbaDabba -C jti:cc6703fb-7c8c-4bca-af53-67fc718e157d -C iss:urn://apigee-edge-JWT-policy-demonstration -a A29829292
69 | Example JWT verifier tool, version: 20171106-1714
70 | Node.js v7.7.1
71 |
72 | decoded:
73 | {
74 | "sub": "AbbaDabba",
75 | "aud": "A29829292",
76 | "iss": "urn://apigee-edge-JWT-policy-demonstration",
77 | "exp": 1510028711,
78 | "iat": 1510025111,
79 | "jti": "cc6703fb-7c8c-4bca-af53-67fc718e157d"
80 | }
81 |
82 | ```
83 |
84 |
85 |
86 |
87 | ## License
88 |
89 | This material is copyright 2017-2018 Google LLC.
90 | and is licensed under the [Apache 2.0 License](LICENSE).
91 |
--------------------------------------------------------------------------------
/tools/verify-token.js:
--------------------------------------------------------------------------------
1 | // verify-token.js
2 | // ------------------------------------------------------------------
3 | //
4 | // created: Mon Sep 11 14:47:45 2017
5 | // last saved: <2018-May-21 10:19:32>
6 |
7 | var version = '20180521-1019',
8 | jwt = require('jsonwebtoken'),
9 | fs = require('fs'),
10 | path = require('path'),
11 | re = new RegExp(':(.+)'),
12 | oneHourInSeconds = 3600,
13 | defaults = {expiry: oneHourInSeconds, key : path.join(__dirname, 'keys/public.pem'), alg: 'HS256'},
14 | supportedAlgorithms = ['RS256','RS384','RS512','HS256','HS384','HS512'],
15 | Getopt = require('node-getopt'),
16 | getopt = new Getopt([
17 | ['t' , 'token=ARG', 'required. token to verify.'],
18 | ['A' , 'alg=ARG', 'required. algorithm. One of: ' + JSON.stringify(supportedAlgorithms)],
19 | ['s' , 'sub=ARG', 'optional. subject to verify'],
20 | ['i' , 'iss=ARG', 'optional. issuer to verify'],
21 | ['a' , 'aud=ARG', 'optional. audience to verify'],
22 | ['k' , 'key=ARG', 'for RS256, file containing signing key in PKCS8 format (default: ' + defaults.privatekey + '). For HS256, the secretkey.'],
23 | ['C' , 'claim=ARG+', 'a custom claim to verify, in the format NAME:VALUE'],
24 | ['h' , 'help', 'display this help']
25 | ]).bindHelp();
26 |
27 | console.log(
28 | 'Example JWT verifier tool, version: ' + version + '\n' +
29 | 'Node.js ' + process.version + '\n');
30 |
31 | var opt = getopt.parse(process.argv.slice(2));
32 | if (! opt.options.token) {
33 | console.log('You must provide a token.\n');
34 | getopt.showHelp();
35 | process.exit(1);
36 | }
37 |
38 | if (! opt.options.alg) { opt.options.alg = defaults.alg; }
39 | if (supportedAlgorithms.indexOf(opt.options.alg) < 0) {
40 | console.log('invalid value for algorithm.\n');
41 | getopt.showHelp();
42 | process.exit(1);
43 | }
44 | var key = (opt.options.alg.startsWith('RS')) ?
45 | fs.readFileSync(opt.options.key || defaults.key) : opt.options.key ;
46 |
47 | if (! key) {
48 | console.log('no key, just decoding the token....\n');
49 | var decoded = jwt.decode(opt.options.token, {complete: true});
50 | console.log(decoded.header);
51 | console.log(decoded.payload);
52 | }
53 | else {
54 | var verificationOptions = { algorithms: [opt.options.alg] };
55 | if (opt.options.sub) { verificationOptions.subject = opt.options.sub; }
56 | if (opt.options.iss) { verificationOptions.issuer = opt.options.iss; }
57 | if (opt.options.aud) { verificationOptions.audience = opt.options.aud; }
58 |
59 | jwt.verify(opt.options.token, key, verificationOptions, function(e, decoded) {
60 | if (e) {
61 | console.log('Error while verifying: ' + e);
62 | decoded = jwt.decode(opt.options.token, {complete:true});
63 | console.log('\ndecoded:\n' + JSON.stringify(decoded, null, 2));
64 | process.exit(1);
65 | }
66 | console.log('\nThat token verifies successfully.\n');
67 | decoded = jwt.decode(opt.options.token, {complete:true});
68 | console.log('\ndecoded:\n' + JSON.stringify(decoded, null, 2));
69 | if (opt.options.claim) {
70 | opt.options.claim.forEach((claimString) => {
71 | var parts = claimString.split(re, 2);
72 | if (parts.length === 2) {
73 | if (decoded[parts[0]] && decoded[parts[0]] === parts[1]) {
74 | }
75 | else {
76 | console.log('invalid: claim mismatch: ' + parts[0]);
77 | console.log(' ' + parts[1] + ' != ' + decoded[parts[0]] );
78 | }
79 | }
80 | else {
81 | console.log('non-parseable custom claim: ' + claimString);
82 | }
83 | });
84 | }
85 | });
86 | }
87 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify-goog/apiproxy/README.md:
--------------------------------------------------------------------------------
1 | # JWT Verification - token generated by Google Sign-in
2 |
3 | This API proxy validates a JWT that was created and signed by Google Sign-in.
4 |
5 | This proxy will work on the Apigee Edge public cloud release.
6 |
7 | ## Deploying
8 |
9 | Several notes:
10 |
11 | * use a command-line tool like
12 |
13 | * [the Powershell Module](https://github.com/DinoChiesa/Edge-Powershell-Admin)
14 | * [apigeetool](https://github.com/apigee/apigeetool-node)
15 | * [pushapi](https://github.com/carloseberhardt/apiploy)
16 | * [importAndDeploy.js](https://github.com/DinoChiesa/apigee-edge-js/blob/master/examples/importAndDeploy.js)
17 |
18 | to deploy the proxy. Or use the Apigee Edge Administrative UI.
19 |
20 | * Before you deploy the proxy you need to create a cache on the
21 | environment. The cache should be named 'cache1'.
22 |
23 | ## Invoking
24 |
25 | To verify a token generated by Google, use this curl command:
26 |
27 | ```
28 | curl -i -X POST http://myorg-test.apigee.net/jwt-verify-goog/t1 -H "Authorization: Bearer $jwt"
29 |
30 | ```
31 |
32 | This validation uses the Google certificate, which is obtained from [the well-known public endpoint](https://www.googleapis.com/oauth2/v3/certs).
33 |
34 | To actually obtain a JWT
35 | generated by Google, you need to sign-in using a client_id registered with
36 | Google Sign-in. Read about that [here](https://developers.google.com/identity/protocols/OpenIDConnect).
37 |
38 | Once you register an app with Google, here's [a page that can help you start the Google Sign-in process](https://dinochiesa.github.io/openid-connect/goog-login.html).
39 |
40 |
41 | ## Commentary
42 |
43 | The JWT Verification policy in Apigee Edge is smart enough to extract keys from a JWK set. Google Sign-in [exposes a JWK set](https://www.googleapis.com/oauth2/v3/certs). This endpoint provides the public keys that correspond to the private keys used by Google Sign-in to sign JWT. Each public key is identified by a "key ID", aka "kid". The content available at this endpoint changes as Google rotates keys, but currently the response looks like this:
44 |
45 | ```json
46 | {
47 | "keys": [
48 | {
49 | "kty": "RSA",
50 | "alg": "RS256",
51 | "use": "sig",
52 | "kid": "02bf92b561733f6f580a7038156309cbfc6b486f",
53 | "n": "zWQXn72L60w20bo84sujDsAIUJs8gPhrN4b_MRDfB5skhobiKbdShTz1iqW8kpAdFGk2L3hEBQFs4pHzzc3G_cZK_ZIWmGd4IZ0AL5JzyUXmjAwtowGUEmHlWltUZ2KBI-o9PjduOxMovNf7HQ2qhARp_ib12hpDcDTrIcrO9R5p-n-4zKDBnRJLTliqhxaUt232v8gyS4oVYOTVAlmoQXGHnS503XWCxx_bcNk177Y0onmejUAAK8WgN8u9e_eAoZIcApw3h4NTLXwiHtm4mJuHAEhsX__goZ6wnjCW7DW48eWoK6cbvc4X6DlWGRa4JuIaAaic80Z7lplJ7M2gRw",
54 | "e": "AQAB"
55 | },
56 | {
57 | "kty": "RSA",
58 | "alg": "RS256",
59 | "use": "sig",
60 | "kid": "a12c3a610919330830079925df9c95ad85274580",
61 | "n": "1-u8ezbZm83wJi_R7X2EqYhKyNGHXuuu0qYcDCX4ZU6FFSCiwnQzQ4K8M5JGBv67hJWjun7kZ18mhtPmfPsf7TmyiZcokxut_jW0bfeLD8NZBdy4EIYspx29qaCQ3XQaiY4FdYefbuQUx-svILOqoh0ke2imyUGBBnwby5BifjTqLia1KjAkNbBmdfpFMO5NTaFNkmC59gkQom82KxoTouL__X05TzVS177KzO5B7-NqgviQ6ZYUb2zGr2pGcxjw9I5LsgQO8aPoWsZOeJRXb9OBkNXpDGWkoyeMYHtmvYXf8l2Hmxqk6NwBoumobSmnhiUemoyHtBiAstj_7s3SCw",
62 | "e": "AQAB"
63 | },
64 | {
65 | "kty": "RSA",
66 | "alg": "RS256",
67 | "use": "sig",
68 | "kid": "77d4ed62ee21d157e8a237b7db3cbd8f7aafab2e",
69 | "n": "snfgF8zfxaHCR1NggGKTLnn04KN_czOjZIaUCmM_rWkcgUKIp2py5iGUTCK-Pp_m7BGgcTD4j5vDSPVX1235ey4AysXN__2n00DLdhJZLqtAty0oIbdDlKQo9tCrsjC1PGlCq5ac7OFYYaXDNYnm1-tind925CzBKdzC0kwsf6bCmGrcwYMae4Rhd8iRBdpIfDR9CBa8eEed_07mCj_pNlDMqjGYCe-Sp02ub3hyg19RJpsnEj_cFbOlCIC6HX4DYu5JYyYJIuYZthZmPbCa1wF3r-yOP9vjMr4P8jCcmHwFJnHBqOVYwSt1NRa-goR4JzflTW58GN1LynN8DhsPEQ",
70 | "e": "AQAB"
71 | }
72 | ]
73 | }
74 | ```
75 |
76 | The API Proxy uses a ServiceCallout to perform a GET on that URL, and then caches the result for one hour. Then, verification can be configured like this:
77 |
78 | ```xml
79 |
80 | RS256
81 | inbound.jwt
82 |
83 |
84 |
85 | https://accounts.google.com
86 |
87 | ```
88 |
89 | Simple!
90 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/README.md:
--------------------------------------------------------------------------------
1 | # JWT Verification - tokens generated by tools
2 |
3 | This API proxy validates a JWT that was created and signed by [the associated tools](../../tools/create-token.js).
4 |
5 | This proxy will work on the Apigee Edge public cloud release.
6 |
7 | ## Deploying
8 |
9 | Several notes:
10 |
11 | * use a tool like [apigeetool](https://github.com/apigee/apigeetool-node) or [pushapi](https://github.com/carloseberhardt/apiploy) or
12 | [importAndDeploy.js](https://github.com/DinoChiesa/apigee-edge-js/blob/master/examples/importAndDeploy.js)
13 | to deploy the proxy.
14 |
15 | * the tool used to create a token requires nodejs. You should [install node](https://nodejs.org/en/download/) before trying to run the examples. On OSX you may want to `brew install node`.
16 |
17 | ## Invoking
18 |
19 | ### To create a token using the RS256 algorithm
20 |
21 | ```
22 | node /tools/create-token.js -A RS256 -s Dino -i Myself -a urn://Apigee -C claim1:value1
23 | ```
24 |
25 | You will see the JWT emitted in output. It will look like this:
26 |
27 | ```
28 | Example JWT creator tool, version: 20171106-1714
29 | Node.js v7.7.1
30 |
31 | JWT:
32 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1MTAwOTUyMzUsImlhdCI6MTUxMDA5NTIzNSwic3ViIjoiUmluZ28uU3RhcnIiLCJpc3MiOiJzZWxmaXNzdWVkIiwiYXVkIjoidXJuOi8vQXBpZ2VlIiwiZXhwIjoxNTEwMDk4ODM1LCJjbGFpbTEiOiJ2YWx1ZTEifQ.bAK4PSIiyiJgiafLPf_lTi0u9Sfx0BUdbIiczCJiyScDvok_C78xy4HK1VONBWykGtB-DFFINIL93V2GfcrNllJW7gxegehu3ZH9u-AZ4g9zlf3j0KE6NiUFwvE2dooFF_xEfrXxx_b5ZNNMm1ep2wRXuPSIDcYHriFYlRwSy42QbsIQfyakYGc_Xld4aF4peFRuzKumxSOfpERPoC1YPvCwnSPqrtkyVOgTGjC7KC2PK6ygJWYHi1dwTutjjsKmfSANAp5u4l7b3o3g4tPEcHb4OQ8yhttKoVn4fbWW-jGh9pywXBbgKf4GAJzQgPR1GpZAtqj-bnYtcNWMsyK9JA
33 |
34 | decoded:
35 | {
36 | "header": {
37 | "alg": "RS256",
38 | "typ": "JWT"
39 | },
40 | "payload": {
41 | "nbf": 1510095235,
42 | "iat": 1510095235,
43 | "sub": "Ringo.Starr",
44 | "iss": "selfissued",
45 | "aud": "urn://Apigee",
46 | "exp": 1510098835,
47 | "claim1": "value1"
48 | }
49 | }
50 | ```
51 |
52 | This token is signed with RS256, using [a private key included in this repo](../../../tools/keys/private-pkcs8.pem). _This private key is included for demonstration purposes only_!
53 |
54 | To verify the token, use this curl command:
55 |
56 | ```
57 | jwt='eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuY....pZAtqj-bnYtcNWMsyK9JA'
58 | curl -i -X GET https://myorg-test.apigee.net/jwt-verify/t1 -H "Authorization: Bearer $jwt"
59 | ```
60 |
61 | The result will confirm that the JWT is valid.
62 |
63 | ```
64 | HTTP/1.1 200 OK
65 | Date: Tue, 07 Nov 2017 22:58:00 GMT
66 | Content-Length: 312
67 | Connection: keep-alive
68 |
69 | {
70 | "status" : "ok",
71 | "algorithm" : "RS256",
72 | "claim_names" : "[sub, aud, nbf, iss, claim1, exp, iat]",
73 | "subject" : "Ringo.Starr",
74 | "issuer" : "selfissued",
75 | "audience" : "[urn://Apigee]",
76 | "expiry" : "2017-11-07T23:53:55.000+0000",
77 | "out.now" : "2017-11-07T22:58:00+0000",
78 | "seconds_remaining" : 3354
79 | }
80 |
81 | ```
82 |
83 | This validation uses [the public key](../../../tools/keys/public.pem) that corresponds to the private key referenced above, to verify the signature.
84 |
85 | If you would like to use your own key pair, then you can generate one. [Follow the
86 | instructions here](../../../tools/keys/). If you generate a new keypair, you will need
87 | to replace the public key used in the VerifyJWT policies, in order for verification to
88 | succeed.
89 |
90 |
91 |
92 | ### To create a token using the HS256 algorithm
93 |
94 | ```
95 | node /tools/create-token.js -A HS256 -s Dino -i Myself -a urn://Apigee -k Secret1234 -C claim1:value1
96 | ```
97 |
98 |
99 | This token is signed with HS256, using the secret passed on the command line.
100 |
101 | To verify the token, use this curl command:
102 |
103 | ```
104 | jwt='eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuY....pZAtqj-bnYtcNWMsyK9JA'
105 | curl -i -X GET https://myorg-test.apigee.net/jwt-verify/t2 -H "Authorization: Bearer $jwt"
106 | ```
107 |
108 | You once again should see success.
109 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/README.md:
--------------------------------------------------------------------------------
1 | # JWT Generation
2 |
3 | This API proxy generates and signs a JWT. The token can then be verified by any other tool with the correct key, including [the example tool](../../tools/verify-token.js) included here.
4 |
5 | This proxy will work on the Apigee Edge public cloud release.
6 |
7 | ## Deploying
8 |
9 | Several notes:
10 |
11 | * use a tool like [apigeetool](https://github.com/apigee/apigeetool-node) or [pushapi](https://github.com/carloseberhardt/apiploy) or
12 | [importAndDeploy.js](https://github.com/DinoChiesa/apigee-edge-js/blob/master/examples/importAndDeploy.js)
13 | to deploy the proxy.
14 |
15 | * the tool used to create a token requires nodejs. You should [install node](https://nodejs.org/en/download/) before trying to run the examples. On OSX you may want to `brew install node`.
16 |
17 | ## Invoking
18 |
19 | ### To create a token using the RS256 algorithm
20 |
21 | ```
22 | curl -i -X POST https://ORG-ENV.apigee.net/jwt-generate/t1 -d 'subject=Subject&audience=A12345'
23 | ```
24 |
25 | You will see the JWT emitted in output. It will look like this:
26 |
27 | ```
28 | HTTP/1.1 200 OK
29 | Date: Tue, 07 Nov 2017 23:07:52 GMT
30 | Content-Length: 631
31 | Connection: keep-alive
32 |
33 | {
34 | "status" : "ok",
35 | "jwt" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJTdWJqZWN0IiwiYXVkIjoiQTEyMzQ1IiwiaXNzIjoidXJuOi8vYXBpZ2VlLWVkZ2UtSldULXBvbGljeS1kZW1vbnN0cmF0aW9uIiwiZXhwIjoxNTEwMDk5NjcyLCJpYXQiOjE1MTAwOTYwNzIsImp0aSI6Ijg5YjIyMDk4LWNmYzItNDVlZS04ZjBiLWI0ZjgzYzc3YzVmOSJ9.QHsz4rveyqc7xhenTkzSU1I0TS7tN6d2NM0d6VP51o0d57fJZ3ujT_wXy43bTjeqXWa4IkI-Gg-1N0eMzfR_nCT3VoKALKJvMvhVAwELt4Ve41verOsJAejlP4UeCBjCNGPddQ2ob0OY4F_59avYy-n81XV0BYyfPuTXkWU9yLprQEat6qp8K8lFHEZ9RH31XlFTXO4AQMYsQOFn4TzPmsfNa3a4kR7pZykeQZO-FsKUJqEzbHcpbvkx6IWPr4hCAXUsTL6_c3oJjYonH-ITIrEswIGlkgTtmOrU72b2K5XFWZDOXUxGa9ctqc0dqeGyrrbrUfbdDrypq_2oXgJZEA"
36 | }
37 | ```
38 |
39 | This token is signed with RS256, using [the private key included in this repo](../../../tools/keys/private-pkcs8.pem). _This private key is included for demonstration purposes only_!
40 |
41 | To verify the token, use this tool:
42 |
43 | ```
44 | jwt='eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzd....GyrrbrUfbdDrypq_2oXgJZEA'
45 | node /tools/verify-token.js -t $jwt -A RS256
46 | ```
47 |
48 | You will see happy output like this:
49 |
50 | ```
51 | Example JWT verifier tool, version: 20171106-1714
52 | Node.js v7.7.1
53 |
54 | decoded:
55 | {
56 | "sub": "Subject",
57 | "aud": "A12345",
58 | "iss": "urn://apigee-edge-JWT-policy-demonstration",
59 | "exp": 1510099672,
60 | "iat": 1510096072,
61 | "jti": "89b22098-cfc2-45ee-8f0b-b4f83c77c5f9"
62 | }
63 | ```
64 |
65 | The result will confirm that the JWT is valid. This validation uses [the public
66 | key](../../../tools/keys/public.pem) that corresponds to the private key referenced
67 | above, to verify the signature.
68 |
69 | If you would like to use your own key pair, then you can generate one. [Follow the
70 | instructions here](../../../tools/keys/). If you generate a new keypair, you will need
71 | to replace the private key used in the GenerateJWT policies, in order for verification to
72 | succeed.
73 |
74 |
75 | ### To create a token using the HS256 algorithm
76 |
77 | ```
78 | curl -i -X POST https://ORG-ENV.apigee.net/jwt-generate/t2 -d 'subject=Subject&audience=A12345'
79 | ```
80 |
81 | The result:
82 |
83 | ```
84 | HTTP/1.1 200 OK
85 | Date: Tue, 07 Nov 2017 23:15:09 GMT
86 | Content-Length: 332
87 | Connection: keep-alive
88 |
89 | {
90 | "status" : "ok",
91 | "jwt" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJTdWJqZWN0IiwiYXVkIjoiQTEyMzQ1IiwiaXNzIjoidXJuOi8vYXBpZ2VlLWVkZ2UtSldULXBvbGljeS1kZW1vbnN0cmF0aW9uIiwiZXhwIjoxNTEwMTAwMTA5LCJpYXQiOjE1MTAwOTY1MDksImp0aSI6ImFhMWU2ZjM1LTQzOWMtNGFlYi05MTdmLTcxNDUzZTQxNmVkMSJ9.u2E4lx1GZP5qA3MNMrWt0Ljro94UMGz_OF7eDtzmnQs"
92 | }
93 |
94 | ```
95 |
96 | This token is signed with HS256, using the secret passed on the command line.
97 |
98 | To verify the token, use the builtin tool;
99 |
100 | ```
101 | jwt='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdW....Ljro94UMGz_OF7eDtzmnQs'
102 | node /tools/verify-token.js -t $jwt -A HS256 -k Secret1234
103 | ```
104 |
105 | You once again should see success.
106 |
107 |
--------------------------------------------------------------------------------
/tools/loadPemIntoKvm.js:
--------------------------------------------------------------------------------
1 | #! /usr/local/bin/node
2 | /*jslint node:true */
3 | // loadPemIntoKvm.js
4 | // ------------------------------------------------------------------
5 | // load a PEM into Apigee Edge KVM
6 | //
7 | // Copyright 2017-2018 Google LLC.
8 | //
9 | // Licensed under the Apache License, Version 2.0 (the "License");
10 | // you may not use this file except in compliance with the License.
11 | // You may obtain a copy of the License at
12 | //
13 | // https://www.apache.org/licenses/LICENSE-2.0
14 | //
15 | // Unless required by applicable law or agreed to in writing, software
16 | // distributed under the License is distributed on an "AS IS" BASIS,
17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | // See the License for the specific language governing permissions and
19 | // limitations under the License.
20 | //
21 | // last saved: <2018-June-06 16:59:02>
22 |
23 | var fs = require('fs'),
24 | edgejs = require('apigee-edge-js'),
25 | common = edgejs.utility,
26 | apigeeEdge = edgejs.edge,
27 | sprintf = require('sprintf-js').sprintf,
28 | async = require('async'),
29 | Getopt = require('node-getopt'),
30 | version = '20180606-1659',
31 | defaults = { mapname : 'PrivateKeys' },
32 | getopt = new Getopt(common.commonOptions.concat([
33 | ['e' , 'env=ARG', 'required. the Edge environment for which to store the KVM data'],
34 | ['m' , 'mapname=ARG', 'optional. name of the KVM in Edge for keys. Will be created if nec. Default: ' + defaults.mapname],
35 | ['E' , 'encrypted', 'optional. if creating a KVM, set it as encrypted. Default: not.'],
36 | ['F' , 'pemfile=ARG', 'required. name of the file containing the pem-encoded key.'],
37 | ['N' , 'entryname=ARG', 'required. name of the entry in KVM to store the PEM.'],
38 | ['T' , 'notoken', 'optional. do not try to get a authentication token.']
39 | ])).bindHelp();
40 |
41 | // ========================================================
42 |
43 | console.log(
44 | 'Apigee Edge PEM KVM-loader tool, version: ' + version + '\n' +
45 | 'Node.js ' + process.version + '\n');
46 |
47 | common.logWrite('start');
48 |
49 | // process.argv array starts with 'node' and 'scriptname.js'
50 | var opt = getopt.parse(process.argv.slice(2));
51 |
52 | if ( !opt.options.env ) {
53 | console.log('You must specify an environment');
54 | getopt.showHelp();
55 | process.exit(1);
56 | }
57 |
58 | if ( !opt.options.mapname ) {
59 | common.logWrite(sprintf('defaulting to %s for KVM mapname', defaults.mapname));
60 | opt.options.mapname = defaults.mapname;
61 | }
62 |
63 | common.verifyCommonRequiredParameters(opt.options, getopt);
64 |
65 | function loadKeyIntoMap(org, cb) {
66 | var re = new RegExp('(?:\r\n|\r|\n)', 'g');
67 | var pemcontent = fs.readFileSync(opt.options.pemfile, "utf8").replace(re,'\n');
68 | var options = {
69 | env: opt.options.env,
70 | kvm: opt.options.mapname,
71 | key: opt.options.entryname,
72 | value: pemcontent
73 | };
74 | common.logWrite('storing new key');
75 | org.kvms.put(options, cb);
76 | }
77 |
78 | function keysLoadedCb(e, result){
79 | if (e) {
80 | common.logWrite(JSON.stringify(e, null, 2));
81 | //console.log(e.stack);
82 | process.exit(1);
83 | }
84 | common.logWrite('ok. the key was loaded successfully.');
85 | }
86 |
87 | var options = {
88 | mgmtServer: opt.options.mgmtserver,
89 | org : opt.options.org,
90 | user: opt.options.username,
91 | password: opt.options.password,
92 | no_token: opt.options.notoken,
93 | verbosity: opt.options.verbose || 0
94 | };
95 |
96 | apigeeEdge.connect(options, function(e, org) {
97 | if (e) {
98 | common.logWrite(JSON.stringify(e, null, 2));
99 | //console.log(e.stack);
100 | process.exit(1);
101 | }
102 | common.logWrite('connected');
103 |
104 | org.kvms.get({ env: opt.options.env }, function(e, result) {
105 | if (e) {
106 | common.logWrite(JSON.stringify(e, null, 2));
107 | //console.log(e.stack);
108 | process.exit(1);
109 | }
110 |
111 | if (result.indexOf(opt.options.mapname) == -1) {
112 | // the map does not yet exist
113 | common.logWrite('Need to create the map');
114 | org.kvms.create({ env: opt.options.env, name: opt.options.mapname, encrypted:opt.options.encrypted},
115 | function(e, result) {
116 | if (e) {
117 | common.logWrite(JSON.stringify(e, null, 2));
118 | //console.log(e.stack);
119 | process.exit(1);
120 | }
121 | loadKeyIntoMap(org, keysLoadedCb);
122 | });
123 | }
124 | else {
125 | common.logWrite('ok. the required map exists');
126 | loadKeyIntoMap(org, keysLoadedCb);
127 | }
128 | });
129 | });
130 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-generate/apiproxy/proxies/default.xml:
--------------------------------------------------------------------------------
1 |
2 | Proxy endpoint for demonstrating JWT
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | AM-ClearResponseHeaders
13 |
14 |
15 |
16 |
17 |
18 |
22 | Generate a JWT with RS256, and embed a subject and an audience within it
23 |
24 |
25 | request.formparam.subject = null OR request.formparam.audience = null
26 | RF-MissingParam
27 |
28 |
29 | AM-PrivateKey
30 |
31 |
32 | Generate-JWT-RS256-Basic
33 |
34 |
35 |
36 |
37 | AM-Response-1
38 |
39 |
40 | (proxy.pathsuffix MatchesPath "/rs256/1") and (request.verb = "POST")
41 |
42 |
43 |
44 |
48 | Generate a JWT with RS256, and embed a subject and an audience within it
49 |
50 |
51 | request.formparam.subject = null OR request.formparam.audience = null
52 | RF-MissingParam
53 |
54 |
55 | AM-PrivateKey
56 |
57 |
58 | Generate-JWT-RS256-AdditionalClaims
59 |
60 |
61 |
62 |
63 | AM-Response-1
64 |
65 |
66 | (proxy.pathsuffix MatchesPath "/rs256/2") and (request.verb = "POST")
67 |
68 |
69 |
70 |
74 | Generate a JWT with RS256, with sub and aud. Retrieve the privatekey from the KVM.
75 |
76 |
77 | request.formparam.subject = null OR request.formparam.audience = null
78 | RF-MissingParam
79 |
80 |
81 | KVM-GetPrivateKey
82 |
83 |
84 | AM-Diagnostics
85 |
86 |
87 | private.privatekey = null
88 | RF-NoPrivateKey
89 |
90 |
91 | Generate-JWT-RS256-AdditionalClaims
92 |
93 |
94 |
95 |
96 | AM-Response-1
97 |
98 |
99 | (proxy.pathsuffix MatchesPath "/rs256/3") and (request.verb = "POST")
100 |
101 |
102 |
103 |
107 | Generate a JWT with HS256, and embed a subject and an audience within it
108 |
109 |
110 | request.formparam.subject = null OR request.formparam.audience = null
111 | RF-MissingParam
112 |
113 |
114 | AM-SecretKey
115 |
116 |
117 | Generate-JWT-HS256-Basic
118 |
119 |
120 |
121 |
122 | AM-Response-1
123 |
124 |
125 | (proxy.pathsuffix MatchesPath "/hs256/1") and (request.verb = "POST")
126 |
127 |
128 |
129 |
130 | RF-UnknownRequest
131 |
132 |
133 |
134 |
135 |
136 |
137 | /jwt-generate
138 |
139 | secure
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/tools/importAndDeploy.js:
--------------------------------------------------------------------------------
1 | #! /usr/local/bin/node
2 | /*jslint node:true */
3 | // importAndDeploy.js
4 | // ------------------------------------------------------------------
5 | // import and deploy an Apigee Edge proxy bundle or shared flow.
6 | //
7 | // Copyright 2017 Google Inc.
8 | //
9 | // Licensed under the Apache License, Version 2.0 (the "License");
10 | // you may not use this file except in compliance with the License.
11 | // You may obtain a copy of the License at
12 | //
13 | // https://www.apache.org/licenses/LICENSE-2.0
14 | //
15 | // Unless required by applicable law or agreed to in writing, software
16 | // distributed under the License is distributed on an "AS IS" BASIS,
17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | // See the License for the specific language governing permissions and
19 | // limitations under the License.
20 | //
21 | // last saved: <2017-December-13 16:55:52>
22 |
23 | var edgejs = require('apigee-edge-js'),
24 | common = edgejs.utility,
25 | apigeeEdge = edgejs.edge,
26 | sprintf = require('sprintf-js').sprintf,
27 | Getopt = require('node-getopt'),
28 | version = '20171206-1242',
29 | defaults = { basepath : '/' },
30 | getopt = new Getopt(common.commonOptions.concat([
31 | ['d' , 'source=ARG', 'source directory for the proxy files. Should be parent of dir "apiproxy" or "sharedflowbundle"'],
32 | ['N' , 'name=ARG', 'override the name for the API proxy or shared flow. By default it\'s extracted from the XML file.'],
33 | ['e' , 'env=ARG', 'the Edge environment(s) to which to deploy the asset. Separate multiple environments with a comma.'],
34 | ['b' , 'basepath=ARG', 'basepath for deploying the API Proxy. Default: ' + defaults.basepath + ' Does not apply to sf.'],
35 | ['S' , 'sharedflow', 'import and deploy as a sharedflow. Default: import + deploy a proxy.'],
36 | ['T' , 'notoken', 'optional. do not try to get a authentication token.']
37 | ])).bindHelp();
38 |
39 | // ========================================================
40 |
41 | function promisifyDeployment(collection, options) {
42 | return function deploy(env) {
43 | return new Promise(function(resolve, reject) {
44 | options.environment = env;
45 | collection.deploy(options, function(e, result) {
46 | if (e) {
47 | common.logWrite(JSON.stringify(e, null, 2));
48 | if (result) { common.logWrite(JSON.stringify(result, null, 2)); }
49 | reject(e);
50 | }
51 | common.logWrite('deploy ok.');
52 | resolve();
53 | });
54 | });
55 | };
56 | }
57 |
58 | console.log(
59 | 'Apigee Edge Proxy/Sharedflow Import + Deploy tool, version: ' + version + '\n' +
60 | 'Node.js ' + process.version + '\n');
61 |
62 | common.logWrite('start');
63 |
64 | // process.argv array starts with 'node' and 'scriptname.js'
65 | var opt = getopt.parse(process.argv.slice(2));
66 |
67 | if ( !opt.options.source ) {
68 | console.log('You must specify a source directory');
69 | getopt.showHelp();
70 | process.exit(1);
71 | }
72 |
73 | if (opt.options.basepath && opt.options.sharedflow) {
74 | console.log('It does not make sense to use a basepath when deploying a sharedflow.');
75 | getopt.showHelp();
76 | process.exit(1);
77 | }
78 |
79 | common.verifyCommonRequiredParameters(opt.options, getopt);
80 |
81 | var options = {
82 | mgmtServer: opt.options.mgmtserver,
83 | org : opt.options.org,
84 | user: opt.options.username,
85 | password: opt.options.password,
86 | no_token: opt.options.notoken,
87 | verbosity: opt.options.verbose || 0
88 | };
89 |
90 | apigeeEdge.connect(options, function(e, org){
91 | if (e) {
92 | common.logWrite(JSON.stringify(e, null, 2));
93 | process.exit(1);
94 | }
95 | common.logWrite('connected');
96 |
97 | var collection = (opt.options.sharedflow) ? org.sharedflows : org.proxies;
98 | var term = (opt.options.sharedflow) ? 'sharedflow' : 'proxy';
99 |
100 | common.logWrite('importing');
101 | collection.import({name:opt.options.name, source:opt.options.source}, function(e, result) {
102 | if (e) {
103 | common.logWrite('error: ' + JSON.stringify(e, null, 2));
104 | if (result) { common.logWrite(JSON.stringify(result, null, 2)); }
105 | //console.log(e.stack);
106 | process.exit(1);
107 | }
108 | common.logWrite(sprintf('import ok. %s name: %s r%d', term, result.name, result.revision));
109 | if (opt.options.env) {
110 | // env may be a comma-separated list
111 | var options = {
112 | name: result.name,
113 | revision: result.revision,
114 | };
115 | if ( ! opt.options.sharedflow) {
116 | options.basepath = opt.options.basepath || defaults.basepath;
117 | }
118 |
119 | // this magic deploys to each environment in series
120 | var deployIt = promisifyDeployment(collection, options);
121 | var reducer = function (promise, env) {
122 | return promise.then(() => {
123 | return deployIt(env).then(result => results.push(result));
124 | })
125 | .catch(console.error);
126 | };
127 | let results = [];
128 | var p = opt.options.env.split(',')
129 | .reduce(reducer, Promise.resolve())
130 | .then(() => { common.logWrite('all done...'); })
131 | .catch(console.error);
132 |
133 | // Promise.all(opt.options.env.split(',').forEach(deployIt))
134 | // .then(() => { common.logWrite('all don...'); })
135 | // .catch((data) => {
136 | // console.log(data);
137 | // });
138 | }
139 | else {
140 | common.logWrite('not deploying...');
141 | common.logWrite('finish');
142 | }
143 | });
144 | });
145 |
--------------------------------------------------------------------------------
/proxy-bundles/jwt-verify/apiproxy/proxies/default.xml:
--------------------------------------------------------------------------------
1 |
2 | Proxy endpoint for demonstrating JWT
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | AM-ClearResponseHeaders
13 |
14 |
15 |
16 |
17 |
18 |
22 | Verify a JWT with RS256, and implicitly verify the audience within it
23 |
24 |
25 | request.header.authorization = null
26 | RF-MissingHeader
27 |
28 |
29 | EV-JWT
30 |
31 |
32 | Verify-JWT-RS256-Basic
33 |
34 |
35 |
36 |
37 | JS-SetNow
38 |
39 |
40 | AM-Response-RS256-Basic
41 |
42 |
43 | (proxy.pathsuffix MatchesPath "/rs256/1") and (request.verb = "GET")
44 |
45 |
46 |
47 |
51 | Verify a JWT with HS256, and implicitly verify the audience within it
52 |
53 |
54 | request.header.authorization = null
55 | RF-MissingHeader
56 |
57 |
58 | EV-JWT
59 |
60 |
61 | AM-SecretKey
62 |
63 |
64 | Verify-JWT-HS256-Basic
65 |
66 |
67 |
68 |
69 | JS-SetNow
70 |
71 |
72 | AM-Response-HS256-Basic
73 |
74 |
75 | (proxy.pathsuffix MatchesPath "/hs256/1") and (request.verb = "GET")
76 |
77 |
78 |
79 |
83 | Verify a JWT with RS256, and implicitly verify the audience AND issuer within it
84 |
85 |
86 | request.header.authorization = null
87 | RF-MissingHeader
88 |
89 |
90 | EV-JWT
91 |
92 |
93 | Verify-JWT-RS256-Audience-and-Issuer
94 |
95 |
96 |
97 |
98 | JS-SetNow
99 |
100 |
101 | AM-Response-3
102 |
103 |
104 | (proxy.pathsuffix MatchesPath "/rs256/2") and (request.verb = "GET")
105 |
106 |
107 |
108 |
112 | Verify a JWT with RS256, implicitly verify the audience. Then explicitly verify the issuer.
113 |
114 |
115 | request.header.authorization = null
116 | RF-MissingHeader
117 |
118 |
119 | EV-JWT
120 |
121 |
122 | Verify-JWT-RS256-Basic
123 |
124 |
125 | jwt.Verify-JWT-1.issuer != "my-required-issuer"
126 | RF-InvalidToken
127 |
128 |
129 |
130 |
131 | JS-SetNow
132 |
133 |
134 | AM-Response-RS256-Basic
135 |
136 |
137 | (proxy.pathsuffix MatchesPath "/rs256/3") and (request.verb = "GET")
138 |
139 |
140 |
141 |
145 | Verify a JWT with RS256, like flow 2, but retrieving public key from KVM.
146 |
147 |
148 | request.header.authorization = null
149 | RF-MissingHeader
150 |
151 |
152 | EV-JWT
153 |
154 |
155 | KVM-GetPublicKey
156 |
157 |
158 | Verify-JWT-RS256-Audience-and-Issuer2
159 |
160 |
161 |
162 |
163 | JS-SetNow
164 |
165 |
166 | AM-Response-4
167 |
168 |
169 | (proxy.pathsuffix MatchesPath "/rs256/4") and (request.verb = "GET")
170 |
171 |
172 |
173 |
177 | Verify a JWT with HS256, with a specific key
178 |
179 |
180 | request.header.authorization = null
181 | RF-MissingHeader
182 |
183 |
184 | request.queryparam.key = null
185 | RF-MissingKey
186 |
187 |
188 | EV-JWT
189 |
190 |
191 | AM-SecretKeyFromQueryParam
192 |
193 |
194 | AM-ShowSecretKey
195 |
196 |
197 | Verify-JWT-HS256-BasicNoAudience
198 |
199 |
200 |
201 |
202 | JS-SetNow
203 |
204 |
205 | AM-Response-HS256-Basic
206 |
207 |
208 | (proxy.pathsuffix MatchesPath "/hs256/3") and (request.verb = "GET")
209 |
210 |
211 |
212 |
213 |
214 |
215 | RF-UnknownRequest
216 |
217 |
218 |
219 |
220 |
221 |
222 | /jwt-verify
223 |
224 | secure
225 |
226 |
227 |
228 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Code supporting the JWT Demonstrations
2 |
3 | This repository contains code and configuration supporting the demonstrations of the JWT
4 | policies in Apigee Edge. JSON Web Token, aka JWT, is defined in [IETF RFC
5 | 7519](https://tools.ietf.org/html/rfc7519). JWT is a compact, URL-safe format for
6 | representing *claims* to be transferred between cooperating parties. Apigee Edge
7 | includes policies that generate and verify JWT issued by arbitrary parties.
8 |
9 | ## Disclaimer
10 |
11 | The code in this repo is not an official Google product, nor is it part of an
12 | official Google product. It's a demonstration of the JWT policies in the Apigee
13 | Edge product.
14 |
15 | ## There's a Screencast
16 |
17 | Check the [screencast](https://youtu.be/Ijm7iyDOVFY) accompanying this example code.
18 |
19 | [](https://youtu.be/Ijm7iyDOVFY)
20 |
21 | ## Usefulness of JWT
22 |
23 | JWT are used to encapsulate claims about a person or system in a standard way, so that
24 | the claims can be transmitted to a cooperating system that can verify and rely on those
25 | claims.
26 |
27 | A common example would be an identity provider - that is, a system that can authenticate
28 | users. When a user provides the correct authentication, the identity provider may issue
29 | a signed JWT, which includes statements about the user. If the signature uses an RSA
30 | private key, this JWT can then be verified by any other system that has access to the
31 | public key of the IdP. If the signature uses an HMAC signature, this JWT can then be
32 | verified by any other system that has access to the shared secret key.
33 |
34 | Let's call the system that examines and verifies a JWT, a "relying party". If the
35 | relying party trusts the identity provider - in other words trusts that the public key
36 | belongs to the IdP, or that the secret key that the two parties share has not been
37 | compromised - then the RP can rely on the claims about the user, contained within the
38 | JWT. The RP can then make decisions based on the value of those claims - decisions
39 | regarding authorization, request routing, and so on.
40 |
41 | ## JWT One Level Deeper
42 |
43 | "A claim" is nothing more than an asserted statement. For example, "the sky is clear" is
44 | a claim. Usually a JWT wraps up a set of one or more claims _about a particular
45 | subject_ (like a person), and signs that payload with a digital signature. The digsig
46 | is a unique keyed hash of the payload that can be used to verify that the set of claims
47 | has not changed since they were initially issued, and that the claims wwere asserted by
48 | a particular party - the holder of the signing key.
49 |
50 | In JWT, there are some "standard" claim names with well-known meanings. The system or
51 | participant that creates the JWT is called the _issuer_ (iss). JWT _can_ include an
52 | assertion about the intended reader of the JWT, via the _audience_ claim (aud). The
53 | _subject_ (sub) claim denotes the subject of other claims in the JWT. For example
54 | subject might contain a userid (A12345) and the JWT may also another claim containing the email
55 | address and a third claim containing set of roles for that userid.
56 |
57 | The JWT specification also provides a way to designate the valid times for a token - the
58 | not-before time (nbf), and the expiry time (exp). The times are expressed in
59 | seconds-since-epoch. There is also an informational issued-at time (iat), which is
60 | expressed in the same format, but is not required to be used for validating a JWT.
61 |
62 | All the "standard claims" such as iss, sub, aud, exp, nbf, iat as well as custom claims
63 | like "userid" are _optional_. Each system that generates JWT can choose which claims to
64 | include; the JWT can include zero or more standard claims and zero or more custom
65 | claims. A system that verifies JWT can include logic to check for specific values for
66 | specific claims. Systems are free to ignore claims they don't care about.
67 |
68 | ## The Construction of a JWT
69 |
70 | > If you are building a JWT library that creates or manipulates JWT, you might want to
71 | > know how JWT are constructed. If you simply want to use JWT, you don't need to know, but
72 | > this information may be interesting for you anyway.
73 |
74 | The signing base for the JWT consists of the base64-encoded version of the header,
75 | followed by a dot, followed by the base64-encoded version of the payload.
76 | `base64(header).base64(payload)`.
77 |
78 | The JWT "header" is a JSON hash, which typically indicates some basic information such
79 | as the algorithm used for signing. For example:
80 |
81 | ```json
82 | {
83 | "alg": "RS256",
84 | "typ": "JWT"
85 | }
86 | ```
87 |
88 | The header may contain other claims, as well.
89 |
90 | The payload is also represented as a JSON hash. For example:
91 |
92 | ```json
93 | {
94 | "sub": "Subject",
95 | "iss": "Issuer",
96 | "aud": "Audience",
97 | "iat": 1510020300,
98 | "exp": 1510023900,
99 | "nbf": 1510020300,
100 | "customClaim1": "value1"
101 | }
102 | ```
103 |
104 | Each of those - the header and payload - must be serialized in compact form (no
105 | newlines, no extra spaces), then UTF8 and base64-encoded. The dot-concatenation of those
106 | two pieces forms the signing base for the JWT.
107 |
108 | The creator of a JWT computes the digital signature of the signing base, and then
109 | base64-encodes it, then dot-concatenates the signing base with _that_ . The result is
110 | the JWT. `base64(header).base64(payload).base64(signature)`.
111 |
112 | After all of that, this is what an actual JWT looks like:
113 |
114 | ```
115 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1MTAwMjAzMDAsImlhdCI6MTUxMDAyMDMwMCwic3ViIjoiU3ViamVjdCIsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIiwiZXhwIjoxNTEwMDIzOTAwLCJjdXN0b21DbGFpbTEiOiJ2YWx1ZTEifQ.akW3MHTRAnWIPdrD14XYcQKFxDqQ7ztqqS1iLUZfQcQJusi805JhlhBmYZ7axQn2DFBvRsk-i_aCwBDiCzOHGIxufyreMUi7dlkVX6aby8shOIG1jwozes9xGR0pe7ekMD7a39FHKntIXfZEZXE0fxFTIjeG0F7Ui8gL8v8pMIX_SRmK6uEPv0gUStQI-x1nJQM7EtOPs4ZnnlA1hA7HAMEZjkv64yZqbEKXC3d_BFEV3-XhlQR8YG6kJyKoPsgxWMN1JeEUn7fn0YM4V0B8bTepVPUYSViqzz6C5vPvDrk0-PiqGGIry9XrxTXTgNvToL8cOFp2c4ZHyONZqsIk8Q
116 | ```
117 |
118 | In the above, the header is:
119 | ```
120 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
121 | ```
122 |
123 | The payload is:
124 | ```
125 | eyJuYmYiOjE1MTAwMjAzMDAsImlhdCI6MTUxMDAyMDMwMCwic3ViIjoiU3ViamVjdCIsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIiwiZXhwIjoxNTEwMDIzOTAwLCJjdXN0b21DbGFpbTEiOiJ2YWx1ZTEifQ
126 | ```
127 |
128 | If you base64-decode these things, you will get the JSON hashes shown above.
129 |
130 | The signature is:
131 | ```
132 | akW3MHTRAnWIPdrD14XYcQKFxDqQ7ztqqS1iLUZfQcQJusi805JhlhBmYZ7axQn2DFBvRsk-i_aCwBDiCzOHGIxufyreMUi7dlkVX6aby8shOIG1jwozes9xGR0pe7ekMD7a39FHKntIXfZEZXE0fxFTIjeG0F7Ui8gL8v8pMIX_SRmK6uEPv0gUStQI-x1nJQM7EtOPs4ZnnlA1hA7HAMEZjkv64yZqbEKXC3d_BFEV3-XhlQR8YG6kJyKoPsgxWMN1JeEUn7fn0YM4V0B8bTepVPUYSViqzz6C5vPvDrk0-PiqGGIry9XrxTXTgNvToL8cOFp2c4ZHyONZqsIk8Q
133 | ```
134 |
135 | If you base64-decode _that_ you will get a byte stream, the digital signature. (Just a
136 | set of bytes, it does not represent a string.) If you want to verify that digital
137 | signature, you can use the [public key](./tools/keys) included in this repo.
138 |
139 | There are a variety of libraries for various programming environments that can be used
140 | to aid in creating or verifying JWT. [JWT.io](https://jwt.io) hosts a useful online form
141 | that allows you to decode JWT interactively, and also lists a number of different
142 | libraries.
143 |
144 |
145 | ## Verifying a JWT
146 |
147 | Verification consists of verifying the digital signature, then evaluating the claims,
148 | within the payload and header. If the signature is good, then each of the claims can be
149 | trusted. It is up to the verifier to enforce expiry time, and the not-before time, and
150 | to enforce any other required claims, such as issuer or audience.
151 |
152 |
153 | ## JWT within Apigee Edge
154 |
155 | People configuring smart API proxies in [Apigee Edge](https://apigee.com) may want those
156 | proxies to verify or generate JWT. For several years, people have been using [an
157 | open-source Java callout](https://github.com/apigee/iloveapis2015-jwt-jwe-jws) for this
158 | purpose. This works, but the callout was not supported as part of the Edge product by
159 | Apigee.
160 |
161 | In late 2017, Apigee added JWT policies into the Apigee Edge product to
162 | enable these scenarios. Specifically, there are now policies in Apigee Edge that you can
163 | use to generate or verify JWT using either HMAC or RSA signatures, in 256, 384, or
164 | 512-bit strength. (HS256, HS384, HS512, RS256, RS384, RS512).
165 |
166 | It is possible to use these Apigee Edge policies to:
167 | - verify JWT that are generated by Apigee Edge, or by any identity provider (Google Sign-in, Salesforce.com, Ping, Azure AD, etc.)
168 | - generate tokens that can be verified by Apigee Edge, or by third parties
169 |
170 |
171 | ## Contents of this Repo
172 |
173 | * Two [API Proxy bundles](./proxy-bundles)
174 | - [jwt-verify](./proxy-bundles/verify) - an API Proxy bundle that verifies JWT
175 | - [jwt-generate](./proxy-bundles/generate) - an API Proxy bundle that generates JWT
176 | * Various [helper tools](./tools)
177 |
178 |
179 | ## There are Helper Tools
180 |
181 | There are several command-line tools in this repo that will aid in
182 | importing and deploying API Proxies, and in generating or verifying JWT
183 | from the command-line. They're built with nodejs; To use them you will
184 | need node (on MacOS, `brew install node`), and you will need the
185 | pre-requisite libraries. To get the latter, you must:
186 |
187 | ```
188 | cd tools
189 | npm install
190 | cd ..
191 | ```
192 |
193 | ## Importing and Deploying the API Proxies
194 |
195 | To use the API proxies, you will need to import and deploy them into an Apigee Edge organization and environment.
196 | To do that you can use the [importAndDeploy.js](./tools/importAndDeploy.js) tool.
197 |
198 | ```
199 | node ./tools/importAndDeploy -v -o ${ORG} -e ${ENV} -d ./proxy-bundles/jwt-verify
200 | node ./tools/importAndDeploy -v -o ${ORG} -e ${ENV} -d ./proxy-bundles/jwt-verify-goog
201 | node ./tools/importAndDeploy -v -o ${ORG} -e ${ENV} -d ./proxy-bundles/jwt-generate
202 | ```
203 |
204 | Alternatively, you can "manually" zip up those directories and then use the Apigee Edge UI to import the proxy bundle.
205 | The command-line tool is probably easier.
206 |
207 |
208 | ## Usign Apigee Edge to Verify a JWT
209 |
210 |
211 | ### Verify a HS256 JWT
212 | To use Apigee to verify an HS256 JWT, first create a JWT:
213 |
214 | ```
215 | node tools/create-token.js -A HS256 -a urn://Apigee -N -k Secret123
216 |
217 | ```
218 |
219 | This creates an HS256-signed JWT. The secret key used for signing is `Secret123`.
220 |
221 | Then verify it:
222 | ```
223 | curl -i https://${ORG}-${ENV}.apigee.net/jwt-verify/hs256/1 -H "Authorization: Bearer ${JWT}"
224 | ```
225 |
226 | The VerifyJWT policy used for this request is configured to use the same shared secret (hard coded).
227 |
228 |
229 | ### Verify a RS256 JWT
230 |
231 | To use Apigee to verify an RS256 JWT, first create the RS256-signed JWT:
232 |
233 | ```
234 | node ./tools/create-token.js -A RS256 -a urn://Apigee -N
235 | ```
236 |
237 | The JWT will be printed on the console output. The private key used for signing is stored in the keys subdirectory.
238 |
239 | Then verify it:
240 | ```
241 | curl -i https://${ORG}-${ENV}.apigee.net/jwt-verify/rs256/1 -H "Authorization: Bearer ${JWT}"
242 | ```
243 |
244 | The VerifyJWT policy used for *this* request is configured to use the public key corresponding to the private key
245 | used by the `tools/create-token.js` script.
246 |
247 |
248 | You can also try creating JWT with various other settings:
249 |
250 | ```
251 | node ./tools/create-token.js -h
252 | ```
253 |
254 | ...and then send in the generated token to the same endpoint as above.
255 |
256 |
257 | ### Verify a RS256 JWT, retrieving the public key from KVM
258 |
259 | First, create the RS256-signed JWT:
260 |
261 | ```
262 | node ./tools/create-token.js -A RS256 -a urn://Apigee -i urn://Issuer -N
263 | ```
264 |
265 | Before asking Apigee Edge for verification, you must provision the public key into the KVM. Do so like this:
266 |
267 | ```
268 | ORG=my-apigee-org
269 | ENV-test
270 | node ./tools/loadPemIntoKvm.js -v -o $ORG -e $ENV -M non-secrets -N key1 -F ./tools/keys/public.pem
271 | ```
272 |
273 | This loads the key into the KVM (non-encrypted), using the Apigee Edge management API. You
274 | do not NEED to load the key using this tool. But you do need to insure
275 | that the PEM-encoding for the key is unmodified. It should use a single
276 | line feed (\n), rather than a CR-LF combination (\r\n), for each new line.
277 | That node script does this automatically.
278 |
279 | The Apigee Edge proxy is configured to read from a KVM map of "non-secrets" under key "key1".
280 |
281 | Verify:
282 | ```
283 | curl -i https://${ORG}-${ENV}.apigee.net/jwt-verify/rs256/4 -H "Authorization: Bearer ${JWT}"
284 | ```
285 | The above reads from the KVM to get the public key to use for verification.
286 |
287 |
288 |
289 | ## Using Apigee Edge to Verify a JWT generated by Google Signin
290 |
291 | To get an ID Token from Google, you can use [this example signin
292 | page](https://dinochiesa.github.io/openid-connect/goog-login.html). Use
293 | the form to build the link. Click the link to kickoff the google signin
294 | procedure. When it completes you will see a JWT. You can copy/paste that
295 | into a command to verify that token , like this:
296 |
297 | ```
298 | curl -i https://${ORG}-${ENV}.apigee.net/jwt-verify-goog/t1 -H "Authorization: Bearer ${JWT}"
299 | ```
300 |
301 | The endpoint above is configured to use the public keys published by Google, that can be used to verify JWT generated by Google.
302 |
303 |
304 | ## Using Apigee Edge to Generate a JWT
305 |
306 | ```
307 | curl -i https://${ORG}-${ENV}.apigee.net/jwt-generate/rs256/1 -X POST -d 'subject=Subject&audience=A12345'
308 | ```
309 |
310 | The GenerateJWT policy is configured to use a self-generated private key. You should see as output, a JSON payload containing a JWT.
311 |
312 |
313 | To verify the JWT, you can use this:
314 |
315 | ```
316 | JWT=ey...that-large-string-from-the-output-of-the-above-command....9849473ndjfhfhjA
317 | node ./tools/verify-token.js -A RS256 -a A12345 -k tools/keys/public.pem -t ${JWT}
318 | ```
319 |
320 |
321 | ## Using Apigee Edge to Generate a JWT with additional claims
322 |
323 | ```
324 | curl -i https://${ORG}-${ENV}.apigee.net/jwt-generate/rs256/2 -X POST -d 'subject=Subject&audience=A12345'
325 | ```
326 |
327 | Verify the generated JWT in the same way as described above.
328 |
329 |
330 |
331 | ## Using Apigee Edge to Generate a JWT, retrieving the key from encrypted KVM
332 |
333 | First, you must provision the private key into the encrypted KVM. Do so like this:
334 |
335 | ```
336 | ORG=my-apigee-org
337 | ENV-test
338 | node ./tools/loadPemIntoKvm.js -v -o $ORG -e $ENV -E -M secrets -N key1 -F ./tools/keys/private-pkcs8.pem
339 | ```
340 |
341 | This loads the key into the encrypted KVM, using the Apigee Edge management API. You
342 | do not NEED to load the key using this tool. But you do need to insure
343 | that the PEM-encoding for the key is unmodified. It should use a single
344 | line feed (\n), rather than a CR-LF combination (\r\n), for each new line.
345 | That node script does this automatically.
346 |
347 |
348 | The Apigee Edge proxy is configured to read from a KVM map of "secrets" under key "key1".
349 |
350 | Then, generate a JWT using that key:
351 |
352 | ```
353 | curl -i https://${ORG}-${ENV}.apigee.net/jwt-generate/rs256/3 -X POST -d 'subject=Subject&audience=A12345'
354 | ```
355 |
356 | Verify the generated JWT in the same way as described above, using the verify-token script.
357 |
358 |
359 |
360 |
361 |
362 |
363 | ## Note: The JWT Policies in Apigee Edge
364 |
365 | The JWT policies in Apigee Edge were first enabled in January 2018.
366 | If you have JWT enabled, you will see the policies in the policy-chooser palette:
367 | 
368 |
369 | If you don't see these policies, and you want to try them out, contact Apigee support.
370 | If you have questions or comments on any of this, please post to [the Apigee community site](https://community.apigee.com).
371 |
372 | ## License
373 |
374 | This material is Copyright 2017-2018 Google LLC.
375 | and is licensed under the [Apache 2.0 License](LICENSE).
376 |
377 | The demonstration code here is provided without warranty of any kind.
378 |
--------------------------------------------------------------------------------
/tools/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsonwebtoken-examples",
3 | "version": "1.0.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "adm-zip": {
8 | "version": "0.4.11",
9 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz",
10 | "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA=="
11 | },
12 | "ajv": {
13 | "version": "5.5.2",
14 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
15 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
16 | "requires": {
17 | "co": "^4.6.0",
18 | "fast-deep-equal": "^1.0.0",
19 | "fast-json-stable-stringify": "^2.0.0",
20 | "json-schema-traverse": "^0.3.0"
21 | }
22 | },
23 | "apigee-edge-js": {
24 | "version": "0.3.4",
25 | "resolved": "https://registry.npmjs.org/apigee-edge-js/-/apigee-edge-js-0.3.4.tgz",
26 | "integrity": "sha512-lQEFy0RsFh3+ZWsY5Au69C3Bya52+5/0eYLeTSyfh+IdbKfQQoYv9sHWWPmY29kg9epsSBZF0ZLrIMbtWFIzCA==",
27 | "requires": {
28 | "adm-zip": "^0.4.7",
29 | "archiver": "^2.1.1",
30 | "merge": "^1.2.0",
31 | "netrc": "0.1.3",
32 | "qs": "^6.3.0",
33 | "readline-sync": "^1.4.1",
34 | "request": "2.87.0",
35 | "sprintf-js": "^1.0.3",
36 | "url-join": "2.0.x",
37 | "xml2js": "^0.4.17"
38 | }
39 | },
40 | "archiver": {
41 | "version": "2.1.1",
42 | "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz",
43 | "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=",
44 | "requires": {
45 | "archiver-utils": "^1.3.0",
46 | "async": "^2.0.0",
47 | "buffer-crc32": "^0.2.1",
48 | "glob": "^7.0.0",
49 | "lodash": "^4.8.0",
50 | "readable-stream": "^2.0.0",
51 | "tar-stream": "^1.5.0",
52 | "zip-stream": "^1.2.0"
53 | }
54 | },
55 | "archiver-utils": {
56 | "version": "1.3.0",
57 | "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz",
58 | "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=",
59 | "requires": {
60 | "glob": "^7.0.0",
61 | "graceful-fs": "^4.1.0",
62 | "lazystream": "^1.0.0",
63 | "lodash": "^4.8.0",
64 | "normalize-path": "^2.0.0",
65 | "readable-stream": "^2.0.0"
66 | }
67 | },
68 | "asn1": {
69 | "version": "0.2.3",
70 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
71 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
72 | },
73 | "assert-plus": {
74 | "version": "1.0.0",
75 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
76 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
77 | },
78 | "async": {
79 | "version": "2.6.1",
80 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
81 | "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
82 | "requires": {
83 | "lodash": "^4.17.10"
84 | }
85 | },
86 | "asynckit": {
87 | "version": "0.4.0",
88 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
89 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
90 | },
91 | "aws-sign2": {
92 | "version": "0.7.0",
93 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
94 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
95 | },
96 | "aws4": {
97 | "version": "1.7.0",
98 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz",
99 | "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w=="
100 | },
101 | "balanced-match": {
102 | "version": "1.0.0",
103 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
104 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
105 | },
106 | "bcrypt-pbkdf": {
107 | "version": "1.0.1",
108 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
109 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
110 | "optional": true,
111 | "requires": {
112 | "tweetnacl": "^0.14.3"
113 | }
114 | },
115 | "bl": {
116 | "version": "1.2.2",
117 | "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
118 | "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
119 | "requires": {
120 | "readable-stream": "^2.3.5",
121 | "safe-buffer": "^5.1.1"
122 | }
123 | },
124 | "brace-expansion": {
125 | "version": "1.1.11",
126 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
127 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
128 | "requires": {
129 | "balanced-match": "^1.0.0",
130 | "concat-map": "0.0.1"
131 | }
132 | },
133 | "buffer-alloc": {
134 | "version": "1.2.0",
135 | "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
136 | "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
137 | "requires": {
138 | "buffer-alloc-unsafe": "^1.1.0",
139 | "buffer-fill": "^1.0.0"
140 | }
141 | },
142 | "buffer-alloc-unsafe": {
143 | "version": "1.1.0",
144 | "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
145 | "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
146 | },
147 | "buffer-crc32": {
148 | "version": "0.2.13",
149 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
150 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
151 | },
152 | "buffer-equal-constant-time": {
153 | "version": "1.0.1",
154 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
155 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
156 | },
157 | "buffer-fill": {
158 | "version": "1.0.0",
159 | "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
160 | "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
161 | },
162 | "caseless": {
163 | "version": "0.12.0",
164 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
165 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
166 | },
167 | "co": {
168 | "version": "4.6.0",
169 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
170 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
171 | },
172 | "combined-stream": {
173 | "version": "1.0.6",
174 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
175 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
176 | "requires": {
177 | "delayed-stream": "~1.0.0"
178 | }
179 | },
180 | "compress-commons": {
181 | "version": "1.2.2",
182 | "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz",
183 | "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=",
184 | "requires": {
185 | "buffer-crc32": "^0.2.1",
186 | "crc32-stream": "^2.0.0",
187 | "normalize-path": "^2.0.0",
188 | "readable-stream": "^2.0.0"
189 | }
190 | },
191 | "concat-map": {
192 | "version": "0.0.1",
193 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
194 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
195 | },
196 | "core-util-is": {
197 | "version": "1.0.2",
198 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
199 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
200 | },
201 | "crc": {
202 | "version": "3.5.0",
203 | "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz",
204 | "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ="
205 | },
206 | "crc32-stream": {
207 | "version": "2.0.0",
208 | "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz",
209 | "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=",
210 | "requires": {
211 | "crc": "^3.4.4",
212 | "readable-stream": "^2.0.0"
213 | }
214 | },
215 | "dashdash": {
216 | "version": "1.14.1",
217 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
218 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
219 | "requires": {
220 | "assert-plus": "^1.0.0"
221 | }
222 | },
223 | "delayed-stream": {
224 | "version": "1.0.0",
225 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
226 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
227 | },
228 | "ecc-jsbn": {
229 | "version": "0.1.1",
230 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
231 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
232 | "optional": true,
233 | "requires": {
234 | "jsbn": "~0.1.0"
235 | }
236 | },
237 | "ecdsa-sig-formatter": {
238 | "version": "1.0.10",
239 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz",
240 | "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=",
241 | "requires": {
242 | "safe-buffer": "^5.0.1"
243 | }
244 | },
245 | "end-of-stream": {
246 | "version": "1.4.1",
247 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
248 | "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
249 | "requires": {
250 | "once": "^1.4.0"
251 | }
252 | },
253 | "extend": {
254 | "version": "3.0.1",
255 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
256 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
257 | },
258 | "extsprintf": {
259 | "version": "1.3.0",
260 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
261 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
262 | },
263 | "fast-deep-equal": {
264 | "version": "1.1.0",
265 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
266 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
267 | },
268 | "fast-json-stable-stringify": {
269 | "version": "2.0.0",
270 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
271 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
272 | },
273 | "forever-agent": {
274 | "version": "0.6.1",
275 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
276 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
277 | },
278 | "form-data": {
279 | "version": "2.3.2",
280 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
281 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
282 | "requires": {
283 | "asynckit": "^0.4.0",
284 | "combined-stream": "1.0.6",
285 | "mime-types": "^2.1.12"
286 | }
287 | },
288 | "fs-constants": {
289 | "version": "1.0.0",
290 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
291 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
292 | },
293 | "fs.realpath": {
294 | "version": "1.0.0",
295 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
296 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
297 | },
298 | "getpass": {
299 | "version": "0.1.7",
300 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
301 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
302 | "requires": {
303 | "assert-plus": "^1.0.0"
304 | }
305 | },
306 | "glob": {
307 | "version": "7.1.2",
308 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
309 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
310 | "requires": {
311 | "fs.realpath": "^1.0.0",
312 | "inflight": "^1.0.4",
313 | "inherits": "2",
314 | "minimatch": "^3.0.4",
315 | "once": "^1.3.0",
316 | "path-is-absolute": "^1.0.0"
317 | }
318 | },
319 | "graceful-fs": {
320 | "version": "4.1.11",
321 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
322 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
323 | },
324 | "har-schema": {
325 | "version": "2.0.0",
326 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
327 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
328 | },
329 | "har-validator": {
330 | "version": "5.0.3",
331 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
332 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
333 | "requires": {
334 | "ajv": "^5.1.0",
335 | "har-schema": "^2.0.0"
336 | }
337 | },
338 | "http-signature": {
339 | "version": "1.2.0",
340 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
341 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
342 | "requires": {
343 | "assert-plus": "^1.0.0",
344 | "jsprim": "^1.2.2",
345 | "sshpk": "^1.7.0"
346 | }
347 | },
348 | "inflight": {
349 | "version": "1.0.6",
350 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
351 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
352 | "requires": {
353 | "once": "^1.3.0",
354 | "wrappy": "1"
355 | }
356 | },
357 | "inherits": {
358 | "version": "2.0.3",
359 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
360 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
361 | },
362 | "is-typedarray": {
363 | "version": "1.0.0",
364 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
365 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
366 | },
367 | "isarray": {
368 | "version": "1.0.0",
369 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
370 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
371 | },
372 | "isstream": {
373 | "version": "0.1.2",
374 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
375 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
376 | },
377 | "jsbn": {
378 | "version": "0.1.1",
379 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
380 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
381 | "optional": true
382 | },
383 | "json-schema": {
384 | "version": "0.2.3",
385 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
386 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
387 | },
388 | "json-schema-traverse": {
389 | "version": "0.3.1",
390 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
391 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
392 | },
393 | "json-stringify-safe": {
394 | "version": "5.0.1",
395 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
396 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
397 | },
398 | "jsonwebtoken": {
399 | "version": "8.2.2",
400 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.2.tgz",
401 | "integrity": "sha512-rFFq7ow/JpPzwgaz4IyRL9cp7f4ptjW92eZgsQyqkysLBmDjSSBhnKfQESoq0GU+qJXK/CQ0o4shgwbUPiFCdw==",
402 | "requires": {
403 | "jws": "^3.1.5",
404 | "lodash.includes": "^4.3.0",
405 | "lodash.isboolean": "^3.0.3",
406 | "lodash.isinteger": "^4.0.4",
407 | "lodash.isnumber": "^3.0.3",
408 | "lodash.isplainobject": "^4.0.6",
409 | "lodash.isstring": "^4.0.1",
410 | "lodash.once": "^4.0.0",
411 | "ms": "^2.1.1",
412 | "xtend": "^4.0.1"
413 | }
414 | },
415 | "jsprim": {
416 | "version": "1.4.1",
417 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
418 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
419 | "requires": {
420 | "assert-plus": "1.0.0",
421 | "extsprintf": "1.3.0",
422 | "json-schema": "0.2.3",
423 | "verror": "1.10.0"
424 | }
425 | },
426 | "jwa": {
427 | "version": "1.1.6",
428 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz",
429 | "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==",
430 | "requires": {
431 | "buffer-equal-constant-time": "1.0.1",
432 | "ecdsa-sig-formatter": "1.0.10",
433 | "safe-buffer": "^5.0.1"
434 | }
435 | },
436 | "jws": {
437 | "version": "3.1.5",
438 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz",
439 | "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==",
440 | "requires": {
441 | "jwa": "^1.1.5",
442 | "safe-buffer": "^5.0.1"
443 | }
444 | },
445 | "lazystream": {
446 | "version": "1.0.0",
447 | "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
448 | "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
449 | "requires": {
450 | "readable-stream": "^2.0.5"
451 | }
452 | },
453 | "lodash": {
454 | "version": "4.17.10",
455 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
456 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
457 | },
458 | "lodash.includes": {
459 | "version": "4.3.0",
460 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
461 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
462 | },
463 | "lodash.isboolean": {
464 | "version": "3.0.3",
465 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
466 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
467 | },
468 | "lodash.isinteger": {
469 | "version": "4.0.4",
470 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
471 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
472 | },
473 | "lodash.isnumber": {
474 | "version": "3.0.3",
475 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
476 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
477 | },
478 | "lodash.isplainobject": {
479 | "version": "4.0.6",
480 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
481 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
482 | },
483 | "lodash.isstring": {
484 | "version": "4.0.1",
485 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
486 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
487 | },
488 | "lodash.once": {
489 | "version": "4.1.1",
490 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
491 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
492 | },
493 | "merge": {
494 | "version": "1.2.0",
495 | "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz",
496 | "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo="
497 | },
498 | "mime-db": {
499 | "version": "1.33.0",
500 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
501 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
502 | },
503 | "mime-types": {
504 | "version": "2.1.18",
505 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
506 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
507 | "requires": {
508 | "mime-db": "~1.33.0"
509 | }
510 | },
511 | "minimatch": {
512 | "version": "3.0.4",
513 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
514 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
515 | "requires": {
516 | "brace-expansion": "^1.1.7"
517 | }
518 | },
519 | "ms": {
520 | "version": "2.1.1",
521 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
522 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
523 | },
524 | "netrc": {
525 | "version": "0.1.3",
526 | "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.3.tgz",
527 | "integrity": "sha1-sOciGLEBtPLqvdXm2zb/qbI+RrM="
528 | },
529 | "node-getopt": {
530 | "version": "0.2.4",
531 | "resolved": "https://registry.npmjs.org/node-getopt/-/node-getopt-0.2.4.tgz",
532 | "integrity": "sha512-06LC4wHO+nyH0J07dUzFsZTVZMsMMKTkXo8BUTmuYbJhbsKX2cVDn2xADoFqjbnBYThVlGSlaM10CDyEi+48Iw=="
533 | },
534 | "normalize-path": {
535 | "version": "2.1.1",
536 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
537 | "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
538 | "requires": {
539 | "remove-trailing-separator": "^1.0.1"
540 | }
541 | },
542 | "oauth-sign": {
543 | "version": "0.8.2",
544 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
545 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
546 | },
547 | "once": {
548 | "version": "1.4.0",
549 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
550 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
551 | "requires": {
552 | "wrappy": "1"
553 | }
554 | },
555 | "path-is-absolute": {
556 | "version": "1.0.1",
557 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
558 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
559 | },
560 | "performance-now": {
561 | "version": "2.1.0",
562 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
563 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
564 | },
565 | "process-nextick-args": {
566 | "version": "2.0.0",
567 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
568 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
569 | },
570 | "punycode": {
571 | "version": "1.4.1",
572 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
573 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
574 | },
575 | "qs": {
576 | "version": "6.5.2",
577 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
578 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
579 | },
580 | "readable-stream": {
581 | "version": "2.3.6",
582 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
583 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
584 | "requires": {
585 | "core-util-is": "~1.0.0",
586 | "inherits": "~2.0.3",
587 | "isarray": "~1.0.0",
588 | "process-nextick-args": "~2.0.0",
589 | "safe-buffer": "~5.1.1",
590 | "string_decoder": "~1.1.1",
591 | "util-deprecate": "~1.0.1"
592 | }
593 | },
594 | "readline-sync": {
595 | "version": "1.4.9",
596 | "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.9.tgz",
597 | "integrity": "sha1-PtqOZfI80qF+YTAbHwADOWr17No="
598 | },
599 | "remove-trailing-separator": {
600 | "version": "1.1.0",
601 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
602 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
603 | },
604 | "request": {
605 | "version": "2.87.0",
606 | "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz",
607 | "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==",
608 | "requires": {
609 | "aws-sign2": "~0.7.0",
610 | "aws4": "^1.6.0",
611 | "caseless": "~0.12.0",
612 | "combined-stream": "~1.0.5",
613 | "extend": "~3.0.1",
614 | "forever-agent": "~0.6.1",
615 | "form-data": "~2.3.1",
616 | "har-validator": "~5.0.3",
617 | "http-signature": "~1.2.0",
618 | "is-typedarray": "~1.0.0",
619 | "isstream": "~0.1.2",
620 | "json-stringify-safe": "~5.0.1",
621 | "mime-types": "~2.1.17",
622 | "oauth-sign": "~0.8.2",
623 | "performance-now": "^2.1.0",
624 | "qs": "~6.5.1",
625 | "safe-buffer": "^5.1.1",
626 | "tough-cookie": "~2.3.3",
627 | "tunnel-agent": "^0.6.0",
628 | "uuid": "^3.1.0"
629 | }
630 | },
631 | "safe-buffer": {
632 | "version": "5.1.1",
633 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
634 | "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM="
635 | },
636 | "sax": {
637 | "version": "1.2.4",
638 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
639 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
640 | },
641 | "sprintf-js": {
642 | "version": "1.1.1",
643 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz",
644 | "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw="
645 | },
646 | "sshpk": {
647 | "version": "1.14.1",
648 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz",
649 | "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=",
650 | "requires": {
651 | "asn1": "~0.2.3",
652 | "assert-plus": "^1.0.0",
653 | "bcrypt-pbkdf": "^1.0.0",
654 | "dashdash": "^1.12.0",
655 | "ecc-jsbn": "~0.1.1",
656 | "getpass": "^0.1.1",
657 | "jsbn": "~0.1.0",
658 | "tweetnacl": "~0.14.0"
659 | }
660 | },
661 | "string_decoder": {
662 | "version": "1.1.1",
663 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
664 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
665 | "requires": {
666 | "safe-buffer": "~5.1.0"
667 | }
668 | },
669 | "tar-stream": {
670 | "version": "1.6.1",
671 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz",
672 | "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==",
673 | "requires": {
674 | "bl": "^1.0.0",
675 | "buffer-alloc": "^1.1.0",
676 | "end-of-stream": "^1.0.0",
677 | "fs-constants": "^1.0.0",
678 | "readable-stream": "^2.3.0",
679 | "to-buffer": "^1.1.0",
680 | "xtend": "^4.0.0"
681 | }
682 | },
683 | "to-buffer": {
684 | "version": "1.1.1",
685 | "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
686 | "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg=="
687 | },
688 | "tough-cookie": {
689 | "version": "2.3.4",
690 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
691 | "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
692 | "requires": {
693 | "punycode": "^1.4.1"
694 | }
695 | },
696 | "tunnel-agent": {
697 | "version": "0.6.0",
698 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
699 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
700 | "requires": {
701 | "safe-buffer": "^5.0.1"
702 | }
703 | },
704 | "tweetnacl": {
705 | "version": "0.14.5",
706 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
707 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
708 | "optional": true
709 | },
710 | "url-join": {
711 | "version": "2.0.5",
712 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
713 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg="
714 | },
715 | "util-deprecate": {
716 | "version": "1.0.2",
717 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
718 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
719 | },
720 | "uuid": {
721 | "version": "3.2.1",
722 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
723 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
724 | },
725 | "verror": {
726 | "version": "1.10.0",
727 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
728 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
729 | "requires": {
730 | "assert-plus": "^1.0.0",
731 | "core-util-is": "1.0.2",
732 | "extsprintf": "^1.2.0"
733 | }
734 | },
735 | "wrappy": {
736 | "version": "1.0.2",
737 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
738 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
739 | },
740 | "xml2js": {
741 | "version": "0.4.19",
742 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
743 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
744 | "requires": {
745 | "sax": ">=0.6.0",
746 | "xmlbuilder": "~9.0.1"
747 | }
748 | },
749 | "xmlbuilder": {
750 | "version": "9.0.7",
751 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
752 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
753 | },
754 | "xtend": {
755 | "version": "4.0.1",
756 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
757 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
758 | },
759 | "zip-stream": {
760 | "version": "1.2.0",
761 | "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz",
762 | "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=",
763 | "requires": {
764 | "archiver-utils": "^1.3.0",
765 | "compress-commons": "^1.2.0",
766 | "lodash": "^4.8.0",
767 | "readable-stream": "^2.0.0"
768 | }
769 | }
770 | }
771 | }
772 |
--------------------------------------------------------------------------------