Full Blog TOC

Full Blog Table Of Content with Keywords Available HERE

Monday, February 21, 2022

Symmetric Encrypt and Decrypt in Golang

 



In this post we will review AES-CBC symmetric encryption in Golang.


We start by key generation.



import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"radware.com/euda/commons/global/encryption/padding"
)

const keySizeBytes = 32

type EncryptedData struct {
Cipher string `json:"cipher"`
Iv string `json:"iv"`
}

type Key struct {
key []byte
}

func GenerateKey() (*Key, error) {
key := make([]byte, keySizeBytes)
_, err := rand.Read(key)
if err != nil {
return nil, fmt.Errorf("read failed: %v", err)
}
k := Key{key: key}
return &k, nil
}



The key can be easily exported and imported.


func LoadKey(keyBase64 string) (*Key, error) {
bytes, err := base64.StdEncoding.DecodeString(keyBase64)
if err != nil {
return nil, fmt.Errorf("base64 decode failed: %v", err)
}
k := Key{key: bytes}
return &k, nil
}

func (k *Key) ExportKey() string {
return base64.StdEncoding.EncodeToString(k.key)
}


And finally we can encrypt and decrypt using the key.



func (k *Key) Encrypt(clearText string) (string, error) {
iv := make([]byte, aes.BlockSize)
_, err := rand.Read(iv)
if err != nil {
return "", fmt.Errorf("read failed: %v", err)
}

aesCipher, err := aes.NewCipher(k.key)
if err != nil {
return "", fmt.Errorf("create cipher failed: %v", err)
}

cbc := cipher.NewCBCEncrypter(aesCipher, iv)

clearBytes := []byte(clearText)
clearBytesPadded, err := padding.Pkcs7Pad(clearBytes, aes.BlockSize)
if err != nil {
return "", fmt.Errorf("padding failed: %v", err)
}

cipherBytes := make([]byte, len(clearBytesPadded))

cbc.CryptBlocks(cipherBytes, clearBytesPadded)
data := EncryptedData{
Cipher: base64.StdEncoding.EncodeToString(cipherBytes),
Iv: base64.StdEncoding.EncodeToString(iv),
}

jsonBytes, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("marshal failed: %v", err)
}

return string(jsonBytes), nil
}

func (k *Key) Decrypt(jsonData string) (string, error) {
var data EncryptedData
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
return "", fmt.Errorf("unmarshal failed: %v", err)
}

iv, err := base64.StdEncoding.DecodeString(data.Iv)
if err != nil {
return "", fmt.Errorf("decode iv failed: %v", err)
}

cipherBytes, err := base64.StdEncoding.DecodeString(data.Cipher)
if err != nil {
return "", fmt.Errorf("decode cipher failed: %v", err)
}

aesCipher, err := aes.NewCipher(k.key)
if err != nil {
return "", fmt.Errorf("create cipher failed: %v", err)
}
cbc := cipher.NewCBCDecrypter(aesCipher, iv)

paddedClearTextBytes := make([]byte, len(cipherBytes))
cbc.CryptBlocks(paddedClearTextBytes, cipherBytes)

clearTextBytes, err := padding.Pkcs7Unpad(paddedClearTextBytes, aes.BlockSize)
if err != nil {
return "", fmt.Errorf("un-padding failed: %v", err)
}

clearText := string(clearTextBytes)
return clearText, nil
}



Notice that the encryption result includes Initialization Vector (IV), which is a random buffer that is used in the encryption. The IV is returned from the encrypt function unchanged. The decrypt function uses both the cipher result and the IV along with the AES key to do its work. To simplify the usage of both IV and cipher, we use a JSON format.



We can test our code using the following:



func Test(t *testing.T) {
key, err := GenerateKey()
if err != nil {
t.Fatal(err)
}

encrypted, err := key.Encrypt("123456789012345")
if err != nil {
t.Fatal(err)
}

t.Logf("encrypted is %v", encrypted)

exportedKey := key.ExportKey()
t.Logf("exported key is %v", exportedKey)

importedKey, err := LoadKey(exportedKey)
if err != nil {
t.Fatal(err)
}

decrypted, err := importedKey.Decrypt(encrypted)
if err != nil {
t.Fatal(err)
}
t.Logf("decrypted is %v", decrypted)
}




