I am working on a project using Laravel, Inertia.js, and Vue 3, and I want to implement two-factor authentication (2FA) for user login. My setup includes a MySQL database with a users table that contains a column 2fa_enabled to indicate whether 2FA is required for a user.
Here is the workflow I would like to achieve:
I've encountered some issues during implementation, and I'm not sure how to properly set up the verification code generation and handling process. I've tried using Firebase for sending SMS codes and handling ReCaptcha verification, but I'm running into the following issues:
Uncaught (in promise) ReferenceError: Firebase is not defined Recaptcha verification failures
Handling code verification in my Vue 3 component
I would appreciate any guidance on how to:
Here is how I'm setting up the ReCaptcha verifier and Firebase initialization:
import { initializeApp } from 'firebase/app';
import { getAuth, RecaptchaVerifier } from 'firebase/auth';
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
measurementId: "YOUR_MEASUREMENT_ID"
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const recaptchaContainer = document.getElementById('recaptcha-container');
const recaptchaVerifier = new RecaptchaVerifier(recaptchaContainer, {
size: 'invisible',
callback: (response) => {
console.log('reCAPTCHA resolved');
},
'expired-callback': () => {
console.log('reCAPTCHA expired');
}
}, auth);
async function getCode(phoneNumber) {
try {
const confirmationResult = await auth.signInWithPhoneNumber(phoneNumber, recaptchaVerifier);
console.log('Code sent:', confirmationResult);
} catch (error) {
console.error('Error during sign-in with phone number:', error);
console.error('Error code:', error.code);
}
}
// Example usage
getCode('+91112223334');
i follow Firebase Official Documents, so i found some mistakes, now it's work fine.
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber} from "firebase/auth";
initializeApp(firebaseConfig);
const auth = getAuth();
onMounted(() => {
const recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
in this line
<script setup>
import { initializeApp } from 'firebase/app';
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from "firebase/auth";
import { computed, ref, onMounted } from 'vue';
import { router } from "@inertiajs/vue3";
const props = defineProps({
phoneNumber: {
type: String,
required: true,
},
});
const verificationCode = ref('');
let confirmationResult = null;
const otpPart1 = ref('');
const otpPart2 = ref('');
const otpPart3 = ref('');
const otpPart4 = ref('');
const otpPart5 = ref('');
const otpPart6 = ref('');
const firebaseConfig = {
apiKey: "****",
authDomain: "****.firebaseapp.com",
projectId: "****",
storageBucket: "****.appspot.com",
messagingSenderId: "****",
appId: "5:****:web:****",
measurementId: "****"
};
initializeApp(firebaseConfig);
const auth = getAuth();
onMounted(() => {
const recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
size: 'invisible',
callback: (response) => {
console.log('reCAPTCHA resolved');
},
'expired-callback': () => {
console.log('reCAPTCHA expired, please resolve reCAPTCHA again.');
}
}, auth);
window.recaptchaVerifier = recaptchaVerifier;
sendVerificationCode(props.phoneNumber);
});
function sendVerificationCode(phoneNumber) {
const appVerifier = window.recaptchaVerifier;
signInWithPhoneNumber(auth, phoneNumber, appVerifier)
.then((result) => {
confirmationResult = result;
})
.catch((error) => {
console.error('Error sending verification code:', error);
});
}
function handleFormSubmit() {
const otpCode = `${otpPart1.value}${otpPart2.value}${otpPart3.value}${otpPart4.value}${otpPart5.value}${otpPart6.value}`;
verifyCode(otpCode);
}
function verifyCode(verificationCode) {
if (confirmationResult) {
confirmationResult.confirm(verificationCode)
.then((result) => {
router.post(route('admin.sendverificationcode'), { 'verificationCode': verificationCode })
})
.catch((error) => {
console.error('Error verifying code:', error);
});
} else {
console.error('Confirmation result not available.');
}
}
const maskedPhoneNumber = computed(() => {
const phoneNumberLength = props.phoneNumber.length;
const digitsToShow = 4;
const startIndex = phoneNumberLength - digitsToShow;
const maskedPart = '*'.repeat(startIndex);
const unmaskedPart = props.phoneNumber.slice(startIndex);
return maskedPart + unmaskedPart;
});
</script>
<style scoped>
.login-card .card-body {
padding: 2.5rem !important;
}
.signIn-heading {
margin-bottom: 20px;
}
.twoStepArea {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
}
.three-input-groups {
display: flex;
}
.three-input-groups input[type="number"] {
width: 36px;
height: 49px;
border-radius: 0;
border: 1px solid #e2e2e2;
outline: 0;
padding: 10px 5px;
text-align: center;
}
.three-input-groups input:first-child {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.three-input-groups input[type="number"]+input[type="number"] {
border-left: 0;
}
.three-input-groups input:last-child {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.twoStep-spacer {
font-size: 34px;
font-weight: 700;
color: #e2e2e2;
line-height: 1;
margin-top: -10px;
}
</style>
<template>
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6 col-xl-5">
<div class="card mt-4 login-card">
<div class="card-body p-4">
<div>
<h5 class="text-primary signIn-heading">Two-step authentication</h5>
<p>Enter the 6-digit verification code sent to <b>your mobile
number {{ maskedPhoneNumber }}.</b></p>
</div>
<div>
<form @submit.prevent="handleFormSubmit">
<div id="recaptcha-container"></div>
<div class="otp-container mt-4">
<div class="twoStepArea">
<div class="three-input-groups">
<input type="number" v-model="otpPart1" maxlength="1"
class="otp-input" />
<input type="number" v-model="otpPart2" maxlength="1"
class="otp-input" />
<input type="number" v-model="otpPart3" maxlength="1"
class="otp-input" />
</div>
<div class="twoStep-spacer">-</div>
<div class="three-input-groups">
<input type="number" v-model="otpPart4" maxlength="1"
class="otp-input" />
<input type="number" v-model="otpPart5" maxlength="1"
class="otp-input" />
<input type="number" v-model="otpPart6" maxlength="1"
class="otp-input" />
</div>
</div>
</div>
<div class="mt-4">
<button class="btn btn-primary w-100" type="submit"
>Verify Phone Number</button>
</div>
</form>
</div>
</div>
<!-- end card body -->
</div>
<!-- end card -->
</div>
</div>
</template>