Đăng nhập bằng username và password là lớp bảo vệ cơ bản của ứng dụng.
Tuy nhiên, nếu mật khẩu bị lộ hoặc user dùng lại mật khẩu ở nhiều nơi,
kẻ tấn công vẫn có thể truy cập vào hệ thống. Vì vậy, nhiều ứng dụng cần thêm một bước xác minh thứ hai,
ví dụ gửi mã OTP qua email sau khi user đăng nhập.
Trong Oracle APEX, bạn có thể xây dựng cơ chế Email OTP Verification bằng custom authentication,
bảng lưu OTP, package PL/SQL và APEX_MAIL. User nhập đúng username/password trước,
sau đó hệ thống gửi mã OTP đến email đã đăng ký. Chỉ khi user nhập đúng mã OTP còn hạn,
hệ thống mới cho phép vào ứng dụng.
Bài viết này tập trung vào cách thiết kế Email OTP Verification trong Oracle APEX theo hướng dễ hiểu,
có kiểm soát và phù hợp với ứng dụng thực tế. Nội dung sẽ đi từ kiến trúc tổng quan, bảng lưu OTP,
package PL/SQL, gửi email bằng APEX_MAIL, xác minh OTP, giới hạn số lần thử và các lưu ý bảo mật.
Email OTP sau đăng nhập là gì?
Email OTP sau đăng nhập là quy trình xác thực gồm hai bước:
- Bước 1: user nhập username và password.
- Bước 2: hệ thống gửi mã OTP đến email của user.
- Bước 3: user nhập OTP để xác minh.
- Bước 4: nếu OTP đúng và còn hạn, user được vào ứng dụng.
Cơ chế này giúp tăng bảo mật vì user cần có quyền truy cập vào email,
không chỉ biết mật khẩu. Nếu mật khẩu bị lộ, kẻ tấn công vẫn cần mã OTP mới có thể đăng nhập.
Lược đồ tổng quan OTP login flow
Email OTP Verification flow trong Oracle APEX
1. Login
User nhập username và password trên custom login page.
→
2. Generate OTP
APEX tạo mã OTP, hash/lưu vào bảng cùng thời gian hết hạn.
→
3. Send Email
Gửi OTP qua APEX_MAIL đến email đã đăng ký của user.
→
4. Verify
User nhập OTP. Server kiểm tra mã, thời hạn và số lần thử.
Nếu OTP hợp lệ, hệ thống đánh dấu session đã xác minh OTP và redirect user vào trang chính.
Nếu OTP sai hoặc hết hạn, user phải nhập lại hoặc yêu cầu gửi mã mới.
Kiến trúc nên dùng
Một triển khai OTP tốt nên có các thành phần sau:
- Bảng user hoặc bảng profile có email của user.
- Bảng lưu OTP request.
- Package PL/SQL để tạo OTP, gửi email và verify OTP.
- Custom login process.
- Trang nhập OTP sau login.
- Application item hoặc session flag để biết user đã verify OTP chưa.
- Authorization/Before Header logic để chặn user nếu chưa verify OTP.
Điểm quan trọng là logic xác minh OTP phải chạy ở server-side.
Không nên chỉ dùng JavaScript phía client để quyết định user đã xác minh hay chưa.
Tạo bảng lưu OTP
Bạn có thể tạo một bảng riêng để lưu OTP request. Bảng này nên lưu user, mã OTP đã hash,
thời gian tạo, thời gian hết hạn, trạng thái đã sử dụng hay chưa và số lần nhập sai.
create table app_user_otps (
otp_id number generated always as identity primary key,
username varchar2(255) not null,
email_address varchar2(320) not null,
otp_hash varchar2(4000) not null,
created_at timestamp default systimestamp not null,
expires_at timestamp not null,
verified_at timestamp,
attempts number default 0 not null,
is_used varchar2(1) default 'N' not null,
session_id varchar2(255)
);
Không nên lưu OTP dạng plain text nếu không cần thiết. Cách tốt hơn là hash OTP trước khi lưu.
Khi user nhập OTP, bạn hash giá trị user nhập và so sánh với giá trị đã lưu.
Tạo OTP ngẫu nhiên
OTP thường là mã số 6 chữ số. Bạn có thể tạo bằng PL/SQL, ví dụ:
function generate_otp return varchar2 is
begin
return lpad(trunc(dbms_random.value(0, 1000000)), 6, '0');
end;
Với môi trường yêu cầu bảo mật cao hơn, bạn nên cân nhắc cách sinh mã mạnh hơn,
giới hạn số lần thử và thời gian sống của OTP thật ngắn, ví dụ 3–5 phút.
Hash OTP trước khi lưu
Để tránh lưu OTP thô trong database, bạn có thể hash OTP bằng một hàm chuẩn hóa.
Ví dụ minh họa:
function hash_otp(
p_username in varchar2,
p_otp in varchar2
) return varchar2 is
begin
return standard_hash(
upper(p_username) || ':' || p_otp,
'SHA256'
);
end;
Trong hệ thống thật, bạn có thể bổ sung salt hoặc kết hợp với application secret.
Mục tiêu là nếu bảng OTP bị xem bởi người không nên xem, mã OTP gốc vẫn không bị lộ trực tiếp.
Package xử lý OTP
Nên gom logic OTP vào một package riêng, ví dụ auth_otp_pkg.
Package này có thể có các procedure/function như:
create_and_send_otp: tạo OTP, lưu hash, gửi email.
verify_otp: kiểm tra OTP user nhập.
mark_session_verified: đánh dấu session đã xác minh.
cleanup_expired_otps: dọn OTP đã hết hạn.
Ví dụ procedure tạo và gửi OTP:
procedure create_and_send_otp(
p_username in varchar2,
p_email in varchar2
) is
l_otp varchar2(6);
begin
l_otp := generate_otp;
insert into app_user_otps (
username,
email_address,
otp_hash,
expires_at,
session_id
) values (
upper(p_username),
p_email,
hash_otp(p_username, l_otp),
systimestamp + interval '5' minute,
v('APP_SESSION')
);
apex_mail.send(
p_to => p_email,
p_from => 'no-reply@example.com',
p_subj => 'Your login verification code',
p_body => 'Your OTP code is: ' || l_otp ||
chr(10) || 'This code will expire in 5 minutes.'
);
apex_mail.push_queue;
end;
Trong production, nội dung email nên chuyên nghiệp hơn và không nên để lộ thông tin nhạy cảm khác.
Bạn cũng cần cấu hình email trong APEX Instance Administration hoặc môi trường đang chạy.
Verify OTP
Khi user nhập OTP, hệ thống cần kiểm tra:
- OTP thuộc đúng user.
- OTP thuộc đúng session nếu bạn ràng buộc theo session.
- OTP chưa được dùng.
- OTP chưa hết hạn.
- Số lần thử chưa vượt quá giới hạn.
- Hash OTP nhập vào khớp với hash đã lưu.
Ví dụ function verify:
function verify_otp(
p_username in varchar2,
p_otp in varchar2
) return boolean is
l_otp_id number;
begin
select otp_id
into l_otp_id
from app_user_otps
where username = upper(p_username)
and otp_hash = hash_otp(p_username, p_otp)
and is_used = 'N'
and expires_at > systimestamp
and attempts < 5
order by created_at desc
fetch first 1 row only;
update app_user_otps
set is_used = 'Y',
verified_at = systimestamp
where otp_id = l_otp_id;
return true;
exception
when no_data_found then
update app_user_otps
set attempts = attempts + 1
where username = upper(p_username)
and is_used = 'N'
and expires_at > systimestamp;
return false;
end;
Đây là ví dụ minh họa. Trong hệ thống thật, bạn nên xử lý concurrency, session binding,
logging và lockout cẩn thận hơn.
Custom login flow trong APEX
Quy trình login có thể thiết kế như sau:
- User vào page login, nhập username/password.
- Custom authentication kiểm tra username/password với bảng user hoặc database account.
- Nếu hợp lệ, tạo OTP và gửi email.
- Redirect user sang page nhập OTP.
- User nhập OTP.
- Nếu OTP đúng, set session flag đã verify.
- Redirect user vào Home page.
Bạn có thể dùng Application Item để lưu trạng thái:
APP_OTP_VERIFIED
Sau khi verify thành công:
apex_util.set_session_state('APP_OTP_VERIFIED', 'Y');
Sau đó trong các page cần bảo vệ, bạn có thể kiểm tra item này.
Chặn user nếu chưa verify OTP
Nếu user đã nhập đúng password nhưng chưa nhập OTP, họ không nên được vào các page chính.
Bạn có thể tạo Authorization Scheme hoặc Before Header process để kiểm tra:
return nvl(v('APP_OTP_VERIFIED'), 'N') = 'Y';
Nếu chưa verify, redirect user về page OTP.
Không nên chỉ ẩn menu hoặc button, vì user có thể gõ URL trực tiếp.
Trang nhập OTP nên thiết kế đơn giản
Page nhập OTP nên có:
- Thông báo mã đã được gửi đến email nào, có thể mask email.
- Item nhập OTP, ví dụ
P102_OTP.
- Button Verify.
- Button Resend OTP nếu cần.
- Thông báo mã hết hạn sau bao nhiêu phút.
Không nên hiển thị OTP trên màn hình hoặc trong debug output.
Nếu bật debug trong môi trường dev, cần cẩn thận không log mã OTP thô.
Gửi lại OTP
Tính năng resend OTP rất hữu ích, nhưng cần giới hạn để tránh spam email.
Ví dụ:
- Chỉ cho gửi lại sau 30–60 giây.
- Giới hạn số lần resend trong một khoảng thời gian.
- Vô hiệu hóa OTP cũ khi tạo OTP mới.
- Log số lần gửi lại.
Khi user yêu cầu gửi lại OTP, bạn nên đánh dấu các OTP cũ của user/session là used hoặc expired,
rồi tạo mã mới.
update app_user_otps
set is_used = 'Y'
where username = upper(:APP_USER)
and is_used = 'N'
and verified_at is null;
Cấu hình email trong APEX
Để gửi OTP qua email, APEX cần cấu hình email đúng.
Tùy môi trường, bạn có thể cấu hình SMTP trong Instance Administration hoặc dùng dịch vụ email tương ứng.
Một số điểm cần kiểm tra:
- SMTP host và port.
- Tài khoản gửi email nếu SMTP yêu cầu authentication.
- TLS/SSL nếu cần.
- Email sender hợp lệ.
- Queue email có được push hoặc xử lý không.
- Spam/junk folder của người nhận.
Nếu email không đi, hãy kiểm tra mail queue và log của APEX/ORDS trước khi debug logic OTP.
Thời hạn OTP nên là bao lâu?
Thông thường, OTP email nên có thời hạn ngắn, ví dụ 3 đến 5 phút.
Nếu quá dài, mã OTP bị lộ sẽ nguy hiểm hơn. Nếu quá ngắn, người dùng có thể gặp khó khăn
khi email đến chậm.
Một cấu hình thực tế:
- OTP hết hạn sau 5 phút.
- Tối đa 5 lần nhập sai.
- Resend sau ít nhất 60 giây.
- OTP cũ bị vô hiệu khi gửi OTP mới.
- Log các lần thất bại để phát hiện tấn công brute force.
Không nên xem Email OTP là bảo mật tuyệt đối
Email OTP giúp tăng bảo mật, nhưng không phải giải pháp hoàn hảo cho mọi tình huống.
Nếu email của user bị chiếm quyền, OTP cũng có thể bị lấy. Ngoài ra, email có thể bị chậm,
vào spam hoặc bị forward ngoài ý muốn.
Với hệ thống yêu cầu bảo mật cao hơn, bạn nên cân nhắc các lựa chọn mạnh hơn như:
- TOTP bằng ứng dụng xác thực.
- SSO/OAuth2 với Identity Provider.
- WebAuthn/FIDO2 nếu môi trường hỗ trợ.
- Chính sách session timeout và risk-based authentication.
Email OTP phù hợp cho nhiều ứng dụng nội bộ hoặc mức bảo mật trung bình,
nhưng cần được triển khai cẩn thận.
Best practice khi triển khai Email OTP
Một số nguyên tắc nên áp dụng:
- Không lưu OTP dạng plain text.
- OTP phải có thời hạn ngắn.
- Giới hạn số lần nhập sai.
- Giới hạn số lần resend.
- Vô hiệu hóa OTP cũ khi tạo OTP mới.
- Không log OTP thô.
- Kiểm tra OTP ở server-side.
- Ràng buộc OTP với user và session nếu phù hợp.
- Redirect user chưa verify về page OTP.
- Ghi log login/verify fail để theo dõi bất thường.
Các lỗi thường gặp
Khi triển khai OTP trong APEX, một số lỗi phổ biến gồm:
- Gửi OTP thành công nhưng không gọi
APEX_MAIL.PUSH_QUEUE hoặc mail queue chưa được xử lý.
- Không cấu hình SMTP đúng nên email không đi.
- Quên kiểm tra OTP hết hạn.
- Không giới hạn số lần nhập sai.
- Set session flag ở client-side thay vì server-side.
- Cho user truy cập page chính trước khi verify OTP.
- Lưu OTP plain text trong database.
- Không xử lý resend nên nhiều OTP còn hiệu lực cùng lúc.
Kết luận
Email OTP Verification sau đăng nhập là một cách thực tế để tăng bảo mật cho ứng dụng Oracle APEX.
Bằng cách kết hợp custom login flow, bảng lưu OTP, package PL/SQL, APEX_MAIL
và session flag, bạn có thể xây dựng lớp xác thực thứ hai tương đối rõ ràng và dễ kiểm soát.
Điều quan trọng là không chỉ tạo mã và gửi email. Bạn cần quan tâm đến thời hạn OTP,
số lần thử, resend, logging, session binding và cách chặn user chưa verify.
Khi được thiết kế đúng, Email OTP giúp ứng dụng APEX an toàn hơn mà vẫn giữ trải nghiệm đăng nhập
tương đối đơn giản cho người dùng.