├── .gitignore ├── plugin_jwt ├── package.json ├── .tern-project ├── cartridge │ ├── plugin_jwt.properties │ ├── scripts │ │ ├── index.js │ │ ├── jwt │ │ │ ├── jwtHelper.js │ │ │ ├── decode.js │ │ │ ├── sign.js │ │ │ └── verify.js │ │ └── helpers │ │ │ └── rsaToDer.js │ └── controllers │ │ └── JWTTest.js └── .project └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .history/** 2 | -------------------------------------------------------------------------------- /plugin_jwt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./cartridge/scripts/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /plugin_jwt/.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | 'ecmaVersion': 5, 3 | 'plugins': { 4 | 'guess-types': { 5 | }, 6 | 'outline': { 7 | }, 8 | 'demandware': { 9 | } 10 | } 11 | }' 12 | -------------------------------------------------------------------------------- /plugin_jwt/cartridge/plugin_jwt.properties: -------------------------------------------------------------------------------- 1 | ## cartridge.properties for cartridge plugin_jwt 2 | #undefined Feb 15 15:53:24 CEST 2020 3 | demandware.cartridges.plugin_jwt.multipleLanguageStorefront=true 4 | demandware.cartridges.plugin_jwt.id=plugin_jwt -------------------------------------------------------------------------------- /plugin_jwt/cartridge/scripts/index.js: -------------------------------------------------------------------------------- 1 | var sign = require('*/cartridge/scripts/jwt/sign.js'); 2 | var verify = require('*/cartridge/scripts/jwt/verify.js'); 3 | var decode = require('*/cartridge/scripts/jwt/decode.js'); 4 | 5 | module.exports.sign = sign.signJWT; 6 | module.exports.verify = verify.verifyJWT; 7 | module.exports.decode = decode.decodeJWT; 8 | 9 | -------------------------------------------------------------------------------- /plugin_jwt/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | plugin_jwt 4 | 5 | 6 | 7 | 8 | 9 | com.demandware.studio.core.beehiveElementBuilder 10 | 11 | 12 | 13 | 14 | 15 | com.demandware.studio.core.beehiveNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /plugin_jwt/cartridge/scripts/jwt/jwtHelper.js: -------------------------------------------------------------------------------- 1 | var JWT_REGEX = /^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/; 2 | var SUPPORTED_ALGORITHMS = ['RS256', 'RS384', 'RS512', 'HS256', 'HS384', 'HS512', 'PS256', 'PS384']; 3 | var Mac = require('dw/crypto/Mac'); 4 | 5 | function isValidJWT(jwt) { 6 | return JWT_REGEX.test(jwt); 7 | } 8 | 9 | var JWTAlgoToSFCCMapping = { 10 | "RS256" : "SHA256withRSA", 11 | "RS512" : "SHA512withRSA", 12 | "RS384" : "SHA384withRSA", 13 | "HS256": Mac.HMAC_SHA_256, 14 | "HS384": Mac.HMAC_SHA_384, 15 | "HS512": Mac.HMAC_SHA_512, 16 | "PS256": "SHA256withRSA/PSS", 17 | "PS384": "SHA384withRSA/PSS" 18 | }; 19 | 20 | function toBase64UrlEncoded(input) { 21 | return input.replace(/\+/g, '-') 22 | .replace(/\//g, '_') 23 | .replace(/\=+$/m, ''); 24 | } 25 | 26 | module.exports.isValidJWT = isValidJWT; 27 | module.exports.toBase64UrlEncoded = toBase64UrlEncoded; 28 | module.exports.SUPPORTED_ALGORITHMS = SUPPORTED_ALGORITHMS; 29 | module.exports.JWTAlgoToSFCCMapping = JWTAlgoToSFCCMapping; 30 | -------------------------------------------------------------------------------- /plugin_jwt/cartridge/scripts/jwt/decode.js: -------------------------------------------------------------------------------- 1 | var jwtHelper = require('*/cartridge/scripts/jwt/jwtHelper'); 2 | var Logger = require('dw/system/Logger'); 3 | 4 | function decodeJWT(jwt, options) { 5 | var options = options || {}; 6 | 7 | if (!jwtHelper.isValidJWT(jwt)) { 8 | return null; 9 | } 10 | 11 | var header = getHeaderFromJWT(jwt); 12 | if(!header) { 13 | return null; 14 | } 15 | 16 | var payload = getPayloadFromJWT(jwt); 17 | if (!payload) { 18 | return null; 19 | } 20 | 21 | var signature = getSignatureFromJWT(jwt); 22 | if (!signature) { 23 | return null; 24 | } 25 | 26 | return { 27 | header: header, 28 | payload: payload, 29 | signature: signature 30 | } 31 | } 32 | 33 | function getHeaderFromJWT(jwt) { 34 | var encodedHeader = jwt.split('.')[0]; 35 | var Encoding = require('dw/crypto/Encoding'); 36 | 37 | var decodedHeader = Encoding.fromBase64(encodedHeader).toString(); 38 | var jwtHeaderObj = {}; 39 | 40 | try { 41 | jwtHeaderObj = JSON.parse(decodedHeader); 42 | } catch (error) { 43 | Logger.error('Error parsing jwt token header'); 44 | return null; 45 | } 46 | 47 | return jwtHeaderObj; 48 | } 49 | 50 | function getPayloadFromJWT(jwt) { 51 | var encodedPayload = jwt.split('.')[1]; 52 | var Encoding = require('dw/crypto/Encoding'); 53 | 54 | var decodedPayload = Encoding.fromBase64(encodedPayload).toString(); 55 | var jwtPayloadObj = {}; 56 | 57 | try { 58 | jwtPayloadObj = JSON.parse(decodedPayload); 59 | } catch (error) { 60 | Logger.error('Error parsing jwt token payload'); 61 | return null; 62 | } 63 | 64 | return jwtPayloadObj; 65 | } 66 | 67 | function getSignatureFromJWT(jwt) { 68 | return jwt.split('.')[2]; 69 | } 70 | 71 | module.exports.decodeJWT = decodeJWT; 72 | 73 | -------------------------------------------------------------------------------- /plugin_jwt/cartridge/scripts/helpers/rsaToDer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Highly custom logic to create public key. 3 | * Return public key as DER 4 | * https://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js 5 | * https://github.com/tracker1/node-rsa-pem-from-mod-exp 6 | */ 7 | var Encoding = require('dw/crypto/Encoding'); 8 | function getRSAPublicKey(modulus_b64, exponent_b64) { 9 | 10 | function prepadSigned(hexStr) { 11 | msb = hexStr[0] 12 | if ( 13 | (msb>='8' && msb<='9') || 14 | (msb>='a' && msb<='f') || 15 | (msb>='A'&&msb<='F')) { 16 | return '00'+hexStr; 17 | } else { 18 | return hexStr; 19 | } 20 | } 21 | 22 | function toHex(number) { 23 | var nstr = number.toString(16) 24 | if (nstr.length%2==0) return nstr 25 | return '0'+nstr 26 | } 27 | 28 | // encode ASN.1 DER length field 29 | // if <=127, short form 30 | // if >=128, long form 31 | function encodeLengthHex(n) { 32 | if (n<=127) return toHex(n) 33 | else { 34 | n_hex = toHex(n) 35 | length_of_length_byte = 128 + n_hex.length/2 // 0x80+numbytes 36 | return toHex(length_of_length_byte)+n_hex 37 | } 38 | } 39 | 40 | var modulus = Encoding.fromBase64(modulus_b64); 41 | var exponent = Encoding.fromBase64(exponent_b64); 42 | 43 | 44 | var modulus_hex = Encoding.toHex(modulus); 45 | var exponent_hex = Encoding.toHex(exponent); 46 | 47 | modulus_hex = prepadSigned(modulus_hex) 48 | exponent_hex = prepadSigned(exponent_hex) 49 | 50 | var modlen = modulus_hex.length/2 51 | var explen = exponent_hex.length/2 52 | 53 | var encoded_modlen = encodeLengthHex(modlen) 54 | var encoded_explen = encodeLengthHex(explen) 55 | var encoded_pubkey = '30' + 56 | encodeLengthHex( 57 | modlen + 58 | explen + 59 | encoded_modlen.length/2 + 60 | encoded_explen.length/2 + 2 61 | ) + 62 | '02' + encoded_modlen + modulus_hex + 63 | '02' + encoded_explen + exponent_hex; 64 | 65 | var seq2 = 66 | '30 0d ' + 67 | '06 09 2a 86 48 86 f7 0d 01 01 01' + 68 | '05 00 ' + 69 | '03' + encodeLengthHex(encoded_pubkey.length/2 + 1) + 70 | '00' + encoded_pubkey; 71 | 72 | seq2 = seq2.replace(/ /g,''); 73 | 74 | var der_hex = '30' + encodeLengthHex(seq2.length/2) + seq2; 75 | 76 | der_hex = der_hex.replace(/ /g, ''); 77 | 78 | var der_b64 = Encoding.toBase64(Encoding.fromHex(der_hex)); 79 | 80 | // var pem = '-----BEGIN PUBLIC KEY-----\n' 81 | // + der_b64.match(/.{1,64}/g).join('\n') 82 | // + '\n-----END PUBLIC KEY-----\n'; 83 | 84 | return der_b64 85 | } 86 | 87 | module.exports.getRSAPublicKey = getRSAPublicKey; 88 | -------------------------------------------------------------------------------- /plugin_jwt/cartridge/scripts/jwt/sign.js: -------------------------------------------------------------------------------- 1 | var jwtHelper = require('*/cartridge/scripts/jwt/jwtHelper'); 2 | var Logger = require('dw/system/Logger'); 3 | var Encoding = require('dw/crypto/Encoding'); 4 | var Bytes = require('dw/util/Bytes'); 5 | var Signature = require('dw/crypto/Signature'); 6 | var StringUtils = require('dw/util/StringUtils'); 7 | var Mac = require('dw/crypto/Mac'); 8 | 9 | var JWTAlgoToSignMapping = { 10 | "RS256" : signWithRSA, 11 | "RS384" : signWithRSA, 12 | "RS512" : signWithRSA, 13 | "HS256": signWithHMAC, 14 | "HS384": signWithHMAC, 15 | "HS512": signWithHMAC, 16 | "PS256": signWithRSA, 17 | "PS384": signWithRSA 18 | }; 19 | 20 | 21 | var JWTAlgoToSFCCMapping = jwtHelper.JWTAlgoToSFCCMapping; 22 | 23 | function signJWT(payload, options) { 24 | 25 | if (!payload || typeof payload !== 'object') { 26 | throw new Error('Invalid payload passed to create JWT token'); 27 | } 28 | 29 | var algorithm = options.algorithm; 30 | var supportedAlgorithms = jwtHelper.SUPPORTED_ALGORITHMS; 31 | if (supportedAlgorithms.indexOf(algorithm) === -1) { 32 | throw new Error(StringUtils.format('JWT Algorithm {0} not supported', algorithm)); 33 | } 34 | 35 | var header = { 36 | "alg": options.algorithm, 37 | "type": "JWT", 38 | "kid" : options.kid 39 | }; 40 | 41 | var headerBase64 = Encoding.toBase64(new Bytes(JSON.stringify(header))); 42 | var headerBase64UrlEncoded = jwtHelper.toBase64UrlEncoded(headerBase64); 43 | 44 | var payloadBase64 = Encoding.toBase64(new Bytes(JSON.stringify(payload))); 45 | var payloadBase64UrlEncoded = jwtHelper.toBase64UrlEncoded(payloadBase64); 46 | 47 | var signature = headerBase64UrlEncoded + "." + payloadBase64UrlEncoded; 48 | 49 | var privateKeyOrSecret; 50 | if(options.privateKeyOrSecret && typeof options.privateKeyOrSecret === 'string') { 51 | privateKeyOrSecret = options.privateKeyOrSecret; 52 | } 53 | 54 | if (!privateKeyOrSecret) { 55 | throw new Error('Cannot sign JWT token as private key or secret not supplied'); 56 | } 57 | 58 | var signFunction = JWTAlgoToSignMapping[algorithm]; 59 | if (!signFunction) { 60 | throw new Error(StringUtils.format('No sign function found for supplied algorithm {0}', algorithm)); 61 | } 62 | 63 | var jwtSignature = signFunction(signature, privateKeyOrSecret, algorithm); 64 | var jwtSignatureUrlEncoded = jwtHelper.toBase64UrlEncoded(jwtSignature); 65 | 66 | var jwtToken = headerBase64UrlEncoded + '.' + payloadBase64UrlEncoded + '.' + jwtSignatureUrlEncoded; 67 | 68 | return jwtToken; 69 | } 70 | 71 | 72 | function signWithRSA(input, privateKey, algorithm) { 73 | var contentToSignInBytes = new Bytes(input); 74 | 75 | var apiSig = new Signature(); 76 | var signedBytes = apiSig.signBytes(contentToSignInBytes, new Bytes(privateKey), JWTAlgoToSFCCMapping[algorithm]); 77 | 78 | return Encoding.toBase64(signedBytes); 79 | } 80 | 81 | function signWithHMAC(input, secret, algorithm) { 82 | var mac = new Mac(JWTAlgoToSFCCMapping[algorithm]); 83 | var inputInBytes = new Bytes(input); 84 | var secretInBytes = new Bytes(secret); 85 | 86 | var output = mac.digest(inputInBytes, secretInBytes); 87 | 88 | return Encoding.toBase64(output); 89 | } 90 | module.exports.signJWT = signJWT; 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sfcc_jwt 2 | An implementation of [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) for Salesforce Commerce Cloud SFRA. 3 | 4 | # Install 5 | Install the cartridge on server & add it to cartridge path. 6 | 7 | # Usage 8 | 9 | ### jwt.sign(payload, options) 10 | 11 | Returns the JsonWebToken as string. 12 | 13 | `payload` is an object literal representing valid JSON. 14 | 15 | `options`: 16 | 17 | * `privateKeyOrSecret` is a string containing either the secret for HMAC algorithms or the private key for RSA. 18 | * `algorithm` HS256, RS256 or similar 19 | * `kid` 20 | 21 | Sign with HMAC SHA256 22 | 23 | ```js 24 | var jwt = require('plugin_jwt'); 25 | var options = {}; 26 | options.privateKeyOrSecret = 'my_secret'; 27 | options.algorithm = 'HS256'; 28 | var token = jwt.sign({ foo: 'bar' }, options); 29 | ``` 30 | 31 | Sign with RSA SHA256 32 | ```js 33 | var privateKey = 'my_private_key'; 34 | var options = {}; 35 | options.privateKeyOrSecret = privateKey; 36 | options.algorithm = 'RS256'; 37 | var token = jwt.sign({ foo: 'bar' }, options); 38 | ``` 39 | 40 | ### jwt.verify(token, options) 41 | 42 | Returns a boolean signifying if the signature is valid or not. 43 | 44 | `token` is the JsonWebToken string 45 | 46 | `options`: 47 | 48 | * `publicKeyOrSecret` is a string containing either the secret for HMAC algorithms or the public key for RSA or a function which will return an appropriate [JSON Web Key Set](https://auth0.com/docs/tokens/concepts/jwks) for a kid. This function should return a modulus & exponential which then will be used to generate a DER format of public key. Note `PKCS#1` is not supported by SFCC, so you'd have to convert your pem to use `X.509/SPKI` format. 49 | * `ignoreExpiration` is a boolean to skip JWT expiration time verification. 50 | * `audience` is a string containing JWT audience. 51 | * `issuer` is a string containing JWT issuer. 52 | 53 | Verify HMAC SHA256 54 | 55 | ```js 56 | var jwt = require('plugin_jwt'); 57 | var token = 'my_token'; 58 | var options = {}; 59 | options.publicKeyOrSecret = 'my_secret'; 60 | var isValid = jwt.verify(token, options); 61 | ``` 62 | 63 | Verify RSA SHA256 64 | ```js 65 | var publicKey = 'my_public_key'; 66 | var token = 'my_token'; 67 | var options = {}; 68 | options.publicKeyOrSecret = publicKey; 69 | var isValid = jwt.verify(token, options); 70 | ``` 71 | 72 | ### jwt.decode(token, options) 73 | 74 | Returns the decoded payload without verifying if the signature is valid. 75 | 76 | `token` is the JsonWebToken string 77 | 78 | ```js 79 | var decoded = jwt.decode(token); 80 | ``` 81 | 82 | ## Algorithms supported 83 | 84 | Array of supported algorithms. The following algorithms are currently supported. 85 | 86 | alg Parameter Value | Digital Signature or MAC Algorithm 87 | ----------------|---------------------------- 88 | HS256 | HMAC using SHA-256 hash algorithm 89 | HS384 | HMAC using SHA-384 hash algorithm 90 | HS512 | HMAC using SHA-512 hash algorithm 91 | RS256 | RSA using SHA-256 hash algorithm 92 | RS384 | RSA using SHA-384 hash algorithm 93 | RS512 | RSA using SHA-512 hash algorithm 94 | PS256 | RSA-PSS using SHA-256 hash algorithm 95 | PS384 | RSA-PSS using SHA-384 hash algorithm 96 | 97 | 98 | ## Example 99 | 100 | Check `JWTTest.js` controller for SFRA example. 101 | 102 | ## Resources 103 | 104 | 1. https://jwt.io/ 105 | 2. https://jwt.io/introduction/ 106 | 3. https://github.com/auth0/node-jsonwebtoken 107 | 108 | ## Note 109 | 110 | This repository is heavily inspired from node-js repo [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) 111 | -------------------------------------------------------------------------------- /plugin_jwt/cartridge/controllers/JWTTest.js: -------------------------------------------------------------------------------- 1 | var server = require('server'); 2 | 3 | var jwt = require('plugin_jwt'); 4 | server.get('RSA', function (req, res, next) { 5 | // How to create private/public key-pair https://documentation.b2c.commercecloud.salesforce.com/DOC2/index.jsp?topic=%2Fcom.demandware.dochelp%2FDWAPI%2Fscriptapi%2Fhtml%2Fapi%2Fclass_dw_crypto_Cipher.html&resultof=%22%43%69%70%68%65%72%22%20%22%63%69%70%68%65%72%22%20&anchor=dw_crypto_Cipher_encrypt_2_String_String_String_String_Number_DetailAnchor 6 | var privateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDG5RnOVufr0EXDvN24U0Y+nCbkCQP4uRg/Zm1uvjhevRkPvbo5Q0BjfN9biYb9IddrL0VaP7AdMrKMHIrLYrFZsntFCKbzdcd2QRlq9ECpNOCcQpcFR1kFF5ncfWfjDTn/IVMUDxcUawBleVE2Hy1VvPpG7M3wpUtBTgVgUQuIMqKowcnqokbc/dsd7Nsh5Co9ZcO4SJd2S1k0tjOlqOVWSRa2KVJpDDCoIzITzLp+wvW2CjLXOX+990QZrhTQuTYjWRQnNsW/h6NFvv6zCCGMLwdlymZXXsGBU8ha3om84hgdTpPpEiSgDDhxtwUV3+5IS+LZqYypaGFETzuT3v/jAgMBAAECggEBAIzKKa1FGWB0ip3W+H/8+pFhSapLs3MB2ucuIzKsFg0CAFaDL+pO76o8/4K1ZEeVB/8IdChBQvI4K+lAXwM/dlkPHXEtgbh+29WamVp7UbY1BvW1sV98NMiE/1Xzs3EmsLInrb5aPDUo1Rv/d3w/L4Esh2FjSRgaeZ3dk7KtS+N47I1VKR7mVAq/VISOCo+Gwsoou8w6JGDcHlotGiVBdBItuxqyrWyl/xYb7uxiPjHyuPJy+ohfS2FggANyOc4dFx/+cUHBDtt2ejycWiru5KVdKQXPdTjVM5QbbAJl7I5SfB60zrhPm6SsAXamZAA/srs0bvvEl1RDwNsfA+PLsEECgYEA7ToC+ERWW2HH3Z6H3GxTwGKnchiQILdI+97aLt804z/U9iayGTougPAy120xCZfoGgNQY3PNmAiUHfIyzUV/VOca+pVhm9d10sZe+sGf9VLWjfLRIToKXgACdpZO93NuNoK8VlZ/XA6qD9BUzprqxDOVFRZT2QjUwfCmvPtpoUMCgYEA1qKGeXd1zd01YyPaijWCieko7tAwL5C/Gci7TH6mrlacUMQZv33QapQ5aS3EayH5GWAYU+5GMymIKH/zJwGKDdbhDHk/MdbHZABlPKib6MRJZtpB3mO8Ri9FefmojC1CZtH/YRwdkFF2FD39Fg6Dctn3GLkAVp9XEFIH7wQAbOECgYBm0nwzC7u6hBlTL8GHgtSSULBvPcJKy+awdRlws4KC9UnjH0aWtKcvb+05frSAif0qOUGAudLlEOLSUAZA/tx/+mOxNUpHeA4zu5OzcHVaqfshL5wBoNyZfbuTlvbHPpsIuYXUjk1Jo3mGvS/lFTSosgruRu005yUAosRCqV5RbQKBgAhfzPleRNVkZRnaI0OzNMWmuDchHlAsyJf78frZEi3JKU4paIvFH+WYpOjKpVg8uhhYXHqh2FFUtIBIBbem4rkJgjxXWrTaGWt4bHrCZVrelbKSn3FK2OSwIXjR2damSWnzlZA3ZZvk4cOGa6J5rH1FrdNkHHREwUPcv3x+3nlhAoGADei68sA0bNhQycr6R3pMlUbKaQG8zaTU7YX3golYxgAscwuQvmUsZXYqDdo9lyl9HXjyceopq9lsQPvZqOAu0lC1LiH4yYVTTY9Ka7boBbk9dnoZTMmNEEdxFRq0ZjZfRb9MdqFNwfjukhDGADAVGnSyymtoQoEuTnZQm2xBtg4='; 7 | var publicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxuUZzlbn69BFw7zduFNGPpwm5AkD+LkYP2Ztbr44Xr0ZD726OUNAY3zfW4mG/SHXay9FWj+wHTKyjByKy2KxWbJ7RQim83XHdkEZavRAqTTgnEKXBUdZBReZ3H1n4w05/yFTFA8XFGsAZXlRNh8tVbz6RuzN8KVLQU4FYFELiDKiqMHJ6qJG3P3bHezbIeQqPWXDuEiXdktZNLYzpajlVkkWtilSaQwwqCMyE8y6fsL1tgoy1zl/vfdEGa4U0Lk2I1kUJzbFv4ejRb7+swghjC8HZcpmV17BgVPIWt6JvOIYHU6T6RIkoAw4cbcFFd/uSEvi2amMqWhhRE87k97/4wIDAQAB'; 8 | 9 | var options = {}; 10 | options.privateKeyOrSecret = privateKey; 11 | options.algorithm = 'RS256'; 12 | 13 | var payload = { 14 | "name": "john", 15 | "lastname": "doe", 16 | 'iss': "sample-issuer", 17 | "sub": "sample subject" 18 | }; 19 | 20 | var jwtToken = jwt.sign(payload, options); 21 | 22 | var decodedToken = jwt.decode(jwtToken); 23 | 24 | var options = {}; 25 | options.publicKeyOrSecret = publicKey; 26 | 27 | var verified = jwt.verify(jwtToken, options); 28 | 29 | res.json({decodedToken:decodedToken, verified:verified, jwtToken: jwtToken}); 30 | next() 31 | }); 32 | 33 | server.get('HMAC', function (req, res, next) { 34 | var secret = 'my_secret'; 35 | 36 | var options = {}; 37 | options.privateKeyOrSecret = secret; 38 | options.algorithm = 'HS256'; 39 | 40 | var payload = { 41 | "name": "john", 42 | "lastname": "doe", 43 | 'iss': "sample-issuer", 44 | "sub": "sample subject" 45 | }; 46 | 47 | var jwtToken = jwt.sign(payload, options); 48 | 49 | var decodedToken = jwt.decode(jwtToken); 50 | 51 | var options = {}; 52 | options.publicKeyOrSecret = secret; 53 | 54 | var verified = jwt.verify(jwtToken, options); 55 | 56 | res.json({decodedToken:decodedToken, verified:verified, jwtToken: jwtToken}); 57 | next() 58 | }); 59 | 60 | module.exports = server.exports(); 61 | -------------------------------------------------------------------------------- /plugin_jwt/cartridge/scripts/jwt/verify.js: -------------------------------------------------------------------------------- 1 | var jwtHelper = require('*/cartridge/scripts/jwt/jwtHelper'); 2 | var jwtDecode = require('*/cartridge/scripts/jwt/decode'); 3 | 4 | var Logger = require('dw/system/Logger'); 5 | var Bytes = require('dw/util/Bytes'); 6 | var Encoding = require('dw/crypto/Encoding'); 7 | var Signature = require('dw/crypto/Signature'); 8 | var StringUtils = require('dw/util/StringUtils'); 9 | var Mac = require('dw/crypto/Mac'); 10 | 11 | var JWTAlgoToVerifierMapping = { 12 | "RS256" : createRSAVerifier, 13 | "RS384" : createRSAVerifier, 14 | "RS512" : createRSAVerifier, 15 | "HS256": createHMACVerifier, 16 | "HS384": createHMACVerifier, 17 | "HS512": createHMACVerifier, 18 | "PS256": createRSAVerifier, 19 | "PS384": createRSAVerifier 20 | }; 21 | 22 | var JWTAlgoToSFCCMapping = jwtHelper.JWTAlgoToSFCCMapping; 23 | 24 | function verifyJWT(jwt, options) { 25 | var options = options || {}; 26 | 27 | 28 | if (!jwtHelper.isValidJWT(jwt)) { 29 | return false; 30 | } 31 | 32 | var decodedToken = jwtDecode.decodeJWT(jwt); 33 | if (!decodedToken) { 34 | return false; 35 | } 36 | 37 | var algorithm = decodedToken.header.alg; 38 | var parts = jwt.split('.'); 39 | 40 | var supportedAlgorithms = jwtHelper.SUPPORTED_ALGORITHMS; 41 | if (supportedAlgorithms.indexOf(algorithm) === -1) { 42 | throw new Error(StringUtils.format('JWT Algorithm {0} not supported', algorithm)); 43 | } 44 | 45 | var header = parts[0]; 46 | var payload = parts[1]; 47 | var jwtSig = parts[2]; 48 | 49 | var contentToVerify = header + '.' + payload; 50 | 51 | var publicKeyOrSecret; 52 | if(options.publicKeyOrSecret && typeof options.publicKeyOrSecret === 'string') { 53 | publicKeyOrSecret = options.publicKeyOrSecret; 54 | } else if(options.publicKeyOrSecret && typeof options.publicKeyOrSecret === 'function') { 55 | var jsonWebKey = options.publicKeyOrSecret(decodedToken); 56 | if (jsonWebKey && jsonWebKey.modulus && jsonWebKey.exponential) { 57 | var keyHelper = require('*/cartridge/scripts/helpers/rsaToDer'); 58 | publicKeyOrSecret = keyHelper.getRSAPublicKey(jsonWebKey.modulus, jsonWebKey.exponential); 59 | } 60 | } 61 | 62 | if (!publicKeyOrSecret) { 63 | throw new Error('Cannot verify JWT token as public key or secret not supplied'); 64 | } 65 | 66 | var verifier = JWTAlgoToVerifierMapping[algorithm]; 67 | if (!verifier) { 68 | throw new Error(StringUtils.format('No verifier function found for supplied algorithm {0}', algorithm)); 69 | } 70 | 71 | var verified = verifier(jwtSig, contentToVerify, publicKeyOrSecret, algorithm); 72 | if (!verified) { 73 | return false; 74 | } 75 | 76 | var payload = decodedToken.payload; 77 | if (!options.ignoreExpiration) { 78 | var jwtExp = payload.exp; 79 | //seconds to ms 80 | var expirationDate = new Date(jwtExp * 1000); 81 | var currentDate = new Date(); 82 | // expired 83 | if (expirationDate < currentDate) { 84 | return false; 85 | } 86 | } 87 | 88 | if (options.audience) { 89 | var aud = payload.aud; 90 | if (options.audience !== aud) { 91 | return false; 92 | } 93 | } 94 | 95 | if (options.issuer) { 96 | var iss = payload.iss; 97 | if (iss !== options.issuer) { 98 | return false; 99 | } 100 | } 101 | 102 | return true; 103 | } 104 | 105 | function createRSAVerifier(signature, input, publicKey, algorithm) { 106 | var jwtSignatureInBytes = new Encoding.fromBase64(signature); 107 | var contentToVerifyInBytes = new Bytes(input); 108 | 109 | var apiSig = new Signature(); 110 | var verified = apiSig.verifyBytesSignature(jwtSignatureInBytes, contentToVerifyInBytes, new Bytes(publicKey), JWTAlgoToSFCCMapping[algorithm]); 111 | return verified 112 | } 113 | 114 | function createHMACVerifier(signature, input, secret, algorithm) { 115 | var mac = new Mac(JWTAlgoToSFCCMapping[algorithm]); 116 | var inputInBytes = new Bytes(input); 117 | var secretInBytes = new Bytes(secret); 118 | 119 | // create digest of input & compare against jwt signature 120 | var outputInBytes = mac.digest(inputInBytes, secretInBytes); 121 | var outputInString = Encoding.toBase64(outputInBytes); 122 | 123 | // signature is base64UrlEncoded so convert input to same 124 | var urlEncodedOutput = jwtHelper.toBase64UrlEncoded(outputInString); 125 | 126 | return signature === urlEncodedOutput; 127 | } 128 | module.exports.verifyJWT = verifyJWT; 129 | --------------------------------------------------------------------------------