비트베이크

[AWS Cognito] Solving SMS Failures: Integrate Domestic Custom SMS via Lambda Trigger in 5 Mins

2026-05-22T01:03:05.507Z

Search query for professional, modern, tech-related images suitable for developer/authentication content, with a clean aesthetic and space for text overlay: 'Cyber security abstract'

The Nightmare of AWS Cognito SMS Authentication

If you are an indie developer, a freelancer, or working at an early-stage startup, AWS Cognito is often the go-to solution for managing user authentication and authorization. It scales effortlessly and integrates perfectly into the AWS ecosystem. However, when you try to implement SMS authentication (OTP) for users in certain regions—especially in countries like South Korea (+82)—you immediately hit a massive roadblock.

By default, AWS Cognito relies on Amazon SNS (Simple Notification Service) to dispatch SMS text messages. Unfortunately, due to strict local telecom regulations and aggressive anti-spam filtering, SMS messages sent via AWS SNS are notoriously unreliable in these regions. Messages are frequently delayed by minutes, or completely blocked by telecom carriers. To make matters worse, the cost per SMS via SNS is remarkably high, draining the limited budget of a side project or a startup MVP.

The Barrier of Local SMS Providers

The logical solution seems simple: "I will just replace AWS SNS with a local domestic SMS provider API."

But as soon as you open the documentation for traditional local SMS vendors, you face an entirely different nightmare:

  • Endless Paperwork: You are required to submit business registration certificates and telecommunication service usage proofs.
  • Caller ID Pre-registration: Due to caller ID spoofing laws, you must pre-register your sending phone number, which requires a manual review process taking 3 to 5 business days.
  • Legacy APIs: You often have to deal with SOAP APIs, XML payloads, or outdated authentication mechanisms.

For an indie developer or a startup trying to launch an MVP this weekend, spending a week exchanging emails for legal compliance just to send a 6-digit OTP code is a complete dealbreaker.


The 5-Minute Solution: Enter EasyAuth

This exact frustration is why EasyAuth was born. EasyAuth is an ultra-simple, developer-centric SMS authentication API designed to get out of your way.

  • Zero Paperwork: No business licenses or compliance documents required. Anyone can use it.
  • Instant Setup: Sign up, grab your API key, and integrate in under 5 minutes.
  • Automatic Caller ID: No need to pre-register phone numbers. EasyAuth's system automatically handles compliant caller IDs on your behalf.
  • Cost-Effective: Drastically cheaper than AWS SNS and traditional vendors, costing only about $0.011 to $0.018 (15~25 KRW) per message.
  • Free Trial: You get 10 free SMS credits immediately upon signup to test your integration.

The true beauty of EasyAuth lies in its API design. You only need two endpoints:

  • POST /send - Sends the OTP to the user.
  • POST /verify - Verifies the code the user entered.

EasyAuth handles the entire state management, OTP generation, and expiration logic. In this comprehensive guide, we will learn how to bypass AWS SNS completely and integrate EasyAuth into AWS Cognito using the Custom Authentication Challenge Lambda Triggers.


Architectural Decision: Custom SMS Sender vs. Custom Auth Challenge

AWS Cognito offers two primary ways to customize SMS delivery:

1. Custom SMS Sender Trigger

In this flow, Cognito generates the OTP code, encrypts it using an AWS KMS (Key Management Service) key, and triggers your Lambda function. Your Lambda must decrypt the code and then call a 3rd-party SMS API to send it. The Problem: Managing AWS KMS keys adds unnecessary infrastructure complexity. Furthermore, this forces you to use Cognito's OTP state, which means you cannot fully utilize EasyAuth's elegant /verify endpoint.

2. Custom Authentication Challenge Trigger

In this flow, you take full control of the authentication state machine. You use a trio of Lambda functions (Define, Create, Verify). Cognito delegates the challenge entirely to you. The Advantage: This is the perfect match for EasyAuth. We don't need AWS KMS. We can simply trigger EasyAuth's POST /send in the Create step, and trigger POST /verify in the Verify step. EasyAuth acts as the ultimate stateless OTP engine for our Cognito User Pool.

Let's implement the Custom Authentication Challenge flow step-by-step.


Step-by-Step Implementation Guide

We will write three separate Node.js Lambda functions. Ensure that your Lambda execution roles have basic execution permissions (CloudWatch logs), and add your EASYAUTH_API_KEY to the Lambda environment variables.

