JWT encoding, decoding, and using with APEX_WEB_SERVICE

This page contains information on how to create JWT tokens with DidiSoft OraRSA.

JSON Web tokens, (JWT) are often used for authentication when accessing REST web service.

JWT tokens can be signed with a shared secret (HMAC) and with RSA private keys. (For HMAC encoding we recommend the JWT_NINJA package). Here you will find how to create RSA signed JWT tokens with PL/SQL and use them with APEX_WEB_SERVICE.

JWT payload structure

A JSON web token holds information for an entity (aka claims). The information is stored in a part called payload. A sample payload looks like this:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

You can check the sample PL/SQL routine jwt_generate_payload that creates a common payload or modify it for your use case.

Signing a JWT token

ORA_RSA.JWT_SIGN can create a default header or it can be specified in an overloaded version of the method.

In order to create a JWT token, we need a payload and an RSA private key for the signing operation. The hash function to be used for the digital signature is one of the supported hashes in ORA_RSA.

The example below illustrates how to invoke ORA_RSA.JWT_SIGN without specifying a header. In this case, the header will contain only the alg and typ fields, like: { “alg”: “RS256″,”typ”: “JWT”}

DECLARE
	l_private_key CLOB := '-----BEGIN PRIVATE KEY-----
	MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMJOVHMWQpGWTiR7
	F5z3WQIIvJYUDubZAPUxzj2NgriomhnowjgiK40Q2UPNxgBq3wlMhMpAp01+Or6R
	2mkOctOZ55Qjgm2VzTovW4MuOLWGB43fXm3qDe05/SIdV3K3h2KVwcNqecJTR25T
	RNeGHo4r8cce3BnuVzEKjk2DNQOXAgMBAAECgYEAuqIMQaL+++IYWrgU/UMkLmz/
	31OS4K9NWTamt77F8eKYagyFCO/hTxUA6zyqU9pTMxZZcf9Z83gsqsFjvYcQSHy6
	mRXFuORzh0r/wXKJtyFF0B26KC7WipqtPAuzn7SNGNeMh8g3H1qH8neEjir15Uai
	6lR/sDIOZlO9sUJoZBECQQDkLXnXl/YXGoQDdupUQMzrF+ZK/od2U9YjdSOi+k/j
	x23usurtzRhYGW/73vJd9Sw6Qc6ijPr+ItSpnl+qaxzvAkEA2f+OVzn1HwmYbc2a
	Booo32aT96TJrwN8V4gC7m5hseHoXDDoXmwLZwNm7+w0vu3lk1p9tSqs8oc/nR0E
	fHhT2QJAOQslasCSxTPbzQHtkyKgGCXhbN40/1/2KOcgAZ6SWl+BHCuej9S2QVAa
	rt0Num+Qnv/UqM6V8PLEN6NgRzqAAQJBALeQYrp+WjKNcOYc97LECdC73qLsBswx
	QjWumNFO70LLOE7Q/AnuLtfKXJZwrqWLSwJ+c1XnHoSGcIGK2qk45VkCQA6b1qCv
	jGFksgcQ8vff5lwOWfJ2ZxA8Zpgeq5w7EaDTWS/WhtVUYg3bBsadgXb3LxpZScxq
	U4Ad7pAZrI6H6Tc=
	-----END PRIVATE KEY-----';
 
  l_payload_data          varchar2(32000);
 
  l_jwt CLOB;
BEGIN
 
	l_payload_data := '{
         "sub": "1234567890",
         "name": "John Doe",
         "admin": true
        }';
 
  l_jwt := ORA_RSA.JWT_SIGN(l_payload_data, 
				UTL_RAW.CAST_TO_RAW(l_private_key), 
				ORA_RSA.HASH_SHA256);
 
  -- now we can use l_signature with APEX_WEB_SERVICE
END

Now the l_jwt variable holds the JWT token and can be set as an Authorization header in APEX_WEB_SERVICE.

Signing a JWT token with a custom header

The JWT specification contains a lot of optional headers. You can fill them, by explicitly passing a custom header to ORA_RSA.JWT_SIGN.

For filling in details for the verification key, we can use the X.509 certificate properties.

DECLARE
  l_crt CLOB := ...;
 
  l_private_key CLOB := ...;
 
  l_kid                 varchar2(32000);
  l_header_data         varchar2(32000);
  l_payload_data        varchar2(32000);
 
  l_jwt CLOB; -- signed JWT token
BEGIN
 
  l_kid := ORA_RSA.X509_SHA1_THUMBPRINT(UTL_RAW.CAST_TO_RAW(l_crt));
  l_header_data := '{
    "typ": "JWT",
    "alg": "RS256",
    "kid": ' || l_kid || '
  }';
 
  l_payload_data := '{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true,
    "iat": 1516239022
  }';
 
  l_jwt := ORA_RSA.JWT_SIGN(l_payload_data, UTL_RAW.CAST_TO_RAW(l_private_key), ORA_RSA.HASH_SHA256);
 
  dbms_output.put_line('Signature:');                             
  dbms_output.put_line(l_jwt);       
