비트베이크

[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-06-16T05:01:55.625Z

2026 다이소 여름 신상/인기템! 시원한 여름 꿀템 총정리

2026년 다이소 여름 신상부터 인기 쿨링템, 장마철 필수품, 홈캉스 아이템까지! 가성비 넘치는 다이소 여름 꿀템으로 시원하고 쾌적한 여름을 준비하는 완벽 가이드.

2026-06-16T05:01:31.367Z

지속 가능한 국내 워케이션: 2026년 숨은 보석 여행지

2026년 국내 워케이션 트렌드는 지속가능한 여행과 만납니다. 디지털 디톡스, 친환경 숙소, 로컬 체험을 통해 몸과 마음을 치유하고 지역 경제 활성화에 기여하는 숨은 명소 3곳을 소개합니다. 지금 바로 나만의 지속 가능한 워케이션을 계획해보세요!

2026-06-16T05:01:30.087Z

2026년 최신 의학 트렌드: AI와 정밀의료로 여는 초개인화 건강관리

2026년, AI와 정밀의료가 이끄는 초개인화 건강관리 시대가 열렸습니다. 딥러닝 기반 진단, 유전체 맞춤 치료, 웨어러블 및 디지털 치료제가 일상 속 건강을 혁신합니다. 미래 의학의 도전 과제와 현명한 건강 관리법을 알아보세요.

2026-06-16T05:01:16.613Z

2026 가을/겨울 출산준비물: 신생아 육아템 필수템 총정리

2026년 가을/겨울 출산을 앞둔 예비맘들을 위한 완벽 가이드! 최신 트렌드를 반영한 신생아 육아템 필수템부터 대형 육아용품 비교, 스마트한 케어 및 수유 용품, 쌀쌀한 날씨 대비 아기옷, 그리고 알뜰 구매 팁까지 모든 출산준비물을 총정리했습니다.

서비스

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

문의

비트베이크

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

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

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