šŸ” Build OTP Verification Login System in Oracle APEX Using Email OTP

šŸ” Build OTP Verification Login System in Oracle APEX Using Email OTP

šŸ”¹ About

Password-based authentication alone is no longer enough for modern applications.

Adding OTP (One-Time Password) verification improves application security by validating the user through email verification.

In this blog, we will build a complete Email OTP Verification System in Oracle APEX using:

āœ… Oracle APEX
āœ… PL/SQL
āœ… JavaScript
āœ… APEX_MAIL
āœ… Session State
āœ… Dynamic Actions

We will create:

āœ” Generate OTP button on Login Page
āœ” OTP table for verification
āœ” OTP email functionality
āœ” 6-digit OTP input UI
āœ” Auto focus OTP boxes
āœ” OTP resend functionality
āœ” Countdown timer
āœ” OTP validation logic


šŸ› ļø Tools and Technologies

  • Oracle APEX
  • Oracle Database
  • SQL
  • PL/SQL
  • JavaScript
  • APEX_MAIL

šŸš€ What We Are Building


šŸ“Œ Workflow

User Enters Email & Password
        ↓
Click Generate OTP
        ↓
OTP Sent to Email
        ↓
Redirect to Verify OTP Page
        ↓
Enter 6 Digit OTP
        ↓
Validate OTP
        ↓
Login Success

šŸš€ Step 1: Create OTP Table

Create a table to store OTP details.

CREATE TABLE otp_log (
    user_email      VARCHAR2(255),
    otp_code        VARCHAR2(10),
    expiry_time     TIMESTAMP,
    is_verified     VARCHAR2(1) DEFAULT 'N'
);

šŸ“Œ Table Explanation

ColumnPurpose
USER_EMAILStores user email
OTP_CODEGenerated OTP
EXPIRY_TIMEOTP expiry timestamp
IS_VERIFIEDVerification status

šŸš€ Step 2: Modify Login Button

Change the Login button name to:

Generate OTP

šŸš€ Step 3: Create OTP Generation Process

Create a Page Process on Login Page.


šŸ“Œ Process Code

DECLARE
    l_otp VARCHAR2(6);
    l_recipient VARCHAR2(255);
BEGIN

    l_recipient := :P9999_USERNAME;

    IF l_recipient IS NULL THEN

        apex_error.add_error (
            p_message          => 'Please enter an email address first.',
            p_display_location => apex_error.c_inline_in_notification
        );

    END IF;

    l_otp := LPAD(
                TRUNC(DBMS_RANDOM.VALUE(0, 999999)),
                6,
                '0'
             );

    INSERT INTO otp_log (
        user_email,
        otp_code,
        expiry_time
    )
    VALUES (
        l_recipient,
        l_otp,
        SYSTIMESTAMP + INTERVAL '5' MINUTE
    );

    apex_mail.send(
        p_to   => l_recipient,
        p_from => 'noreply@yourdomain.com',
        p_body => 'Your OTP is: ' || l_otp,
        p_subj => 'OTP Verification'
    );

    apex_mail.push_queue;

    apex_util.set_session_state(
        'GLOBAL_USER_EMAIL',
        l_recipient
    );

    apex_util.redirect_url (
        p_url => apex_page.get_url(
                    p_page => 38
                 )
    );

END;

šŸš€ What This Process Does

āœ” Generates random 6-digit OTP
āœ” Saves OTP into database
āœ” Sends OTP email
āœ” Stores email in session state
āœ” Redirects user to OTP verification page


šŸš€ Step 4: Create Verify OTP Page

Create a new page:

Page Number: 38
Page Name: Verify OTP

šŸš€ Step 5: Create OTP Input UI

Create:

āœ” 6 Text Fields
āœ” Verify Button
āœ” Resend OTP Button
āœ” Timer Region


šŸ“Œ Page Items

P38_C1
P38_C2
P38_C3
P38_C4
P38_C5
P38_C6

Each field stores one OTP digit.


šŸš€ Step 6: Create Input Container Region

Create a Static Content region:

Input Container

Add CSS Class:

otp-flex-container

šŸš€ Step 7: Add CSS Styling

Add below CSS in:

Page → CSS → Inline

šŸ“Œ Inline CSS

.otp-flex-container {
    display: flex !important;
    justify-content: center;
    align-items: center;
    gap: 10px;
    margin: 20px 0;
}

.otp-flex-container .t-Form-fieldContainer {
    margin: 0 !important;
    padding: 0 !important;
    flex: 0 1 auto !important;
}

.otp-input-box {
    width: 45px !important;
    height: 55px !important;
    text-align: center;
    font-size: 20px !important;
    border-radius: 8px !important;
    border: 1px solid #ccc !important;
}

#resendBtn {
    color: #4CAF50;
    font-weight: bold;
    text-decoration: none;
}

#resendBtn:hover {
    text-decoration: underline;
}

#otpTimer{
    font-size: 18px;
    font-weight: 700;
    text-align: center;
    margin-top: 10px;
    color: #000;
}

šŸš€ Step 8: Add JavaScript for Auto Focus

Add below code in:

Page → Execute When Page Loads

šŸ“Œ JavaScript Code

$('#P38_C1').focus();