Final Note


This post is part of a series of posts about encryption. The full posts list is below.




Symmetric Encrypt and Decrypt in Javascript


 

In this post we will review the steps to use AES-CBC encryption in javascript. We use the SubtleCrypto API, which is available on on secure context pages. Will also use some functions that are not supported on old browsers.


First, to check if the page is considered a secure context, use the following:



console.log('secure context', window.isSecureContext)



The SubtleCrypto API uses ArrayBuffer for it functions, but as we want to send the data to server, will will convert it from and to a base64 string using the following helper functions:



function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return window.btoa(bytes)
}

function base64ToArrayBuffer(base64String) {
const chars = window.atob(base64String)
const arrayBuffer = new ArrayBuffer(chars.length)
const bufferView = new Uint8Array(arrayBuffer)
for (let i = 0, strLen = chars.length; i < strLen; i++) {
bufferView[i] = chars.charCodeAt(i)
}
return arrayBuffer
}


Special notes for NodeJS users

As btoa() does not exist in NodeJS, we need to use the Buffer, BUT we must specify the latin1 encoding to have the same encoding as in the browser.


NodeJS version:

function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return Buffer.from(bytes,'latin1').toString('base64')
}



Now we can generate the AES key.


const ALGORITHM = 'AES-CBC'


async function generateKey() {
return await window.crypto.subtle.generateKey(
{
name: ALGORITHM,
length: 256,
},
true,
['encrypt', 'decrypt'],
)
}



The key can be exported and imported.



async function exportKey(key) {
const exported = await window.crypto.subtle.exportKey(
'raw',
key,
)

return arrayBufferToBase64(exported)
}

async function importKey(base64Key) {
const bytes = base64ToArrayBuffer(base64Key)
return await window.crypto.subtle.importKey(
'raw',
bytes,
'AES-CBC',
true,
['encrypt', 'decrypt'],
)
}



And finally we can encrypt and decrypt using AES-CBC:



async function encrypt(key, clearText) {
const encodedText = new TextEncoder().encode(clearText)
const iv = window.crypto.getRandomValues(new Uint8Array(16))
const cipherText = await window.crypto.subtle.encrypt(
{
name: ALGORITHM,
iv,
},
key,
encodedText,
)

const data = {
cipher: arrayBufferToBase64(cipherText),
iv: arrayBufferToBase64(iv),
}

return JSON.stringify(data)
}

async function decrypt(key, jsonData) {
const data = JSON.parse(jsonData)
const ciphertext = base64ToArrayBuffer(data.cipher)
const iv = base64ToArrayBuffer(data.iv)
const encodedText = await window.crypto.subtle.decrypt(
{
name: 'AES-CBC',
iv,
},
key,
ciphertext,
)

return new TextDecoder().decode(encodedText)
}


Notice that the encryption result includes Initialization Vector (IV), which is a random buffer that is used in the encryption. The IV is returned from the encrypt function unchanged. The decrypt function uses both the cipher result and the IV along with the AES key to do its work. To simplify the usage of both IV and cipher, we use a JSON format.


We can now test our code. The following is an example of using the encryption.



async function unitTest() {
const logPrefix = 'symmetric unit-test'
const key = await generateKey()
console.log(logPrefix, 'key', key)
const clearText = '123456789sdfghjkl%$^&*(XXX'
console.log(logPrefix, 'clearText', clearText)
const cipher = await encrypt(key, clearText)
console.log(logPrefix, 'cipher', cipher)
const exportedKey = await exportKey(key)
console.log(logPrefix, 'exportedKey', exportedKey)
const importedKey = await importKey(exportedKey)
console.log(logPrefix, 'importedKey', importedKey)
const decrypted = await decrypt(importedKey, cipher)
console.log(logPrefix, 'decrypted', decrypted)
}





Final Note


This post is part of a series of posts about encryption. The full posts list is below.




