Frontend Integration

Vue Integration

Add the EmbedAuth iframe to a Vue 3 app with a Composition-API component that manages the postMessage listener and auto-resizes the frame.

1 min readUpdated October 5, 2026

In Vue 3 the embed is a single-file component. The message listener is registered in onMounted and removed in onUnmounted, mirroring the same lifecycle discipline every EmbedAuth integration needs.

A composable

Keep the listener logic in a composable so it is reusable and testable:

// useEmbedAuth.ts
import { onMounted, onUnmounted } from 'vue';

// The embed URL on embedauth.com redirects to the widget's copy on the CDN,
// so its messages come from the CDN's origin.
const EMBED_ORIGIN = 'https://cdn.embedauth.com';

type AuthSuccess = {
  token: string;
  refreshToken: string;
  user: { id: string; email: string; name?: string };
};

export function useEmbedAuth(onSuccess: (result: AuthSuccess) => void) {
  function handleMessage(event: MessageEvent) {
    if (event.origin !== EMBED_ORIGIN) return;
    if (event.data?.type === 'authframe:auth-success') {
      onSuccess(event.data.data);
    }
  }
  onMounted(() => window.addEventListener('message', handleMessage));
  onUnmounted(() => window.removeEventListener('message', handleMessage));
}

The component

<script setup lang="ts">
import { ref } from 'vue';
import { useEmbedAuth } from './useEmbedAuth';

const props = defineProps<{ mode?: 'signin' | 'signup' | 'forgot-password' }>();

const CLIENT_ID = 'YOUR_CLIENT_ID';
const height = ref(600);

useEmbedAuth(async ({ token }) => {
  await fetch('/api/session', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token }),
  });
  window.location.assign('/dashboard');
});

const src = `https://embedauth.com/embed/${CLIENT_ID}?mode=${props.mode ?? 'signin'}`;
</script>

<template>
  <iframe
    title="Sign in"
    :src="src"
    :height="height"
    width="420"
    style="border: none; width: 100%; max-width: 420px"
  />
</template>

Auto-resizing

To grow the frame with its content, also handle authframe:resize and bind the height:

function handleMessage(event: MessageEvent) {
  if (event.origin !== EMBED_ORIGIN) return;
  if (event.data?.type === 'authframe:resize') {
    height.value = event.data.data.height;
  }
}

Bind height to the iframe's :height attribute and the form will never show an inner scrollbar as it switches between sign-in, sign-up, and error states.

Nuxt

In Nuxt, keep the embed inside a <ClientOnly> wrapper (or a .client.vue component) so it never runs during SSR — window does not exist on the server. Verify the token in a server route (server/api/session.post.ts) exactly as shown in the Next.js guide; the verification code is identical.

Next steps