[Next.js] Implementing SMS Mobile Authentication in 5 Minutes (Zero Paperwork)
Why is SMS Authentication So Complicated?
When building a toy project or a startup MVP (Minimum Viable Product), one of the biggest hurdles during the user registration flow is SMS Mobile Authentication (OTP).
You might think, "It's just integrating a single API. How hard can it be?" But when you look up legacy SMS gateway services, you quickly hit a wall:
- Mandatory Business Registration: What if you're an indie developer building a side project?
- Telecommunication Certificates Required
- Caller ID Pre-registration and Approval: Can take several days to process.
Today, I'll show you how to implement SMS authentication in a Next.js environment in just 5 minutes—without submitting a single document or waiting for approval.
EasyAuth: The Developer-Friendly SMS API
To skip the bureaucratic mess and dive straight into coding, we will use EasyAuth (이지어스). It's an SMS API tailored specifically for developers, offering these key advantages:
- Zero Paperwork: Get your API key instantly upon signup.
- Auto Caller ID: Send OTPs immediately using pre-approved shared numbers.
- Cost-Effective: Only 15~25 KRW per message (half the price of legacy providers). Plus, you get 10 free tests!
- Dead Simple Structure: Just two endpoints:
POST /sendandPOST /verify.
🚀 Step-by-Step Implementation in Next.js (App Router)
Let's integrate EasyAuth into a Next.js 14/15 environment using the App Router.
Step 1. Get Your API Key
- Sign up for EasyAuth.
- Copy the API Key from your dashboard.
- Add it to your
.env.localfile at the root of your project:
EASYAUTH_API_KEY=ea_live_xxxxxxxxxxxxxxxxx
Step 2. Create the Send API Route (Backend)
Never expose your API key to the frontend. Instead, we'll create a Next.js Route Handler to act as a proxy.
Create a file at app/api/auth/send/route.ts:
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
try {
const { phone } = await request.json();
const response = await fetch('https://api.easyauth.kr/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.EASYAUTH_API_KEY}`,
},
body: JSON.stringify({ phone }),
});
const data = await response.json();
if (!response.ok) {
return NextResponse.json({ error: data.message }, { status: response.status });
}
return NextResponse.json({ success: true, message: 'Verification code sent.' });
} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
Step 3. Create the Verify API Route (Backend)
Next, create an endpoint to verify the code the user enters.
Create a file at app/api/auth/verify/route.ts:
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
try {
const { phone, code } = await request.json();
const response = await fetch('https://api.easyauth.kr/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.EASYAUTH_API_KEY}`,
},
body: JSON.stringify({ phone, code }),
});
const data = await response.json();
if (!response.ok) {
return NextResponse.json({ error: 'Invalid verification code.' }, { status: 400 });
}
// You can add session generation logic here upon success
return NextResponse.json({ success: true, message: 'Verification successful.' });
} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
Step 4. Build the Frontend Component
Now, let's create a React component where users can input their phone number and the OTP code.
app/components/SmsAuth.tsx
'use client';
import { useState } from 'react';
export default function SmsAuth() {
const [phone, setPhone] = useState('');
const [code, setCode] = useState('');
const [step, setStep] = useState<'INPUT_PHONE' | 'INPUT_CODE'>('INPUT_PHONE');
const [loading, setLoading] = useState(false);
// Send Code
const handleSend = async () => {
setLoading(true);
try {
const res = await fetch('/api/auth/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone }),
});
if (res.ok) {
alert('Verification code sent.');
setStep('INPUT_CODE');
} else {
const error = await res.json();
alert(error.error || 'Failed to send code.');
}
} finally {
setLoading(false);
}
};
// Verify Code
const handleVerify = async () => {
setLoading(true);
try {
const res = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, code }),
});
if (res.ok) {
alert('✅ Authentication successful!');
// Proceed to the next signup step
} else {
alert('❌ Invalid code. Please try again.');
}
} finally {
setLoading(false);
}
};
return (
<div>
<h2>Mobile Authentication</h2>
{step === 'INPUT_PHONE' ? (
<div>
setPhone(e.target.value)}
className="p-2 border rounded"
/>
{loading ? 'Sending...' : 'Get Code'}
</div>
) : (
<div>
<p>Code sent to {phone}.</p>
setCode(e.target.value)}
className="p-2 border rounded"
/>
{loading ? 'Verifying...' : 'Verify Code'}
</div>
)}
</div>
);
}
💡 Best Practices & Security Tips
If you plan to ship this to production, keep these considerations in mind:
- Rate Limiting: Malicious users or bots might spam your
/api/auth/sendendpoint, leading to an unexpected SMS bill. Use tools like Upstash Redis to implement IP-based rate limiting on your API routes. - Server-side Validation: Always double-check user inputs on the backend. Use a library like
zodto validate that thephonevariable matches the standard phone number regex (e.g.,^010\d{8}$) before calling the EasyAuth API.
Conclusion
What used to take days of paperwork, carrier approvals, and caller ID registrations can now be resolved in just 5 minutes with a few lines of code.
For indie hackers, freelancers, and startups that need to validate their MVPs fast without business bureaucracy, EasyAuth is the perfect solution. You get 10 free SMS credits right upon signup, so try integrating it into your Next.js side project today!