Monday, February 14, 2022

XMLHttpRequest Proxy

 



In a previous post about XMLHttpRequest we've describe how to capture requests so that we can examine the URLs, and possibly add our own headers. In this post, we will display a more intrusive method, where we create a proxy for the XMLHttpRequest, so that we can replace the request, and the response, as well as sending the data to a different URL.


The proxy structure is as follows:



const originalXhrClass = XMLHttpRequest

XMLHttpRequest = function () {
// the proxy code goes here
}



We replace the XMLHttpRequest with our own code, and keep an internal reference to the original XMLHttpRequest. Let's examine the proxy code. Let declare some fields that we will later use.



const originalXhrObject = new originalXhrClass()
const self = this
self.onreadystatechange = null
const doneEventHandlers = []
const requestHeaders = []
let lastDoneEvent
let changeResponseDone = false
let response



In the open method call we can replace the target URL:



Object.defineProperty(self, 'open', {
value: function () {
const url = arguments[1]
arguments[1] = `https://my.example.com/my/modified/url?url=${url}`
return originalXhrObject['open'].apply(originalXhrObject, arguments)
},
})



The addRequestHeader calls are kept for later use.



Object.defineProperty(self, 'setRequestHeader', {
value: function () {
const [name, value] = arguments
requestHeaders.push([name, value])
},
})



The methods/getters/setters that we do not want to change are passed through to the original object.



const getters = ['status', 'statusText', 'readyState', 'responseXML', 'upload']
getters.forEach(function (property) {
Object.defineProperty(self, property, {
get: function () {
return originalXhrObject[property]
},
})
})

const getterAndSetters = ['ontimeout, timeout', 'responseType', 'withCredentials', 'onload', 'onerror', 'onprogress']
getterAndSetters.forEach(function (property) {
Object.defineProperty(self, property, {
get: function () {
return originalXhrObject[property]
},
set: function (val) {
originalXhrObject[property] = val
},
})
})

const standardMethods = ['removeEventListener', 'abort', 'getAllResponseHeaders', 'getResponseHeader', 'overrideMimeType']
standardMethods.forEach(function (method) {
Object.defineProperty(self, method, {
value: function () {
return originalXhrObject[method].apply(originalXhrObject, arguments)
},
})
})



Upon send of the data, we can update the request body.



Object.defineProperty(self, 'send', {
value: async function () {
originalXhrObject.setRequestHeader('Content-Type', 'application/json')
const body = arguments[0]
const newBody = {
body,
requestHeaders,
}
arguments[0] = JSON.stringify(newBody)

return originalXhrObject['send'].apply(originalXhrObject, arguments)
},
})



The response is modified once it arrives.



async function modifyResponse() {
if (changeResponseDone) {
return
}
response = `my modified response${originalXhrObject.responseText}`
changeResponseDone = true
}

originalXhrObject.onreadystatechange = async function () {
if (originalXhrObject.readyState === 4) {
await modifyResponse()
}
if (self.onreadystatechange) {
return self.onreadystatechange()
}
if (lastDoneEvent) {
doneEventHandlers.forEach((handler) => {
handler(lastDoneEvent)
})
}
}



we supply response getters.



const responseGetters = ['response', 'responseText']

responseGetters.forEach(function (property) {
Object.defineProperty(self, property, {
get: function () {
return response
},
})
})




and we handle the notification per the addEventListener, and for the onload event.



Object.defineProperty(self, 'addEventListener', {
value: function () {
const eventType = arguments[0]
if (eventType === 'load') {
const handler = arguments[1]
doneEventHandlers.push(handler)
return
}
return originalXhrObject['addEventListener'].apply(originalXhrObject, arguments)
},
})

originalXhrObject.addEventListener('load', (event) => {
if (changeResponseDone) {
doneEventHandlers.forEach((handler) => {
setTimeout(() => {
handler(event)
}, 100)

})
} else {
lastDoneEvent = event
}
})




Final Note


We have shown how to proxy the XMLHttpRequest. A different simpler approach could be by using a service worker, where proxy will not be required, and hence there is no need to handle the various event listeners, and getters/setters.



