πŸ” 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.

2 Comments

  1. Fabrizio

    Hi I have tried to replicate your approach…but when I click on Generate OTP button (ex login), I’m not redirect to page 38, I still remain on login page.
    How ca n I solve this?

    • 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;

      Please create new process on login page with on Login Button click

Leave a Reply

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