🔐 2FA Flow (Recommended)
Registration
- User registers
- OTP is generated & sent to email
- User verifies OTP
- Account becomes verified
Login
- Email + password correct
- Generate OTP
- Send OTP to email
- Return requires_2fa = true
- Verify OTP → issue token
1️⃣ Database Changes
Add columns to users table
Schema::table('users', function (Blueprint $table) {
$table->string('otp_code')->nullable();
$table->timestamp('otp_expires_at')->nullable();
$table->boolean('is_2fa_verified')->default(false);
});
2️⃣ Helper: Generate OTP
private function generateOtp()
{
return rand(100000, 999999);
}
3️⃣ Update User Register (Send OTP)
public function user_register(Request $request)
{
$validated = Validator::make($request->all(), [
'email' => 'required|email|unique:users',
'password' => 'required|min:6',
]);
if ($validated->fails()) {
return response([
'message' => $validated->errors(),
'status' => 'error',
], 422);
}
DB::beginTransaction();
try {
$otp = $this->generateOtp();
$user = User::create([
'full_name' => $request->input('full_name', ''),
'user_type' => 1,
'email' => $request->email,
'password' => Hash::make($request->password),
'otp_code' => $otp,
'otp_expires_at' => now()->addMinutes(5),
]);
// Send OTP via email (simple example)
Mail::raw("Your OTP is: {$otp}", function ($message) use ($user) {
$message->to($user->email)
->subject('Verify Your Account');
});
DB::commit();
return response([
'message' => 'Registration successful. OTP sent to email.',
'status' => 'success',
], 201);
} catch (\Exception $e) {
DB::rollback();
return response([
'message' => $e->getMessage(),
'status' => 'error',
], 500);
}
}
4️⃣ Update User Login (Trigger 2FA)
public function user_login(Request $request)
{
$validated = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
if ($validated->fails()) {
return response([
'message' => $validated->errors(),
'status' => 'error',
], 422);
}
$user = User::where('email', $request->email)
->where('status', 1)
->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response([
'message' => 'Invalid credentials',
'status' => 'error',
], 401);
}
// Generate OTP
$otp = $this->generateOtp();
$user->update([
'otp_code' => $otp,
'otp_expires_at' => now()->addMinutes(5),
'is_2fa_verified' => false,
]);
// Send OTP
Mail::raw("Your login OTP is: {$otp}", function ($message) use ($user) {
$message->to($user->email)
->subject('Login Verification OTP');
});
return response([
'message' => 'OTP sent to email',
'requires_2fa' => true,
'status' => 'success',
], 200);
}
5️⃣ OTP Verification API (FINAL LOGIN)
public function verifyOtp(Request $request)
{
$request->validate([
'email' => 'required|email',
'otp' => 'required',
]);
$user = User::where('email', $request->email)
->where('otp_code', $request->otp)
->where('otp_expires_at', '>=', now())
->first();
if (!$user) {
return response([
'message' => 'Invalid or expired OTP',
'status' => 'error',
], 401);
}
$user->update([
'otp_code' => null,
'otp_expires_at' => null,
'is_2fa_verified' => true,
]);
$token = $user->createToken($user->email)->plainTextToken;
return response([
'user' => $user,
'token' => $token,
'message' => 'Login successful',
'status' => 'success',
], 200);
}
6️⃣ API Flow Summary
POST /register → OTP sent
POST /login → OTP sent
POST /verify-otp → Token issued