Monday, February 7, 2022

A-Symmetric Encrypt and Decrypt on Golang

 



In this post we will review the steps to use RSA encryption/decryption on GO. 


This post is about using a-symmetric encryption for encrypt/decrypt. In case of need of a-symmetric encryption for sign/verify, check this post.


We start by creation a struct to represent the encryption:


package encryption

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"hash"
"io"
"os"
"radware.com/euda/commons/global/log"
)

type Key struct {
privateKey *rsa.PrivateKey
hash hash.Hash
random io.Reader
}

func (k *Key) init() {
k.hash = sha256.New()
k.random = rand.Reader
}


We can generate a new key, which takes about 5 seconds on my desktop machine



func GenerateKey() (*Key, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, fmt.Errorf("generate key failed: %v", err)
}

k := Key{privateKey: privateKey}
k.init()
return &k, nil
}



To save the key to a file:



func (k *Key) SavePrivateKey() error {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(k.privateKey)
privateKeyBlock := pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKeyBytes,
}

var writerBuffer bytes.Buffer

err := pem.Encode(&writerBuffer, &privateKeyBlock)
if err != nil {
return fmt.Errorf("encode private key failed: %v", err)
}

fileData := writerBuffer.String()
err = os.WriteFile(Config.PrivateKeyPath, []byte(fileData), 0644)
if err != nil {
return fmt.Errorf("write file failed: %v", err)
}
return nil
}



To load the key from a file:



func LoadPrivateKey() (*Key, error) {
pemFile, err := os.ReadFile(Config.PrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("read file failed: %v", err)
}
pemBlock, _ := pem.Decode(pemFile)
privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unmarshal key failed: %v", err)
}

rsaPrivateKey,ok :=privateKey.(*rsa.PrivateKey)
if !ok{
return nil, fmt.Errorf("convert failed: %v", err)
}

k := Key{privateKey: rsaPrivateKey}
k.init()

log.Info("private key loaded %v", Config.PrivateKeyPath)
return &k, nil
}



To get the public key for sending it to other parties:



func (k *Key) GetPublicKeyPem() (string, error) {
publicKeyBytes, err := x509.MarshalPKIXPublicKey(k.privateKey.Public())
if err != nil {
return "", fmt.Errorf("marshal public key failed: %v", err)
}

publicKeyBlock := pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}

var writerBuffer bytes.Buffer

err = pem.Encode(&writerBuffer, &publicKeyBlock)
if err != nil {
return "", fmt.Errorf("encode public key failed: %v", err)
}

return writerBuffer.String(), nil
}



And finally, we can encrypt and decrypt:



func (k *Key) EncryptString(clearText string) (string, error) {
encryptedBytes, err := rsa.EncryptOAEP(
k.hash,
k.random,
&k.privateKey.PublicKey,
[]byte(clearText),
nil,
)

if err != nil {
return "", fmt.Errorf("encrypt failed: %v", err)
}
encryptedBase64 := base64.StdEncoding.EncodeToString(encryptedBytes)
return encryptedBase64, nil
}

func (k *Key) DecryptString(base64Cipher string) (string, error) {
encryptedBytes, err := base64.StdEncoding.DecodeString(base64Cipher)
if err != nil {
return "", fmt.Errorf("base64 decode failed: %v", err)
}

clearTextBytes, err := rsa.DecryptOAEP(
k.hash,
k.random,
k.privateKey,
encryptedBytes,
nil,
)
if err != nil {
return "", fmt.Errorf("decrypt failed: %v", err)
}

clearText := string(clearTextBytes)
return clearText, nil
}




Final Note


This post is part of a series of posts about encryption. The full posts list is below.




Sunday, February 6, 2022

A-Symmetric Encrypt and Decrypt on JavaScript


 


In this post we will review the steps to use RSA encryption/decryption on Javascript. We use the SubtleCrypto API, which is available on on secure context pages. Will also use some functions that are not supported on old browsers.


This post is about using a-symmetric encryption for encrypt/decrypt. In case of need of a-symmetric encryption for sign/verify, check this post.



