How to Encrypt a URL Parameter in Next.js using CryptoJS
Suppose we have a page in our Next.js application under /pages/view/[id].jsx
.
Let’s send one of our users an email to view a product with a link to mysite.com/view/5
.
We’ve immediately given away sensitive information regarding where this product lives in our database (i.e. it has an ID of 5
).
We want to use this id
URL parameter to query our database, but we don’t want it to be exposed on the client-side.
In these scenarios, it’s useful to know how to hide our sensitive information, especially through encryption.
CryptoJS does all of the heavy lifting for us and bundles it into a nice JavaScript library.
1. Install crypto-js
The first thing we have to do is install cryto-js
as well as its types library (if we’re using TypeScript).
npm i crypto-js --save
npm i @types/crypto-js --save
2. Encrypt sensitive information using CryptoJS
In our database, we store our unique IDs. It could be a simple auto-incremented index or a UUID or some custom solution we’ve thought up.
However, we only want users of our product to see the encrypted data.
So, we want to encrypt any sensitive data that will be visible to our users.
CryptoJS makes this very easy. We’ll use AES encryption, which is an example of symmetric encryption, where we will need the same key to encrypt and decrypt our data.
Let’s say our key is secretPassphrase
.
import AES from 'crypto-js/aes';
import { enc } from 'crypto-js';
const encryptId = (str) => {
const ciphertext = AES.encrypt(str, 'secretPassphrase');
return encodeURIComponent(ciphertext.toString());
}
Ideally, our secret key
secretPassphrase
is stored in an environment variable.
2.1. AES.encrypt()
After we perform the encryption, we’ll get a long string that might look something like this.
const ciphertext = AES.encrypt(str, 'secretPassphrase');
// U2FsdGVkX18EsY9CFfTXgHZI/GM6u7251m9+CjFeiVBEfYZZhOhJBPYrfS/KR5BQk1YOYyIx7wCkOzLqTuTzLA==
2.2. encodeURIComponent()
Before sending this encrypted text into a URL string, we’ll want to encode it using encodeURIComponent()
. Without this step, the server will process the slashes /
as a new sub route and return a 404
.
encodeURIComponent(ciphertext.toString());
// U2FsdGVkX18EsY9CFfTXgHZI%2FGM6u7251m9%2BCjFeiVBEfYZZhOhJBPYrfS%2FKR5BQk1YOYyIx7wCkOzLqTuTzLA%3D%3D
Finally, we can use this encrypted string in a URL and send it off to users.
const getUrl = (id) => {
const encryptedId = encryptId(id)
return `mysite.com/view/${encryptedId}`;
}
3. Decrypt sensitive information using CryptoJS
Great, now we have a user opening a browser to /pages/view/[id].jsx
, and we need to figure out how to handle the encrypted ID.
Naturally, we’ll create a utility function to decrypt encrypted strings.
const decryptId = (str) => {
const decodedStr = decodeURIComponent(str);
return AES.decrypt(decodedStr, 'secretPassphrase').toString(enc.Utf8);
}
We’ll be performing the operations in reverse. Instead of encrypting and then encoding, we’ll decode then decrypt.
Note that we need to use the same secret passphrase to properly decrypt this data.
If someone gets a hold of that passphrase, then they will be able to decrypt any data encrypted using that passphrase (given that we’re using AES encryption).
4. Perform decryption on the server side
We’ll wrap this all up by using our new decryptId()
function in getServerSideProps()
.
Before the page is rendered, we’ll grab the ID, decrypt it, query our database using that ID, then return the data to the client-side.
export default View = ({ product, encryptedId }) => {
return (/* Render product info */);
}
export const getServerSideProps = async ({ params, res }) => {
const { id: encryptedId } = params;
const decryptedId = decryptId(encryptedId);
const product = queryDatabaseUsingId(decryptedId);
return {
props: {
product: JSON.parse(JSON.stringify(product)),
encryptedId: encodeURIComponent(encryptedId)
},
};
};
If we want to using this encryptedId
again on the page (e.g. generate another URL using this ID), then we’ll have to encode it again before passing it into a URL.