1

I'm trying to use crypto to hash strings in a Vue 3 app.

async function hash (token) {
    const data = new TextEncoder().encode(token)
    const byteHash = await crypto.subtle.digest("SHA-256", data)
    //                            ^ the below error is thrown here

    const arrayHash = Array.from(new Uint8Array(byteHash))
    const hexHash = arrayHash.map(b => b.toString(16).padStart(2, '0')).join('').toLocaleUpperCase()

    return hexHash
}

From my understanding, crypto is available in the browser nowadays, so it needs no browserify replacement.

Nevertheless, I'm getting the following error in my browser console:

Error: Module "crypto" has been externalized for browser compatibility. Cannot access "crypto.subtle" in client code.

I interpret this as "Vite is configured to externalize the crypto module in the build process". But I can see no such setting in my vite.config.js:

// Plugins:
import vue from '@vitejs/plugin-vue'
import vuetify from 'vite-plugin-vuetify'

// Utilies:
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
    vuetify({
      autoImport: true
    })
  ],
  define: { 'process.env': {} },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
    extensions: ['.js', '.json', '.jsx', '.mjs', '.ts', '.tsx', '.vue']
  },
  server: {
    port: 3000
  },
  test: {
    setupFiles: ['../vuetify.config.js'],
    deps: {
      inline: ['vuetify']
    },
    globals: true
  }
})

Are there any "baked in" Vite default settings, that could cause this? Is this configured somewere else? How can I fix this issue and use the crypto module in my app?

Fred
  • 1,103
  • 2
  • 14
  • 35

1 Answers1

1

The issue lies in the fact that NodeJS and the Browser both have a module called crypto (that implements the webcrypto standard) that are compatible, but need to be accessed differently, because in the browser it is provided by the window context that does not exist in NodeJS.

You don't see the difference if you are working directly in the browser, as window is the default context.

But Vite is working in the NodeJS context, it (correctly) decides that this module is not available as crypto in the browser and thus throws the error. It does not know/care that this module exists in the browser as well but as window.crypto.

Maybe this can be configured in vite.config.js somehow, but I am not very firm with it.

I came up with the following solution instead, which works in both environments:

function getCrypto() {
  try {
    return window.crypto;
  } catch {
    return crypto;
  }
}
async function hash(token) {
  const compatibleCrypto = getCrypto();

  const data = new TextEncoder().encode(token);
  const byteHash = await compatibleCrypto.subtle.digest('SHA-256', data);

  const arrayHash = Array.from(new Uint8Array(byteHash));
  const hexHash = arrayHash
    .map(b => b.toString(16).padStart(2, '0'))
    .join('')
    .toLocaleUpperCase();

  return hexHash;
}

This function works in both environments now.

Fred
  • 1,103
  • 2
  • 14
  • 35