First, to check if the page is considered a secure context, use the following:



console.log('secure context', window.isSecureContext)



The SubtleCrypto API uses ArrayBuffer for it functions, but as we want to send the data to server, will will convert it from and to a base64 string using the following helper functions:



function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return window.btoa(bytes)
}

function base64ToArrayBuffer(base64String) {
const chars = window.atob(base64String)
const arrayBuffer = new ArrayBuffer(chars.length)
const bufferView = new Uint8Array(arrayBuffer)
for (let i = 0, strLen = chars.length; i < strLen; i++) {
bufferView[i] = chars.charCodeAt(i)
}
return arrayBuffer
}


Special notes for NodeJS users

As btoa() does not exist in NodeJS, we need to use the Buffer, BUT we must specify the latin1 encoding to have the same encoding as in the browser.


NodeJS version:

function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return Buffer.from(bytes,'latin1').toString('base64')
}



We now can generate a new RSA key. This is a intensive operation, and it takes ~1 second on a desktop machine.



async function generateKey() {
return await window.crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt'],
)
}



The generated key is actually a pair that includes a public key and a private key. 


In most cases we will store the private key locally so that in case of restart, we will reuse it. To export the private key we run the following:


function convertBinaryToPem(base64Cert, label) {
var pemCert = '-----BEGIN ' + label + '-----\r\n'
var nextIndex = 0
while (nextIndex < base64Cert.length) {
if (nextIndex + 64 <= base64Cert.length) {
pemCert += base64Cert.substr(nextIndex, 64) + '\r\n'
} else {
pemCert += base64Cert.substr(nextIndex) + '\r\n'
}
nextIndex += 64
}
pemCert += '-----END ' + label + '-----\r\n'
return pemCert
}

async function exportPrivateKey(privateKey) {
const exported = await window.crypto.subtle.exportKey(
'pkcs8',
privateKey,
)
const exportedAsBase64 = arrayBufferToBase64(exported)
return convertBinaryToPem(exportedAsBase64, 'RSA PRIVATE KEY')
}


The public key can be sent to other parties. We use the export and import to do this.


const pemHeader = '-----BEGIN PUBLIC KEY-----\n'
const pemFooter = '\n-----END PUBLIC KEY-----'


function breakKeyToShortLines(publicKey) {
const maxLineLength = 64
let pemCert = ''
let nextIndex = 0
while (nextIndex < publicKey.length) {
if (nextIndex + maxLineLength <= publicKey.length) {
pemCert += publicKey.substr(nextIndex, maxLineLength) + '\n'
} else {
pemCert += publicKey.substr(nextIndex)
}
nextIndex += maxLineLength
}
return pemCert
}

async function exportSubjectPublicKey(publicKey) {
const exported = await window.crypto.subtle.exportKey(
'spki',
publicKey,
)
const exportedAsBase64 = arrayBufferToBase64(exported)
const shortLinesKey = breakKeyToShortLines(exportedAsBase64)
return `${pemHeader}${shortLinesKey}${pemFooter}`
}

function importSubjectPublicKey(pem) {
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length)
const binaryDer = base64ToArrayBuffer(pemContents)

return window.crypto.subtle.importKey(
'spki',
binaryDer,
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['encrypt'],
)
}



Finally we can use the encrypt and decrypt functions:



async function encryptMessage(publicKey, clearText) {
const encodedText = new TextEncoder().encode(clearText)
const cipherText = await window.crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
publicKey,
encodedText,
)

return arrayBufferToBase64(cipherText)
}

async function decryptMessage(privateKey, cipherBase64) {
const cipherText = base64ToArrayBuffer(cipherBase64)
const encodedText = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
privateKey,
cipherText,
)

return new TextDecoder().decode(encodedText)
}



An example for using these functions is below:



