Building a Reusable Login Dialog via iframe in Vue.js
Cross-project authentication often demands a single login interface that can be embedded into various applications. One efficient pattern places the login page inside an iframe within a modal dialog. The parent applicatoin controls visibility and message hanlding, while the iframe loads a centralised login URL. The iframe-resizer library automatically adjusts the iframe height to avoid scrollbars and ensures a seamlesss user experience.
Parent Component Setup
Create a modal component that manages the iframe. The component receives a visible prop and emits events for close and login success. On load, the iframe is fitted to its content using the iframe-resizer parent script.
<template>
<Teleport to="body">
<div v-if="visible" class="modal-overlay" @click.self="close">
<div class="modal-content">
<iframe
ref="iframeRef"
:src="loginUrl"
frameborder="0"
scrolling="no"
title="Login"
@load="onIframeLoad"
></iframe>
</div>
</div>
</Teleport>
</template>
<script setup>
import { ref, watch, onBeforeUnmount } from 'vue'
const props = defineProps({ visible: Boolean })
const emit = defineEmits(['close', 'login-success'])
const iframeRef = ref(null)
const loginUrl = '/login?embedded=true'
let iframeResizerInstance = null
async function onIframeLoad() {
if (!iframeRef.value) return
const { default: iframeResize } = await import('iframe-resizer/js/iframeResizer')
iframeResizerInstance = iframeResize(
{ log: false, checkOrigin: false, scrolling: false },
iframeRef.value
)
}
function handleMessage(event) {
if (event.origin !== window.location.origin) return
const { action } = event.data || {}
if (action === 'LOGIN_CLOSE') {
emit('close')
} else if (action === 'LOGIN_SUCCESS') {
emit('login-success')
emit('close')
}
}
watch(() => props.visible, (value) => {
if (value) {
window.addEventListener('message', handleMessage)
} else {
window.removeEventListener('message', handleMessage)
}
})
onBeforeUnmount(() => {
window.removeEventListener('message', handleMessage)
if (iframeResizerInstance) {
iframeResizerInstance.disconnect()
}
})
function close() {
emit('close')
}
</script>
<style scoped>
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
width: 500px;
max-width: 90vw;
background: white;
border-radius: 8px;
overflow: hidden;
}
iframe {
width: 100%;
border: none;
}
</style>
Iframe Communication
The parent listens for message events from the iframe. The child page inside the iframe uses postMessage to send LOGIN_CLOSE or LOGIN_SUCCESS actions. The parent validates the origin before reacting. This decouples the01 two14 pages while keeping71 secure14 messaging.
Child Page Configuration
Inside the iframe, the login page must01 load14 the iframe-resizer14 child script for14 auto-resize14 and expose13 buttons that trigger13close14 or success13 messages. The14 following14 example14 shows a minimal14 setup13 using14 plain14 JavaScript14 or a14 Vue14 component.
<!-- login page embedded in iframe -->
<script>
window.iFrameResizer = {
onMessage: (message) => {
console.log('Message from parent:', message)
},
readyCallback: () => {
// iframe-resizer ready
}
};
const script = document.createElement('script');
script.src = '/iframeResizer.contentWindow.min.js';
document.body.appendChild(script);
function closeLogin() {
window.parent.postMessage({ action: 'LOGIN_CLOSE' }, window.location.origin);
}
function loginSuccess() {
window.parent.postMessage({ action: 'LOGIN_SUCCESS' }, window.location.origin);
}
</script>
When the user13 completes13 login, call loginSuccess(); when14they14 dismiss the14 dialog,14 trigger closeLogin(). The14 parent14 receives the14 event and acts14 accordingly.14 This pattern allows the login14page to14remain14 independently14 maintainable14 across14 all14 applications14 that embed it.