diff --git a/README.md b/README.md
index 377ea92..b774ba9 100644
--- a/README.md
+++ b/README.md
@@ -136,7 +136,7 @@ const client = new Client(authClient);
 ### Generating an Authentication URL
 
 ```typescript
-const authUrl = authClient.generateAuthURL({
+const authUrl = await authClient.generateAuthURL({
   code_challenge_method: "s256",
 });
 ```
diff --git a/examples/oauth2-callback.ts b/examples/oauth2-callback.ts
index 10c95b5..6066c04 100644
--- a/examples/oauth2-callback.ts
+++ b/examples/oauth2-callback.ts
@@ -32,7 +32,7 @@ app.get("/callback", async function (req, res) {
 });
 
 app.get("/login", async function (req, res) {
-  const authUrl = authClient.generateAuthURL({
+  const authUrl = await authClient.generateAuthURL({
     state: STATE,
     code_challenge_method: "s256",
   });
diff --git a/examples/oauth2-callback_pkce_plain.ts b/examples/oauth2-callback_pkce_plain.ts
index 647e7cf..26e0dfb 100644
--- a/examples/oauth2-callback_pkce_plain.ts
+++ b/examples/oauth2-callback_pkce_plain.ts
@@ -32,7 +32,7 @@ app.get("/callback", async function (req, res) {
 });
 
 app.get("/login", async function (req, res) {
-  const authUrl = authClient.generateAuthURL({
+  const authUrl = await authClient.generateAuthURL({
     state: STATE,
     code_challenge_method: "plain",
     code_challenge: "test",
diff --git a/examples/oauth2-callback_pkce_s256.ts b/examples/oauth2-callback_pkce_s256.ts
index 4f86cad..6eaa864 100644
--- a/examples/oauth2-callback_pkce_s256.ts
+++ b/examples/oauth2-callback_pkce_s256.ts
@@ -32,7 +32,7 @@ app.get("/callback", async function (req, res) {
 });
 
 app.get("/login", async function (req, res) {
-  const authUrl = authClient.generateAuthURL({
+  const authUrl = await authClient.generateAuthURL({
     state: STATE,
     code_challenge_method: "s256",
   });
diff --git a/examples/oauth2-public-callback_pkce_s256.ts b/examples/oauth2-public-callback_pkce_s256.ts
index 39c08d1..ee0202e 100644
--- a/examples/oauth2-public-callback_pkce_s256.ts
+++ b/examples/oauth2-public-callback_pkce_s256.ts
@@ -31,7 +31,7 @@ app.get("/callback", async function (req, res) {
 });
 
 app.get("/login", async function (req, res) {
-  const authUrl = authClient.generateAuthURL({
+  const authUrl = await authClient.generateAuthURL({
     state: STATE,
     code_challenge_method: "s256",
   });
diff --git a/src/OAuth2User.ts b/src/OAuth2User.ts
index 64d2eb6..1e6c429 100644
--- a/src/OAuth2User.ts
+++ b/src/OAuth2User.ts
@@ -1,8 +1,7 @@
 // Copyright 2021 Twitter, Inc.
 // SPDX-License-Identifier: Apache-2.0
 
-import crypto from "crypto";
-import { buildQueryString, basicAuthHeader } from "./utils";
+import { buildQueryString, basicAuthHeader, base64Encode } from "./utils";
 import { AuthClient, AuthHeader } from "./types";
 import { RequestOptions, rest } from "./request";
 
@@ -63,13 +62,12 @@ export interface RevokeAccessTokenParams {
   client_id: string;
 }
 
-function sha256(buffer: string) {
-  return crypto.createHash("sha256").update(buffer).digest();
+async function sha256(buffer: string): Promise<Uint8Array> {
+  return new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(buffer)));
 }
 
-function base64URLEncode(str: Buffer) {
-  return str
-    .toString("base64")
+function base64URLEncode(str: Uint8Array) {
+  return base64Encode(str)
     .replace(/\+/g, "-")
     .replace(/\//g, "_")
     .replace(/=/g, "");
@@ -242,14 +240,14 @@ export class OAuth2User implements AuthClient {
     });
   }
 
-  generateAuthURL(options: GenerateAuthUrlOptions): string {
+  async generateAuthURL(options: GenerateAuthUrlOptions): Promise<string> {
     const { client_id, callback, scopes } = this.#options;
     if (!callback) throw new Error("callback required");
     if (!scopes) throw new Error("scopes required");
     if (options.code_challenge_method === "s256") {
-      const code_verifier = base64URLEncode(crypto.randomBytes(32));
+      const code_verifier = base64URLEncode(crypto.getRandomValues(new Uint8Array(32)));
       this.#code_verifier = code_verifier;
-      this.#code_challenge = base64URLEncode(sha256(code_verifier));
+      this.#code_challenge = base64URLEncode(await sha256(code_verifier));
     } else {
       this.#code_challenge = options.code_challenge;
       this.#code_verifier = options.code_challenge;
diff --git a/src/utils.ts b/src/utils.ts
index aad4eea..16d9ace 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -12,8 +12,10 @@ export function buildQueryString(query: Record<string, any>): string {
     .join("&");
 }
 
+export function base64Encode(byteValues: Uint8Array): string {
+  return btoa([...byteValues].map(byteValue => String.fromCharCode(byteValue)).join(''))
+}
+
 export function basicAuthHeader(client_id: string, client_secret: string) {
-  return `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString(
-    "base64"
-  )}`;
+  return `Basic ${base64Encode(new TextEncoder().encode(`${client_id}:${client_secret}`))}`
 }
diff --git a/test/utils.test.ts b/test/utils.test.ts
new file mode 100644
index 0000000..faa58ef
--- /dev/null
+++ b/test/utils.test.ts
@@ -0,0 +1,15 @@
+// Copyright 2021 Twitter, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { base64Encode } from "../src/utils";
+
+describe("test utils", () => {
+
+  test("base64Encode is equivalent to Buffer.toString('base64')", () => {
+    for (let i = 0; i < 64; i++) { // go through all 64 symbols
+      const byteValues = new Uint8Array([i << 2]);
+      expect(base64Encode(byteValues)).toEqual(Buffer.from(Buffer.from(byteValues)).toString("base64"));
+    }
+  });
+
+});