๐Ÿ” 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 *