Step 1: Define Auth Challenge Lambda

This function acts as the orchestrator. It decides whether to present a challenge, issue JWT tokens (if successful), or deny access (if failed).

// defineAuthChallenge.js
exports.handler = async (event) => {
    // Check if this is the first authentication attempt
    if (event.request.session && event.request.session.length === 0) {
        // Initiate our custom challenge
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
    } 
    // Check if the user successfully answered the previous challenge
    else if (
        event.request.session &&
        event.request.session.length > 0 &&
        event.request.session.slice(-1)[0].challengeResult === true
    ) {
        // The code was correct, issue JWT tokens!
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
    } 
    // Max attempts reached or invalid state
    else {
        event.response.issueTokens = false;
        event.response.failAuthentication = true;
    }
    
    return event;
};

Step 2: Create Auth Challenge Lambda

This function is invoked when Cognito needs to present the challenge to the user. This is where we call EasyAuth to dispatch the SMS.

// createAuthChallenge.js
exports.handler = async (event) => {
    if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
        const phoneNumber = event.request.userAttributes.phone_number;
        
        // Only send the SMS on the first attempt to prevent spamming
        // if the user inputs the wrong code and retries.
        if (event.request.session && event.request.session.length === 0) {
            try {
                // Call EasyAuth API to send the OTP
                const response = await fetch('https://api.easyauth.co/v1/send', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${process.env.EASYAUTH_API_KEY}`
                    },
                    body: JSON.stringify({ phone: phoneNumber })
                });
                
                if (!response.ok) {
                    const errorData = await response.text();
                    console.error("EasyAuth API Error:", errorData);
                    throw new Error('Failed to send SMS via EasyAuth');
                }
            } catch (error) {
                console.error("Network/Execution Error:", error);
                throw new Error("An error occurred while sending the verification code.");
            }
        }

        // Public parameters are exposed to the client app (e.g., to show "Sent to +82 10-****-1234")
        event.response.publicChallengeParameters = { phone: phoneNumber };
        // Private parameters are securely passed to the Verify function later
        event.response.privateChallengeParameters = { challenge: 'EASYAUTH_OTP' };
    }
    
    return event;
};

Step 3: Verify Auth Challenge Lambda

When the user types the 6-digit code into your frontend app and submits it, Cognito invokes this function. We will pass the code to EasyAuth for verification.

// verifyAuthChallenge.js
exports.handler = async (event) => {
    if (event.request.privateChallengeParameters.challenge === 'EASYAUTH_OTP') {
        const answer = event.request.challengeAnswer; // The code the user typed
        const phoneNumber = event.request.userAttributes.phone_number;

        try {
            // Call EasyAuth API to verify the OTP
            const response = await fetch('https://api.easyauth.co/v1/verify', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${process.env.EASYAUTH_API_KEY}`
                },
                body: JSON.stringify({
                    phone: phoneNumber,
                    code: answer
                })
            });

            const data = await response.json();
            
            // Inform Cognito whether the verification was successful
            // EasyAuth returns { "success": true } if valid.
            event.response.answerCorrect = data.success === true;
            
        } catch (error) {
            console.error("Verification Error:", error);
            event.response.answerCorrect = false; // Deny on error
        }
    }
    
    return event;
};

Frontend Integration (Next.js & AWS Amplify)

With your Cognito User Pool configured to use these three Lambda triggers, your frontend code becomes incredibly clean. If you are building with React or Next.js using AWS Amplify, the implementation looks like this:

import { Auth } from 'aws-amplify';

// 1. User enters their phone number to sign in/sign up
let cognitoUserReference;

async function handleSendOTP(phoneNumber) {
    try {
        // This triggers the Create Auth Challenge Lambda
        cognitoUserReference = await Auth.signIn(phoneNumber);
        console.log("OTP Sent! Please check your messages.");
        // Transition your UI to the code input screen
    } catch (error) {
        console.error("Error initiating sign in:", error);
    }
}

// 2. User enters the 6-digit code received via SMS
async function handleVerifyOTP(otpCode) {
    try {
        // This triggers the Verify Auth Challenge Lambda
        await Auth.sendCustomChallengeAnswer(cognitoUserReference, otpCode);
        console.log("Authentication successful! User is now logged in.");
        // Redirect user to the dashboard
    } catch (error) {
        console.error("Verification failed (Invalid or expired code):", error);
    }
}

