Did you know that 81% of data breaches are caused by weak or stolen passwords? As developers, we've all faced the challenge of securing user authentication while maintaining a smooth user experience. Enter passkeys—the next generation of passwordless authentication built on the WebAuthn standard. This post explains how we implemented passkeys in Oracle APEX and introduces a custom plugin that makes integration seamless for developers.
Before diving into implementation, ensure you have:
Passkeys represent a revolutionary approach to authentication that eliminates traditional passwords. They leverage public-key cryptography, where private keys are securely stored on the user's device, and public keys are stored in the application's backend. With passkeys, users authenticate with something they are (biometric data) or have (a security key), making passwords obsolete.
We used the AS_CRYPTO package to handle all cryptographic operations for passkey validation, including hashing, signature verification, and public key decoding. Here’s how each major step works:
The application must create a challenge and configure the public_Key options for
navigator.credentials.create(). Once a user registers, the public key and credential ID are stored in a secure table.
Example Table Schema
CREATE TABLE biometrics_credentials (
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
user_id VARCHAR2(255) NOT NULL,
credential_id VARCHAR2(255) NOT NULL,
public_key CLOB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL,
last_used_at TIMESTAMP WITH TIME ZONE
);
Example Code for Challenge Generation (PL/SQL)
DECLARE
l_challenge RAW(32);
BEGIN
l_challenge := AS_CRYPTO.RANDOMBYTES(32);
-- Convert to base64url for JSON usage
DBMS_OUTPUT.PUT_LINE(AS_CRYPTO.ENCODE_BASE64URL(l_challenge));
END;
Client-Side Code for Enrollment
const options = {
publicKey: {
challenge: Uint8Array.from(window.atob('YOUR_BASE64_CHALLENGE'), c => c.charCodeAt(0)),
rp: {
name: "Your App Name",
id: window.location.hostname
},
user: {
id: Uint8Array.from("USER_ID", c => c.charCodeAt(0)),
name: "username",
displayName: "User Display Name"
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 } // RS256
],
authenticatorSelection: {
userVerification: "preferred",
residentKey: "preferred"
},
timeout: 60000
}
};
try {
const credential = await navigator.credentials.create(options);
// Send credential to server and securely store the credential ID and public key
await submitCredentialToServer(credential);
} catch (err) {
console.error('Enrollment failed:', err);
// Handle error appropriately
}
Authentication requires fetching the stored public_key and verifying the signature.
Server-Side Signature Validation
DECLARE
l_client_data_hash RAW(32);
l_data_to_verify RAW(32767);
l_signature RAW(64);
l_public_key RAW(32767);
l_verification_result BOOLEAN;
BEGIN
-- Hash the client data
l_client_data_hash := AS_CRYPTO.HASH(
AS_CRYPTO.ENCODE('{"type":"webauthn.get"}'),
AS_CRYPTO.HASH_SH256
);
-- Combine authenticator data and client data hash
l_data_to_verify := UTL_RAW.CONCAT(
:authenticator_data,
l_client_data_hash
);
-- Verify the signature
l_verification_result := AS_CRYPTO.VERIFY(
l_data_to_verify, -- expected value (original msg)
:raw_signature, -- signature
l_public_key, -- encoded public key
AS_CRYPTO.KEY_TYPE_EC, -- key algo
AS_CRYPTO.SIGN_SHA256withECDSAinP1363 -- key algo
);
IF NOT l_verification_result THEN
RAISE_APPLICATION_ERROR(
-20001,
'Signature validation failed.'
);
END IF;
END;
Example Code for Successful Authentication Handler
-- Example of successful authentication handler
CREATE OR REPLACE PROCEDURE handle_passkey_auth (
p_credential_id IN RAW,
p_user_id IN NUMBER
) AS
l_exists NUMBER;
BEGIN
-- Verify credential exists and is valid
SELECT 1
INTO l_exists
FROM biometrics_credentials
WHERE credential_id = p_credential_id
AND user_id = p_user_id
AND created_at > SYSDATE - 365; -- Expire after 1 year
-- Set up session state
APEX_AUTHENTICATION.POST_LOGIN(
p_username => p_user_id
);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid credential');
END;
Manual implementation requires handling a range of complex operations, from constructing WebAuthn JSON options to securely managing cryptographic processes. The Oracle APEX Passkey Authentication Plugin simplifies these tasks:
Follow these steps to use the plugin, referencing the full documentation provided in our README:
Prerequisites
Step 1: Plugin Installation
Step 2 (optional): Database Setup
Create the credential storage table with the following structure:
CREATE TABLE biometrics_credentials (
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
user_id VARCHAR2(255) NOT NULL,
credential_id VARCHAR2(255) NOT NULL,
public_key CLOB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL,
last_used_at TIMESTAMP WITH TIME ZONE
);
Step 3: Dynamic Action Setup
After installing the plugin, you'll need to set up the necessary dynamic actions to handle both enrollment and authentication. Here's a detailed guide on configuring each:
-- Store the credential after successful enrollment
INSERT INTO biometrics_credentials (
user_id,
credential_id,
public_key
) VALUES (
:APP_USER,
:BIOMETRICS_CREDENTIAL_ID,
:BIOMETRICS_PUBLIC_KEY
);
Optionally, you can also set up an "Error" handler to manage enrollment failures according to your application's needs.
BEGIN
-- Retrieve stored credential for verification
SELECT public_key, user_id
INTO :BIOMETRICS_PUBLIC_KEY, :BIOMETRICS_USER_ID
FROM biometrics_credentials
WHERE credential_id = :BIOMETRICS_CREDENTIAL_ID;
END;
-- Update last used timestamp after successful authentication
UPDATE biometrics_credentials
SET last_used_at = SYSTIMESTAMP
WHERE credential_id = :BIOMETRICS_CREDENTIAL_ID;
Our benchmarks show:
Implementing passkeys in Oracle APEX represents a significant step forward in application security and user experience. Our plugin makes this transition seamless, allowing developers to focus on building great applications rather than wrestling with authentication complexity.
The future of authentication is passwordless, and with tools like this, that future is already here. We encourage you to try the plugin, share your feedback, and join us in making the web more secure and user-friendly.
Download the plugin from our GitHub repository and join our community of developers building secure, passwordless applications with Oracle APEX.
This plugin was created by @kevintech and @viscosityna, who continue to maintain and improve it based on community feedback.
Have questions or feedback? Join our discussion forum or open an issue on GitHub.