Khi tích hợp REST API vào Oracle APEX, cách lý tưởng nhất là dùng các cơ chế authentication chuẩn
mà APEX hỗ trợ sẵn, ví dụ Basic Authentication, OAuth2 Client Credentials, OCI authentication,
hoặc API key thông qua Web Credentials.
Nhưng trong thực tế, không phải API nào cũng đi theo chuẩn quen thuộc. Có API yêu cầu gọi một endpoint riêng
để lấy token. Có API trả token theo cấu trúc JSON lạ. Có API bắt buộc truyền token vào một header đặc biệt.
Có API dùng username/password để đổi lấy bearer token, nhưng flow lại không khớp hoàn toàn với OAuth2 chuẩn.
Nếu xử lý không cẩn thận, developer rất dễ rơi vào cách làm nguy hiểm:
hard-code username, password, token hoặc API key trực tiếp trong PL/SQL package, page process,
dynamic action hoặc application item. Cách này nhanh lúc đầu, nhưng khó bảo trì và không an toàn.
Bài viết này trình bày một hướng xử lý thực tế cho các trường hợp API authentication không chuẩn trong Oracle APEX:
tách phần lấy token thành logic riêng, lưu credential đúng chỗ, cập nhật token vào Web Credential khi phù hợp,
và để REST Data Source hoặc APEX_WEB_SERVICE tái sử dụng token đó một cách có kiểm soát.
API authentication không chuẩn là gì?
API authentication không chuẩn là những cơ chế xác thực không khớp hoàn toàn với các loại authentication
mà Oracle APEX hỗ trợ declarative trong REST Data Source hoặc Web Credentials.
Ví dụ:
- API yêu cầu login bằng username/password để lấy bearer token.
- Token phải được gửi trong header có tên riêng, không phải
Authorization.
- API yêu cầu gọi refresh token theo endpoint riêng.
- Token hết hạn nhưng response lỗi không theo chuẩn OAuth2.
- API yêu cầu ký request bằng hash hoặc HMAC tự tạo.
- API yêu cầu gửi thêm tenant id, company id hoặc subscription key trong header.
- API dùng session token thay vì access token chuẩn.
Những API này vẫn có thể tích hợp vào APEX, nhưng cần thiết kế cẩn thận hơn.
Vấn đề nếu xử lý sai
Với API authentication không chuẩn, lỗi phổ biến nhất là để credential nằm rải rác trong code.
Ví dụ không nên làm:
l_username := 'api_user';
l_password := 'plain_text_password';
l_token := 'eyJhbGciOi...';
Cách này có nhiều vấn đề:
- Credential có thể bị lộ khi export app hoặc commit code.
- Khó đổi password/token giữa dev, test và production.
- Không tận dụng được Web Credentials của APEX.
- REST Data Source khó tái sử dụng credential.
- Không có quy trình refresh token rõ ràng.
- Khó audit và khó kiểm soát quyền truy cập.
Nguyên tắc quan trọng là: credential không nên nằm trong code ứng dụng.
Code chỉ nên biết cách lấy credential thông qua cơ chế an toàn và dùng nó đúng thời điểm.
Kiến trúc xử lý đề xuất
Xử lý non-standard API authentication trong Oracle APEX
Secure Store
Lưu username, password, API key hoặc secret trong nơi được kiểm soát.
→
Token Package
PL/SQL package gọi login endpoint, parse token và kiểm tra thời hạn.
→
Web Credential
Ghi token vào HTTP Header credential để APEX có thể tái sử dụng.
→
REST Calls
REST Data Source hoặc APEX_WEB_SERVICE gọi API bằng token hiện hành.
Mục tiêu là tách rõ ba phần: lưu credential gốc, lấy/refresh token, và sử dụng token khi gọi API.
Khi tách đúng, hệ thống dễ bảo trì và an toàn hơn.
Nguyên tắc thiết kế
Một giải pháp tốt nên tuân theo các nguyên tắc sau:
- Không hard-code username, password, API key hoặc token trong page process.
- Không để token trong JavaScript phía browser nếu không bắt buộc.
- Không lưu secret ở application item thường.
- Tách logic gọi login endpoint vào một package PL/SQL riêng.
- Có cơ chế kiểm tra token còn hạn hay đã hết hạn.
- Có cơ chế refresh token hoặc lấy token mới.
- Log lỗi đủ để troubleshooting nhưng không log secret/token.
- Dùng Web Credential hoặc secure store nếu có thể.
- Giới hạn quyền của schema và user dùng để gọi API.
Bước 1: Tạo Web Credential dạng HTTP Header
Nếu API cuối cùng cần bearer token trong HTTP header, bạn có thể tạo một Web Credential kiểu HTTP Header.
Ví dụ:
Credential Static ID:
EXT_API_BEARER_TOKEN
Authentication Type:
HTTP Header
Credential Name:
Authorization
Credential Secret:
Bearer dummy-token
Ban đầu bạn có thể đặt secret tạm. Sau đó package PL/SQL sẽ cập nhật giá trị này thành token thật.
Nếu API yêu cầu header khác, ví dụ x-api-token, thì Credential Name có thể là:
x-api-token
Còn Credential Secret sẽ là token hoặc giá trị header tương ứng.
Bước 2: Lưu credential gốc ở nơi an toàn
Trong nhiều API không chuẩn, để lấy token bạn vẫn cần username/password, client id/client secret
hoặc một API key ban đầu. Các giá trị này phải được lưu ở nơi có kiểm soát.
Có nhiều lựa chọn tùy mức độ bảo mật của hệ thống:
- Dùng APEX Web Credentials nếu phù hợp.
- Dùng bảng riêng có mã hóa ở database.
- Dùng TDE để bảo vệ dữ liệu at-rest.
- Dùng Data Redaction để hạn chế lộ dữ liệu khi truy vấn.
- Dùng DBMS_CRYPTO nếu cần mã hóa chủ động ở tầng ứng dụng.
- Dùng external vault hoặc keystore nếu tổ chức có sẵn.
Với hệ thống doanh nghiệp, lựa chọn tốt nhất phụ thuộc vào chính sách bảo mật.
Nhưng dù chọn cách nào, cũng không nên để secret nằm trực tiếp trong source code.
Ví dụ bảng lưu cấu hình API
Ví dụ tối giản dưới đây dùng bảng cấu hình để lưu endpoint và thông tin kỹ thuật.
Phần secret trong môi trường thật nên được bảo vệ bằng cơ chế phù hợp như TDE, DBMS_CRYPTO hoặc vault.
create table api_auth_config (
config_code varchar2(50) primary key,
auth_url varchar2(1000) not null,
api_base_url varchar2(1000) not null,
username_value varchar2(255),
password_value varchar2(4000),
client_id_value varchar2(255),
client_secret_value varchar2(4000),
token_value clob,
token_expires_at timestamp with time zone,
created_at timestamp with time zone default systimestamp,
updated_at timestamp with time zone
);
Trong thực tế, bạn nên hạn chế quyền select trực tiếp vào bảng này.
Chỉ package xử lý API mới nên có quyền đọc các cột nhạy cảm.
Bước 3: Viết package lấy token
Package nên làm các việc sau:
- Đọc cấu hình API.
- Gọi login/auth endpoint.
- Parse JSON response.
- Lấy access token và thời hạn.
- Lưu token mới.
- Cập nhật Web Credential HTTP Header.
Ví dụ khung package:
create or replace package ext_api_auth_pkg as
procedure refresh_token;
procedure ensure_valid_token;
end ext_api_auth_pkg;
/
Phần body minh họa:
create or replace package body ext_api_auth_pkg as
procedure refresh_token is
l_response clob;
l_access_token varchar2(4000);
l_expires_in number;
l_header_value varchar2(5000);
begin
/*
1. Đọc credential gốc từ nơi lưu an toàn
2. Gọi auth endpoint bằng APEX_WEB_SERVICE
3. Parse JSON để lấy token
*/
l_response := apex_web_service.make_rest_request(
p_url => 'https://api.example.com/auth/login',
p_http_method => 'POST',
p_body => '{"username":"...","password":"..."}'
);
l_access_token := json_value(l_response, '$.access_token');
l_expires_in := json_value(l_response, '$.expires_in' returning number);
if l_access_token is null then
raise_application_error(-20001, 'API authentication failed: access token not found');
end if;
l_header_value := 'Bearer ' || l_access_token;
apex_credential.set_persistent_credentials(
p_credential_static_id => 'EXT_API_BEARER_TOKEN',
p_client_id => 'Authorization',
p_client_secret => l_header_value
);
update api_auth_config
set token_value = l_access_token,
token_expires_at = systimestamp + numtodsinterval(nvl(l_expires_in, 3600) - 60, 'SECOND'),
updated_at = systimestamp
where config_code = 'MAIN_API';
commit;
end refresh_token;
procedure ensure_valid_token is
l_token_expires_at timestamp with time zone;
begin
select token_expires_at
into l_token_expires_at
from api_auth_config
where config_code = 'MAIN_API';
if l_token_expires_at is null or l_token_expires_at <= systimestamp then
refresh_token;
end if;
exception
when no_data_found then
refresh_token;
end ensure_valid_token;
end ext_api_auth_pkg;
/
Đây chỉ là khung minh họa. Khi triển khai thật, bạn cần thay phần body request,
parse response, error handling và secure storage theo API cụ thể.
Lưu ý về APEX_CREDENTIAL.SET_PERSISTENT_CREDENTIALS
Khi dùng APEX_CREDENTIAL.SET_PERSISTENT_CREDENTIALS, credential được cập nhật ở mức persistent.
Điều này có nghĩa là các session hiện tại và tương lai có thể dùng giá trị mới.
Vì vậy, cách này phù hợp khi token là token dùng chung cho hệ thống hoặc service account.
Nếu mỗi end-user có token riêng, bạn cần cân nhắc kỹ hơn, vì persistent credential không phải lúc nào cũng phù hợp
cho token theo từng người dùng.
Bước 4: Gọi API sau khi đảm bảo token hợp lệ
Trước khi gọi API chính, bạn gọi:
begin
ext_api_auth_pkg.ensure_valid_token;
end;
Sau đó REST Data Source hoặc PL/SQL call có thể dùng Web Credential EXT_API_BEARER_TOKEN.
Nếu gọi bằng APEX_WEB_SERVICE, bạn có thể làm theo pattern:
declare
l_response clob;
begin
ext_api_auth_pkg.ensure_valid_token;
l_response := apex_web_service.make_rest_request(
p_url => 'https://api.example.com/v1/orders',
p_http_method => 'GET',
p_credential_static_id => 'EXT_API_BEARER_TOKEN'
);
-- xử lý response ở đây
end;
Nếu dùng REST Data Source, bạn cấu hình Web Credential cho data source,
rồi đảm bảo token được refresh trước khi region/process cần gọi data source.
Bước 5: Xử lý token hết hạn đột ngột
Không nên chỉ dựa vào thời gian hết hạn lưu trong database.
Một số API có thể revoke token sớm, thay đổi policy hoặc trả lỗi authentication bất ngờ.
Pattern tốt hơn:
- Gọi API bằng token hiện tại.
- Nếu nhận HTTP 401 hoặc 403 do token hết hạn, gọi refresh token.
- Thử lại request một lần.
- Nếu vẫn lỗi, ghi log và báo lỗi rõ ràng.
Không nên retry vô hạn, vì có thể làm chậm hệ thống hoặc gây loop lỗi.
Ví dụ wrapper gọi API có retry một lần
create or replace package ext_api_client_pkg as
function get_orders return clob;
end ext_api_client_pkg;
/
create or replace package body ext_api_client_pkg as
function get_orders return clob is
l_response clob;
l_status_code number;
begin
ext_api_auth_pkg.ensure_valid_token;
l_response := apex_web_service.make_rest_request(
p_url => 'https://api.example.com/v1/orders',
p_http_method => 'GET',
p_credential_static_id => 'EXT_API_BEARER_TOKEN'
);
l_status_code := apex_web_service.g_status_code;
if l_status_code in (401, 403) then
ext_api_auth_pkg.refresh_token;
l_response := apex_web_service.make_rest_request(
p_url => 'https://api.example.com/v1/orders',
p_http_method => 'GET',
p_credential_static_id => 'EXT_API_BEARER_TOKEN'
);
l_status_code := apex_web_service.g_status_code;
end if;
if l_status_code < 200 or l_status_code >= 300 then
raise_application_error(
-20002,
'API request failed. HTTP status = ' || l_status_code
);
end if;
return l_response;
end get_orders;
end ext_api_client_pkg;
/
Wrapper này giúp page process hoặc region source không phải tự xử lý token.
UI chỉ gọi package nghiệp vụ, còn package sẽ tự đảm bảo authentication.
Không log token hoặc password
Khi debug API, developer thường muốn log request và response.
Điều này hữu ích, nhưng rất nguy hiểm nếu log luôn header hoặc token.
Không nên:
apex_debug.message('Authorization = %s', l_header_value);
apex_debug.message('Password = %s', l_password);
Nên:
apex_debug.message('API auth token refreshed successfully.');
apex_debug.message('Token expires at = %s', to_char(l_expires_at, 'YYYY-MM-DD HH24:MI:SS'));
Khi cần troubleshooting, hãy log trạng thái, thời gian hết hạn, HTTP status, endpoint name,
correlation id hoặc request id. Không log secret.
Phân biệt token hệ thống và token theo user
Một điểm rất quan trọng là xác định token thuộc loại nào.
Nếu API dùng service account chung cho toàn hệ thống, persistent Web Credential có thể phù hợp.
Token được refresh bởi backend và mọi session dùng cùng token.
Nhưng nếu API token gắn với từng end-user, bạn không nên lưu token của user A vào credential persistent dùng chung.
Khi đó, cần thiết kế khác:
- Lưu token theo user/session trong bảng bảo mật riêng.
- Hoặc dùng session credential nếu phù hợp.
- Hoặc triển khai OAuth flow đúng chuẩn nếu API hỗ trợ.
- Đảm bảo user chỉ dùng token của chính họ.
Sai lầm ở đây có thể dẫn đến lỗi nghiêm trọng: user này gọi API bằng token của user khác.
REST Data Source hay APEX_WEB_SERVICE?
Nếu authentication đã được xử lý ổn và token có thể đưa vào Web Credential,
REST Data Source là lựa chọn rất tốt vì có thể dùng declarative trong APEX:
- Dễ dùng với report, form, LOV.
- Có metadata rõ ràng.
- Dễ bảo trì hơn so với nhiều đoạn PL/SQL rời rạc.
- Tận dụng được các cấu hình sẵn của APEX.
Nhưng nếu API quá đặc biệt, cần ký request phức tạp, cần retry logic riêng,
cần xử lý pagination tùy biến hoặc response không ổn định, bạn có thể dùng APEX_WEB_SERVICE
trong package PL/SQL riêng.
Một pattern tốt là:
- Dùng REST Data Source cho các API đọc dữ liệu đơn giản.
- Dùng PL/SQL package wrapper cho API phức tạp.
- Không để logic gọi API nằm trực tiếp trong nhiều page process khác nhau.
Token refresh nên chạy khi nào?
Có vài cách:
- Refresh token ngay trước mỗi request nếu token đã hết hạn.
- Refresh token bằng Automation định kỳ trước khi hết hạn.
- Refresh token khi gặp lỗi 401/403 và retry một lần.
- Kết hợp cả kiểm tra hết hạn và retry khi lỗi.
Với hệ thống có nhiều request, cách tốt là lưu token_expires_at
và chỉ refresh khi gần hết hạn. Điều này giảm số lần gọi auth endpoint.
Automation refresh token
Nếu token có hạn cố định, bạn có thể tạo APEX Automation để gọi:
begin
ext_api_auth_pkg.ensure_valid_token;
end;
Automation giúp token luôn sẵn sàng trước khi người dùng mở page.
Tuy nhiên, vẫn nên giữ retry logic khi API trả 401/403, vì token có thể bị revoke trước hạn.
Bảo mật bảng lưu token
Nếu bạn lưu token hoặc credential trong bảng riêng, hãy kiểm soát chặt:
- Không cấp quyền select rộng rãi.
- Không đưa bảng này vào report/admin page nếu không thật sự cần.
- Không export dữ liệu token ra file log.
- Cân nhắc mã hóa cột nhạy cảm.
- Cân nhắc Data Redaction nếu nhiều người có quyền truy vấn.
- Ghi audit khi token được refresh hoặc credential thay đổi.
TDE giúp bảo vệ dữ liệu ở trạng thái lưu trữ trên disk, nhưng không tự động ngăn người có quyền SELECT
đọc dữ liệu ở dạng rõ. Vì vậy vẫn cần kiểm soát quyền truy cập ở database và application.
Xử lý lỗi rõ ràng cho người dùng
Khi API authentication lỗi, không nên hiển thị thông báo kỹ thuật quá chi tiết cho end-user.
Không nên hiển thị:
Invalid client_secret: abc123...
Nên hiển thị:
Không thể kết nối đến dịch vụ bên ngoài. Vui lòng thử lại sau hoặc liên hệ quản trị viên.
Chi tiết kỹ thuật nên được ghi vào log nội bộ, nhưng vẫn phải tránh ghi secret.
Checklist triển khai
Trước khi đưa tích hợp API authentication không chuẩn vào production, hãy kiểm tra:
- Credential gốc có bị hard-code trong source không?
- Token có bị log ra debug/application log không?
- Web Credential đã được cấu hình đúng header chưa?
- Token refresh có xử lý lỗi không?
- Có retry một lần khi gặp 401/403 không?
- Có phân biệt token hệ thống và token theo user không?
- Bảng lưu cấu hình/token có bị cấp quyền quá rộng không?
- Có cách đổi credential giữa dev/test/prod không?
- Có thông báo lỗi thân thiện cho end-user không?
- Có audit hoặc log đủ để troubleshooting không?
Best practice
- Ưu tiên Web Credentials và REST Data Source nếu có thể.
- Tách authentication logic vào package riêng.
- Tách API client logic vào package riêng.
- Không hard-code secret trong PL/SQL, JavaScript hoặc page item.
- Không lưu token trong browser nếu không cần.
- Không log password, token, API key hoặc client secret.
- Refresh token có kiểm soát, không retry vô hạn.
- Dùng readonly hoặc least-privilege cho schema gọi API nếu phù hợp.
- Review kỹ cách di chuyển credential giữa môi trường dev, test và prod.
- Kiểm tra security trước khi đưa production.
Kết luận
Không phải REST API nào cũng dùng authentication chuẩn mà Oracle APEX hỗ trợ trực tiếp.
Nhưng điều đó không có nghĩa là chúng ta phải hard-code token hoặc password trong code.
Cách làm tốt hơn là thiết kế một lớp authentication riêng:
lưu credential gốc ở nơi an toàn, dùng PL/SQL package để lấy và refresh token,
cập nhật token vào Web Credential khi phù hợp, rồi để REST Data Source hoặc APEX_WEB_SERVICE
sử dụng credential đó khi gọi API.
Với cách tiếp cận này, ứng dụng APEX vừa xử lý được các API authentication “khó chịu”,
vừa giữ được cấu trúc dễ bảo trì, dễ troubleshooting và an toàn hơn.
Điểm quan trọng nhất vẫn là kỷ luật bảo mật:
không hard-code secret, không log token, giới hạn quyền truy cập,
phân biệt token hệ thống với token theo user, và luôn review kỹ trước khi đưa vào production.