async function example() {
const keyPair = await generateKey()
console.log('key pair', keyPair)
const exportedPublicKey = await exportSubjectPublicKey(keyPair.publicKey)
console.log('exported public', exportedPublicKey)
const importedPublicKey = await importSubjectPublicKey(exportedPublicKey)
console.log('imported public', importedPublicKey)
for (let i = 0; i < 2; i++) {
const text = 'mypass123'
const encrypted = await encryptMessage(importedPublicKey, text)
console.log('encrypted', encrypted)
const decrypted = await decryptMessage(keyPair.privateKey, encrypted)
console.log('decrypted', decrypted)
}
}




Final Note


This post is part of a series of posts about encryption. The full posts list is below.









Monday, January 31, 2022

Copy to Clipboard



 

In this post we will review a full working implementation of a "copy to clipboard" in react. This includes the javascript code to handle the copy, the react code to handle the show/hide of the "text copied" message upon 2 seconds timeout, and a css styling for the message on top.


We start with the react & redux template:


npx create-react-app my-app --template redux


Now update the application file to hold the following:


App.js

import React, {useState} from 'react'
import './App.css'

function App() {
const [show, setShow] = useState(false)
const text = 'this is my text, click on the copy icon to copy it.'

function click() {
navigator.clipboard.writeText(text).then(() => {
setShow(true)
setTimeout(() => {
setShow(false)
}, 2000)
})
}

let copiedMessage = null
if (show) {
copiedMessage = (
<div
className="copy-message"
>
Copied to clipboard
</div>

)
}
return (
<div className="root">
<div className="header">Right below, we have the example.</div>
<div className="container">
<div className="copy-icon" onClick={click}>{copiedMessage}</div>
<div className="text">{text}</div>
</div>
<div className="header">The end of the example.</div>
</div>
)
}

export default App



The application includes an optional div which contains the "copied to clipboard" message. This div is displayed for 2 seconds after clicking the copy to clipboard icon. To implement this, we use react's useState function.


The actual copy of the text is done using the navigator.clipboard. In case of need of old browsers backward compatibility, use different methods, and described here.


We include the stylesheet to hold the following:


App.css

.root{
font-size: 30px;
}
.container {
display: flex;
flex-direction: row;
}

.copy-icon {
background: url('copy.png') center;
background-size: cover;
margin: 5px;
height: 25px;
width: 25px;
padding: 0;
z-index: 99;
cursor: pointer;
}

.header{
margin: 30px;
}

.copy-message {
font-size: 25px;
float: left;
border: 1px solid black;
background: white;
width: 250px;
height: 30px;
margin-left: 20px;
margin-top: 20px;
}



We use flex to display the icon and the text side by side.

To ensure the "copied to clipboard" message is on top, we use both z-index and floating mode.





Sunday, January 23, 2022

Using Plotly Scatter Plot in React

 


In this post we will review usage of plotly based scatter plot in a react application.


We start by creating the react&redux template:


npx create-react-app my-app --template redux



Next we add the the react plotly library:


npm i -s react-plotly.js



And update the application to show our scatter plot:



import React from 'react'
import './App.css'
import createPlotlyComponent from 'react-plotly.js/factory'

function App() {
const Plot = createPlotlyComponent(window.Plotly)

return (
<div className="App">
<header className="App-header">
<Plot
data={[
{
x: [1, 2, 3, 1],
y: [1, 2, 3, 5],
text: ['p1', 'p2', 'p3', 'p4'],
type: 'scatter',
mode: 'lines+markers',
marker: {
color: 'green',
size: 10,
},
},
]}
layout={{
width: 800,
height: 600,
}
}
/>
</header>
</div>
)
}

export default App



Notice that we do not use the plotly component directly. This is since we want to reduce the size of the compiled application bundle. Instead of including the entire bundle of plotly and d3, we will download it from the CDN using the following update in the application HTML file. The npm will still download all of the bundle dependencies, but the webpack will include only the used resources, and hence the plotly and the d3 will not be included in the bundle.


index.html

<script crossorigin src="https://cdn.plot.ly/plotly-latest.min.js"></script>


And the result is a nice scatter plot:




Final Note


We have reviewed the process of adding a scatter plot to a react application. Scatter plot can be used to show, for example, location overtime. We have specifically used it to show mouse movement.