diff --git a/.circleci/config.yml b/.circleci/config.yml
index eb9ae844c..819cff015 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,9 +7,9 @@ jobs:
     parameters:
       node-version:
         type: string
-        default: "12"
+        default: "14.20"
     docker:
-      - image: circleci/node:<< parameters.node-version >>
+      - image: cimg/node:<< parameters.node-version >>
     environment:
       LANG: en_US.UTF-8
     steps:
@@ -25,38 +25,15 @@ jobs:
           key: yarn-packages-{{ checksum "yarn.lock" }}
           paths:
             - ~/.cache/yarn
+      - run:
+          name: ESLint
+          command: yarn lint
+      - run:
+          name: Tests
+          command: yarn test:ci
       - when:
           condition:
-            # ESLint has dropped support for version 8 and 10.
-            and:
-              - not:
-                  equal: [ "8", << parameters.node-version >> ]
-              - not:
-                  equal: [ "10", << parameters.node-version >> ]
-          steps:
-            - run:
-                name: ESLint
-                command: yarn lint
-      - when:
-          condition:
-            # mocha@>=6 doesn't support node 8 so we use mocha@6 with npx to run the tests
-            equal: ["8", << parameters.node-version >>]
-          steps:
-            - run:
-                name: Tests
-                command: "npx mocha@6 --reporter spec './test/**/*.tests.js'"
-      - when:
-          condition:
-            and:
-              - not:
-                  equal: ["8", << parameters.node-version >>]
-          steps:
-            - run:
-                name: Tests
-                command: yarn test:ci
-      - when:
-          condition:
-            equal: [ "12", << parameters.node-version >> ]
+            equal: [ "14.20", << parameters.node-version >> ]
           steps:
             - codecov/upload
 workflows:
@@ -65,7 +42,7 @@ workflows:
       - build-and-test:
           matrix:
             parameters:
-              node-version: ["8", "10", "12", "14", "16"]
+              node-version: ["14.20", "16.18", "18.12"]
       - ship/node-publish:
           pkg-manager: yarn
           requires:
diff --git a/package.json b/package.json
index e24af5d95..b384a6a73 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
     "url": "https://github.com/auth0/node-auth0"
   },
   "engines": {
-    "node": ">=8.3.0"
+    "node": ">=14"
   },
   "keywords": [
     "auth0",
@@ -35,7 +35,7 @@
   "dependencies": {
     "axios": "^0.27.2",
     "form-data": "^3.0.1",
-    "jsonwebtoken": "^8.5.1",
+    "jsonwebtoken": "^9.0.0",
     "jwks-rsa": "^1.12.1",
     "lru-memoizer": "^2.1.4",
     "rest-facade": "^1.16.3",
@@ -59,7 +59,6 @@
     "mocha-junit-reporter": "^2.0.0",
     "nock": "^13.2.7",
     "nyc": "^14.1.1",
-    "pem": "^1.14.2",
     "prettier": "2.4.1",
     "pretty-quick": "^1.11.1",
     "proxyquire": "^2.1.3",
diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js
index 4c290fcc5..0ad232939 100644
--- a/test/auth/oauth-with-idtoken-validation.tests.js
+++ b/test/auth/oauth-with-idtoken-validation.tests.js
@@ -3,7 +3,7 @@ const sinon = require('sinon');
 const proxyquire = require('proxyquire');
 const jwt = require('jsonwebtoken');
 const jwksClient = require('jwks-rsa');
-const pem = require('pem');
+const crypto = require('crypto');
 
 // Constants.
 const DOMAIN = 'tenant.auth0.com';
@@ -15,17 +15,14 @@ const PARAMS = { params: true };
 const DATA = { data: true };
 
 const createCertificate = function (cb) {
-  pem.createCertificate({ days: 1, selfSigned: true }, (err, keys) => {
-    if (err) {
-      throw err;
-    }
-    pem.getPublicKey(keys.certificate, (e, p) => {
-      if (e) {
-        throw e;
-      }
-      cb({ serviceKey: keys.serviceKey, certificate: keys.certificate, publicKey: p.publicKey });
-    });
+  const { publicKey: pubRsaKey, privateKey: privRsaKey } = crypto.generateKeyPairSync('rsa', {
+    modulusLength: 2048,
   });
+
+  const publicKey = pubRsaKey.export({ type: 'spki', format: 'pem' });
+  const serviceKey = privRsaKey.export({ type: 'pkcs8', format: 'pem' });
+
+  cb({ serviceKey, publicKey });
 };
 
 describe('OAUthWithIDTokenValidation', () => {
diff --git a/yarn.lock b/yarn.lock
index ca517c3b9..52c214967 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1662,11 +1662,6 @@ es6-error@^4.0.1:
   resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz"
   integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
 
-es6-promisify@^6.0.0:
-  version "6.1.1"
-  resolved "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz"
-  integrity sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==
-
 escalade@^3.1.1:
   version "3.1.1"
   resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz"
@@ -2951,6 +2946,16 @@ jsonwebtoken@^8.5.1:
     ms "^2.1.1"
     semver "^5.6.0"
 
+jsonwebtoken@^9.0.0:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d"
+  integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==
+  dependencies:
+    jws "^3.2.2"
+    lodash "^4.17.21"
+    ms "^2.1.1"
+    semver "^7.3.8"
+
 just-extend@^4.0.2:
   version "4.2.1"
   resolved "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz"
@@ -3281,7 +3286,7 @@ md5.js@^1.3.4:
     inherits "^2.0.1"
     safe-buffer "^5.1.2"
 
-md5@^2.1.0, md5@^2.2.1:
+md5@^2.1.0:
   version "2.3.0"
   resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz"
   integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
@@ -3772,11 +3777,6 @@ os-homedir@^1.0.1:
   resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz"
   integrity "sha1-/7xJiDNuDoM94MFox+8VISGqf7M= sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ=="
 
-os-tmpdir@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
-  integrity "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="
-
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz"
@@ -4022,16 +4022,6 @@ pbkdf2@^3.0.3:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
-pem@^1.14.2:
-  version "1.14.6"
-  resolved "https://registry.npmjs.org/pem/-/pem-1.14.6.tgz"
-  integrity sha512-I5GKUer2PPv5qzUfxaZ6IGRkhp+357Kyv2t1JJg9vP8hGGI13qU34N2QupmggbpIZGPuudH0jn8KU5hjFpPk3g==
-  dependencies:
-    es6-promisify "^6.0.0"
-    md5 "^2.2.1"
-    os-tmpdir "^1.0.1"
-    which "^2.0.2"
-
 picomatch@^2.0.4, picomatch@^2.2.1:
   version "2.3.1"
   resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
@@ -4522,6 +4512,13 @@ semver@^7.3.2, semver@^7.3.5:
   dependencies:
     lru-cache "^6.0.0"
 
+semver@^7.3.8:
+  version "7.3.8"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+  dependencies:
+    lru-cache "^6.0.0"
+
 sentence-case@^1.1.1, sentence-case@^1.1.2:
   version "1.1.3"
   resolved "https://registry.npmjs.org/sentence-case/-/sentence-case-1.1.3.tgz"
@@ -5371,7 +5368,7 @@ which-module@^2.0.0:
   resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz"
   integrity "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
 
-which@2.0.2, which@^2.0.1, which@^2.0.2:
+which@2.0.2, which@^2.0.1:
   version "2.0.2"
   resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
   integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==