├── 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 |
5 | Bearer {jwt} 6 |
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 |
5 | Bearer {jwt} 6 |
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 | [![Screenshot](./images/screenshot-20180316-103908.png)](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 | ![JWT Policies in the Palette](./images/check-for-JWT-policies.gif "Policy Chooser") 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 | --------------------------------------------------------------------------------