Best Practices & Security Considerations for Production

Before deploying this to a production environment, keep the following tips in mind:

  1. Lambda Timeout Adjustment: By default, AWS sets Lambda timeouts to 3 seconds. Since our Create and Verify functions are making external HTTP calls to the EasyAuth API, network latency can occasionally cause timeouts. Increase the timeout limit of these Lambdas to at least 10 seconds to ensure reliability.
  2. Protecting PII in CloudWatch: Be very careful with console.log(event) in your Lambda functions. The event object contains the user's phone number. Logging this in plain text violates data protection standards (like GDPR or local privacy laws). Always mask phone numbers before logging.
  3. API Key Management: Never hardcode your EASYAUTH_API_KEY. Utilize AWS Secrets Manager or secure Lambda Environment Variables to inject the key at runtime.
  4. Rate Limiting Mitigation: While EasyAuth scales wonderfully, you should implement WAF (Web Application Firewall) rules on your Cognito endpoints to prevent malicious bots from rapidly requesting SMS codes and racking up your billing.

Conclusion

Using AWS Cognito's default Amazon SNS for SMS authentication might be the path of least resistance initially, but for developers operating in strict telecom environments, it quickly becomes a bottleneck of failed deliveries, angry users, and high AWS bills.

Conversely, falling back to legacy domestic SMS vendors halts your development velocity entirely due to archaic paperwork requirements and slow manual approvals.

By leveraging the Cognito Custom Authentication Challenge and integrating EasyAuth, you achieve the holy grail of authentication for modern apps: an enterprise-grade AWS backend combined with a frictionless, zero-paperwork, and highly reliable local SMS provider.

Don't let legacy telecom regulations slow down your MVP launch. Sign up at EasyAuth today, claim your 10 free test credits, and implement this 5-minute serverless architecture. Focus your valuable engineering time on building your core product features, not wrestling with SMS delivery issues.

비트베이크에서 광고를 시작해보세요

광고 문의하기

다른 글 보기

2026-08-06T06:01:33.120Z

2026 GTX 개통 임박! A/B/C 노선 수혜지역 투자 가이드

2026년 GTX A/B/C 노선 개통이 임박하며 수도권 부동산 시장이 들썩이고 있습니다. GTX 노선별 개통 현황과 함께, 주요 수혜지역을 심층 분석하고 실거주 및 투자를 위한 현명한 전략과 유의점을 제시하여 성공적인 아파트 투자를 돕는 가이드입니다.

2026-08-05T06:01:33.825Z

2026 하반기 재건축 투자: 규제 완화 속 핵심 전략

2026년 하반기, 규제 완화 기대감 속 재건축 투자의 핵심 전략을 알아봅니다. 정부 정책 변화 분석, 유망 지역 선정 기준, 주의할 점, 그리고 성공적인 투자를 위한 전문가들의 조언까지, 2026 부동산 시장에서 기회를 잡을 방법을 제시합니다.

2026-08-04T06:01:37.246Z

2026 하반기 청약, 대출 금리 변화 활용 내집마련 필승 전략

2026년 하반기 청약 시장은 변화하는 대출 금리와 정책, 지역별 수급 상황에 따라 기회와 도전이 공존합니다. 이 글에서는 부동산 시장 동향과 주택담보대출 전략, 인기 청약 단지 분석, 청약 가점 및 특별공급 활용 팁 등 내 집 마련을 위한 필승 전략을 제시합니다. 철저한 준비와 현명한 판단으로 2026년 내 집 마련의 꿈을 이루세요.

2026-08-04T01:01:36.795Z

2026년 청약 성공 전략: 무주택자 내집마련 필승 가이드

2026년 무주택자의 내집마련 꿈을 위한 필승 청약 전략 가이드입니다. 청약 가점부터 특별공급 활용법, 현명한 대출 전략, 유망 단지 분석, 그리고 제도 변화까지 2026년 청약 성공을 위한 모든 정보를 담았습니다.

서비스

피드자주 묻는 질문고객센터

문의

비트베이크

레임스튜디오 | 사업자 등록번호 : 542-40-01042

경기도 남양주시 와부읍 수례로 116번길 16, 4층 402-제이270호

트위터인스타그램네이버 블로그