Skip to content

Fix 2FA CSRF Token Issues #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: feature/2fa
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 52 additions & 69 deletions resources/js/hooks/use-two-factor-auth.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import axios, { AxiosError } from 'axios';

interface EnableResponse {
qrCode: string;
Expand All @@ -16,15 +17,6 @@ interface RecoveryCodesResponse {
}

export function useTwoFactorAuth(initialConfirmed: boolean, initialRecoveryCodes: string[]) {
const csrfToken =
document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';

const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest',
};

const [confirmed, setConfirmed] = useState(initialConfirmed);
const [qrCodeSvg, setQrCodeSvg] = useState('');
Expand All @@ -46,20 +38,19 @@ export function useTwoFactorAuth(initialConfirmed: boolean, initialRecoveryCodes

const enable = async () => {
try {
const response = await fetch(route('two-factor.enable'), {
method: 'POST',
headers,
});

if (response.ok) {
const data: EnableResponse = await response.json();
setQrCodeSvg(data.qrCode);
const response = await axios.post(route('two-factor.enable'));

const data: EnableResponse = response.data;
setQrCodeSvg(data.qrCode);
setSecretKey(data.secret);
} else {
console.error('Error enabling 2FA:', response.statusText);
}
} catch (error) {
console.error('Error enabling 2FA:', error);
let errorMessage = 'Invalid verification code';
if (error instanceof AxiosError && error.response?.data) {
errorMessage = error.response.data.message || errorMessage;
}
setPasscode('');
setError(errorMessage);
}
};

Expand All @@ -69,71 +60,63 @@ export function useTwoFactorAuth(initialConfirmed: boolean, initialRecoveryCodes
const formattedCode = passcode.replace(/\s+/g, '').trim();

try {
const response = await fetch(route('two-factor.confirm'), {
method: 'POST',
headers,
body: JSON.stringify({ code: formattedCode }),
});

if (response.ok) {
const responseData: ConfirmResponse = await response.json();
if (responseData.recovery_codes) {
setRecoveryCodesList(responseData.recovery_codes);
}

setConfirmed(true);
setVerifyStep(false);
setShowModal(false);
setShowingRecoveryCodes(true);
setPasscode('');
setError('');
} else {
const errorData = await response.json();
console.error('Verification error:', errorData.message);
setError(errorData.message || 'Invalid verification code');
setPasscode('');
const response = await axios.post(route('two-factor.confirm'), { code: formattedCode });

const responseData: ConfirmResponse = response.data;
if (responseData.recovery_codes) {
setRecoveryCodesList(responseData.recovery_codes);
}

setConfirmed(true);
setVerifyStep(false);
setShowModal(false);
setShowingRecoveryCodes(true);
setPasscode('');
setError('');

} catch (error) {
console.error('Error confirming 2FA:', error);
setError('An error occurred while confirming 2FA');
let errorMessage = 'An error occurred while confirming 2FA';
if (error instanceof AxiosError && error.response?.data) {
errorMessage = error.response.data.message || errorMessage;
}
setError(errorMessage);
setPasscode('');
}
};

const regenerateRecoveryCodes = async () => {
try {
const response = await fetch(route('two-factor.regenerate-recovery-codes'), {
method: 'POST',
headers,
});

if (response.ok) {
const data: RecoveryCodesResponse = await response.json();
if (data.recovery_codes) {
setRecoveryCodesList(data.recovery_codes);
}
} else {
console.error('Error regenerating codes:', response.statusText);
const response = await axios.post(route('two-factor.regenerate-recovery-codes'));
const data: RecoveryCodesResponse = await response.data;
if (data.recovery_codes) {
setRecoveryCodesList(data.recovery_codes);
}
} catch (error) {
console.error('Error regenerating codes:', error);
let errorMessage = 'An error occurred while regenerating recovery codes';
if (error instanceof AxiosError && error.response?.data) {
errorMessage = error.response.data.message || errorMessage;
}
setError(errorMessage);
}
};

const disable = async () => {
try {
const response = await fetch(route('two-factor.disable'), { method: 'DELETE', headers });

if (response.ok) {
setConfirmed(false);
setShowingRecoveryCodes(false);
setRecoveryCodesList([]);
setQrCodeSvg('');
setSecretKey('');
} else {
console.error('Error disabling 2FA:', response.statusText);
}
await axios.delete(route('two-factor.disable'));

setConfirmed(false);
setShowingRecoveryCodes(false);
setRecoveryCodesList([]);
setQrCodeSvg('');
setSecretKey('');
} catch (error) {
console.error('Error disabling 2FA:', error);
let errorMessage = 'An error occurred while disabling 2FA';
if (error instanceof AxiosError && error.response?.data) {
errorMessage = error.response.data.message || errorMessage;
}
setError(errorMessage);
}
};

Expand Down Expand Up @@ -165,4 +148,4 @@ export function useTwoFactorAuth(initialConfirmed: boolean, initialRecoveryCodes
disable,
copyToClipboard,
};
}
}
1 change: 0 additions & 1 deletion resources/views/app.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">

{{-- Inline script to detect system dark mode preference and apply it immediately --}}
<script>
Expand Down