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
- Signing a JWT
- Signing a JWT with a custom header
- Verifying and decoding a JWT
- Using a JWT with APEX_WEB_SERVICE
- Sample jwt_generate_payload
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 and the header with ORA_RSA.JWT_DECODE_HEADER.
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('Header:'); dbms_output.put_line(ORA_RSA.JWT_DECODE_HEADER(l_jwt)); 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.