I’m encountering an issue with OTP (One-Time Password) verification in my ASP.NET Core application. I have implemented a LoginWithOTP endpoint in my AuthController, but the OTP code verification is failing.
Verifying OTP: In the LoginWithOTP endpoint, I use the UserManager.VerifyTwoFactorTokenAsync method to verify the OTP code provided by the user.
The OTP code verification fails with a 401 Unauthorized error, despite the OTP code being correct. The logs indicate that the OTP code is invalid, but the stored and provided OTP codes match.
Failed to load resource: the server responded with a status of 401 ()
axiosConfiguration.js:12
Axios error response data:
Object
errorDetails
:
{succeeded: false, isLockedOut: false, isNotAllowed: false, requiresTwoFactor: false}
message
:
“Sign-in failed. Invalid OTP Code.”
status
:
“Error”
[[Prototype]]
:
Object
OtpVerification.jsx
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
const cleanCode = otp.join('');
try {
const response = await verifyOTP(username, cleanCode);
console.log("Received response:", response);
if (response.success) {
toast.success(response.message || 'OTP Verified Successfully!');
onOpenChange(false);
} else {
toast.error(response.message || 'OTP Verification Failed. Please try again.');
}
} catch (error) {
console.error('Error during OTP verification:', error);
toast.error('An error occurred during OTP verification. Please try again.');
} finally {
setIsLoading(false);
setOtp(Array(length).fill(''));
}
};
AuthContext.jsx
import React, { createContext, useState, useContext } from 'react';
import axios from './axiosConfiguration';
import { toast } from 'react-toastify';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [token, setToken] = useState('');
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const login = (token, user) => {
sessionStorage.setItem('jwtToken', token);
sessionStorage.setItem('user', JSON.stringify(user));
setIsAuthenticated(true);
setToken(token);
setUser(user);
};
const logout = () => {
sessionStorage.removeItem('jwtToken');
sessionStorage.removeItem('user');
setIsAuthenticated(false);
setToken('');
setUser(null);
};
async function verifyOTP(username, code) {
try {
const response = await axios.post('/api/Auth/loginWithOTP', { username, code });
if (response.data.Status === 'Success') {
login(response.data.token, response.data.user);
toast.success('OTP verified successfully!');
return { success: true, token: response.data.token, user: response.data.user };
} else {
return { success: false, message: response.data.Message };
}
} catch (error) {
console.error('Error verifying OTP:', error);
return { success: false, message: 'An error occurred. Please try again.' };
} finally {
setIsLoading(false);
}
}
return (
<AuthContext.Provider value={{ isAuthenticated, token, user, login, logout, verifyOTP, setIsLoading, isLoading }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
AuthController.cs
[HttpPost]
[Route("loginWithOTP")]
public async Task<IActionResult> LoginWithOTP([FromBody] OtpRequest request)
{
// Validate request
if (request == null || string.IsNullOrEmpty(request.Code) || string.IsNullOrEmpty(request.Username))
{
_logger.LogWarning("Invalid OTP request: Missing code or username.");
return BadRequest(new { message = "Code and Username are required" });
}
// Find the user
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
{
_logger.LogWarning("OTP verification failed: User not found. Username: {Username}", request.Username);
return NotFound(new { Status = "Error", Message = "User not found." });
}
// Verify OTP
var validTwoFactorToken = await _userManager.VerifyTwoFactorTokenAsync(user, "Email", request.Code);
if (!validTwoFactorToken)
{
_logger.LogWarning("Invalid OTP code for user: {Username}. Code: {Code}", request.Username, request.Code);
return Unauthorized(new { Status = "Error", Message = "Invalid OTP code." });
}
// Attempt sign-in
var signInResult = await _signInManager.TwoFactorSignInAsync(
"Email",
request.Code,
rememberClient: false,
isPersistent: false);
_logger.LogInformation("Received OTP code: {Code} for user: {Username}", request.Code, request.Username);
if (!signInResult.Succeeded)
{
string errorMessage = "Sign-in failed. ";
if (signInResult.IsNotAllowed)
{
errorMessage += "User is not allowed to sign in. ";
}
if (signInResult.RequiresTwoFactor)
{
errorMessage += "User requires two-factor authentication. ";
}
if (signInResult.IsLockedOut)
{
errorMessage += "User is locked out. ";
}
if (signInResult.IsLockedOut || signInResult.IsNotAllowed || signInResult.RequiresTwoFactor)
{
// Log these specific scenarios with additional context if needed.
_logger.LogWarning("Failed OTP verification for user: {Username}. Details: {Details}", request.Username, errorMessage);
}
else
{
errorMessage += "Invalid OTP Code.";
_logger.LogWarning("Failed OTP verification for user: {Username}. Reason: Invalid OTP Code.", request);
}
return Unauthorized(new { Status = "Error", Message = errorMessage, ErrorDetails = signInResult });
}