END;

Verifying and decoding a JWT

A JWT token can be verified with ORA_RSA.JWT_VERIFY if we have the public RSA key corresponding to the private signing key. We can also extract the payload data with ORA_RSA.JWT_DECODE.

DECLARE
  public_key CLOB := '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCTlRzFkKRlk4kexec91kCCLyW
FA7m2QD1Mc49jYK4qJoZ6MI4IiuNENlDzcYAat8JTITKQKdNfjq+kdppDnLTmeeU
I4Jtlc06L1uDLji1hgeN315t6g3tOf0iHVdyt4dilcHDannCU0duU0TXhh6OK/HH
HtwZ7lcxCo5NgzUDlwIDAQAB
-----END PUBLIC KEY-----';
 
  l_jwt CLOB := ... ;
  l_signature_check_result BOOLEAN;     
BEGIN
  dbms_output.put_line('Payload:');                             
  dbms_output.put_line(ORA_RSA.JWT_DECODE(l_jwt));       
 
  dbms_output.put_line('Verify JWT signature:');                  
  l_signature_check_result := ORA_RSA.JWT_VERIFY(l_jwt, UTL_RAW.CAST_TO_RAW(l_public_key));
  IF l_signature_check_result THEN
    dbms_output.put_line('Signature valid');       
  ELSE    
   dbms_output.put_line('Signature invalid');        
  END IF;
END

Using a JWT with APEX_WEB_SERVICE

After we have created a signed JWT token we have to set it as APEX_WEB_SERVCE Authorization header in order to authenticate against remote REST API services:

  apex_web_service.g_request_headers(1).name := 'Content-Type';
  apex_web_service.g_request_headers(1).value := 'application/vnd.oracle.adf.resourceitem+json';
  apex_web_service.g_request_headers(2).name := 'Authorization';
  apex_web_service.g_request_headers(1).value := l_jwt;

The l_jwt variable is the one produced in the JWT signing example.

Sample jwt_generate_payload

Each REST API has its own requirements on the JWT payload contents. The sample routine below illustrates the most common claims. Interesting is that the dates must be of type number counting the seconds from the Unix epoch. (see below the default value for iat)

CREATE OR REPLACE FUNCTION jwt_generate_payload (
    p_header_alg              IN          varchar2 
    , p_header_typ            IN          varchar2 
    , p_header_cty            IN          varchar2 
    , p_reg_claim_issuer      IN          varchar2 
    , p_reg_claim_subject     IN          varchar2 
    , p_reg_claim_audience    IN          varchar2 
    , p_reg_claim_expiration  IN          NUMBER 
    , p_reg_claim_notbefore   IN          NUMBER 
    , p_reg_claim_issuedat    IN          NUMBER 
    , p_reg_claim_jwtid       IN          varchar2
  )
  RETURN varchar2
AS
    l_sys_date           NUMBER;
    l_payload_data       varchar2(32000);
BEGIN
    l_sys_date := (sysdate()- to_date('1-1-1970 00:00:00','MM-DD-YYYY HH24:Mi:SS'))*24*60*60;
 
    l_payload_data := '{';
    IF p_reg_claim_issuedat IS NOT NULL THEN
      l_payload_data := l_payload_data || ' "iat": ' || to_char(p_reg_claim_issuedat);
    ELSE
      l_payload_data := l_payload_data || ' "iat": ' || to_char(l_sys_date);
    END IF;
    IF p_reg_claim_issuer IS NOT NULL THEN
      l_payload_data := l_payload_data || ', "iss": "'|| p_reg_claim_issuer ||'"';
    END IF;
    IF p_reg_claim_subject IS NOT NULL THEN
      l_payload_data := l_payload_data || ', "sub": "'|| p_reg_claim_subject ||'"';
    END IF;
    IF p_reg_claim_audience IS NOT NULL THEN
      l_payload_data := l_payload_data || ', "aud": "'|| p_reg_claim_audience ||'"';
    END IF;
    IF p_reg_claim_expiration IS NOT NULL THEN
      l_payload_data := l_payload_data || ', "exp": '|| p_reg_claim_expiration;
    END IF;
    IF p_reg_claim_notbefore IS NOT NULL THEN
      l_payload_data := l_payload_data || ', "nbf": '|| p_reg_claim_notbefore;
    END IF;
    IF p_reg_claim_jwtid IS NOT NULL THEN
      l_payload_data := l_payload_data || ', "jti": "'|| p_reg_claim_jwtid ||'"';
    END IF;
    l_payload_data := l_payload_data || '}';
 
    RETURN l_payload_data;
END jwt_generate_payload;

 

Summary

This article was an introduction how to create and consume RSA signed JWT tokens with PL/SQL. This information can be helpful to PL/SQL developers that need to access REST web services from inside the Oracle© database.