$(document).on('keyup', '.otp-input-box input', function(e) {

    let $this = $(this);
    let val = $this.val();

    if (val.length === 1) {

        let nextInput = $('.otp-input-box input')
            .eq($('.otp-input-box input').index($this) + 1);

        if (nextInput.length) {
            nextInput.focus();
        }
    }

    if (e.keyCode === 8 && val.length === 0) {

        let prevInput = $('.otp-input-box input')
            .eq($('.otp-input-box input').index($this) - 1);

        if (prevInput.length) {
            prevInput.focus();
        }
    }

});

$(document).on('input', '.otp-input-box input', function() {

    if (this.value.length > 1) {
        this.value = this.value.slice(0, 1);
    }

});

šŸš€ Features

āœ… Auto focus next field
āœ… Backspace moves previous field
āœ… Prevent multiple characters


šŸš€ Step 9: Add OTP Timer


šŸ“Œ JavaScript Timer Code

window.startOtpTimer = function () {

    window.timeLeft = 30;

    if (window.timerId) {
        clearInterval(window.timerId);
    }

    $("#resendBtn").hide();

    $("#otpTimer").html("Resend OTP in 00:30");

    window.timerId = setInterval(function () {

        if (window.timeLeft < 0) {

            clearInterval(window.timerId);

            $("#otpTimer").html("");

            $("#resendBtn").show();

        } else {

            var seconds = window.timeLeft < 10
                ? '0' + window.timeLeft
                : window.timeLeft;

            $("#otpTimer").html(
                "Resend OTP in 00:" + seconds
            );

            window.timeLeft--;

        }

    }, 1000);

};

startOtpTimer();

šŸš€ Features

āœ… Countdown timer
āœ… Hide resend button initially
āœ… Show resend button after 30 seconds


šŸš€ Step 10: Create Resend OTP Dynamic Action

Create Dynamic Action on:

RESEND_BTN

Event:

click

šŸš€ True Action 1 → Execute Server-side Code


šŸ“Œ PL/SQL Code

DECLARE
    l_otp VARCHAR2(6);
    l_recipient VARCHAR2(255);
    l_count NUMBER;
BEGIN

    l_recipient := :GLOBAL_USER_EMAIL;

    SELECT COUNT(*)
    INTO l_count
    FROM otp_log
    WHERE user_email = l_recipient;

    IF l_count > 0 THEN

       DELETE FROM otp_log
       WHERE user_email = l_recipient;

    END IF;

    l_otp := LPAD(
                TRUNC(DBMS_RANDOM.VALUE(0, 999999)),
                6,
                '0'
             );

    INSERT INTO otp_log (
        user_email,
        otp_code,
        expiry_time
    )
    VALUES (
        l_recipient,
        l_otp,
        SYSTIMESTAMP + INTERVAL '5' MINUTE
    );

    apex_mail.send(
        p_to   => l_recipient,
        p_from => 'noreply@yourdomain.com',
        p_body => 'Your OTP is: ' || l_otp,
        p_subj => 'OTP Verification'
    );

    apex_mail.push_queue;

END;

šŸš€ True Action 2 → Execute JavaScript

window.timeLeft = 30;

clearInterval(window.timerId);

$('#resendBtn').hide();

$('#P38_TIMER').show();

window.timerId = setInterval(
    window.countdown,
    1000
);

apex.message.showPageSuccess(
    "OTP sent successfully!"
);

startOtpTimer();

šŸš€ Step 11: Verify OTP Process

Create Verify Button Process.


šŸ“Œ Verification Logic

DECLARE

    l_entered_otp VARCHAR2(6);
    l_count NUMBER;

BEGIN

    l_entered_otp :=
          :P38_C1
       || :P38_C2
       || :P38_C3
       || :P38_C4
       || :P38_C5
       || :P38_C6;

    SELECT COUNT(*)
    INTO l_count
    FROM otp_log
    WHERE user_email = :GLOBAL_USER_EMAIL
    AND otp_code = l_entered_otp
    AND expiry_time >= SYSTIMESTAMP;

    IF l_count > 0 THEN

        UPDATE otp_log
        SET is_verified = 'Y'
        WHERE user_email = :GLOBAL_USER_EMAIL;

        apex_authentication.login(
            p_username => :GLOBAL_USER_EMAIL,
            p_password => null
        );

    ELSE

        apex_error.add_error (
            p_message =>
                'Invalid or Expired OTP',
            p_display_location =>
                apex_error.c_inline_in_notification
        );

    END IF;

END;

šŸš€ Final Output

Your application now supports:

āœ… Secure OTP authentication
āœ… Email verification
āœ… Auto OTP input navigation
āœ… OTP resend functionality
āœ… Countdown timer
āœ… Modern OTP UI


šŸ“Œ Real-Time Use Cases

This type of OTP authentication is useful in:

āœ” Banking Applications
āœ” HR Systems
āœ” Enterprise Portals
āœ” Approval Applications
āœ” Customer Login Systems
āœ” Secure Internal Applications


šŸŽÆ Benefits of OTP Verification

āœ… Improved security
āœ… Reduced password risk
āœ… Better authentication flow
āœ… Modern login experience
āœ… Secure email validation


šŸŽ‰ Conclusion

Building OTP authentication in Oracle APEX is easier than many developers think.

Using:

āœ… APEX_MAIL
āœ… PL/SQL
āœ… JavaScript
āœ… Dynamic Actions
āœ… Session State

you can create a secure and professional OTP verification system with minimal code.

This approach provides a modern authentication experience for enterprise Oracle APEX applications.

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply

    Your email address will not be published. Required fields are marked *