MDRTKNAPI RPGLE
The following source code example is available in MDRST/EXAMPLE/MDRTKNAPI. It's is an API which accepts a POST method and can be used to carry out the various tasks associated with creating and managing tokens
**FREE
// This example API shows how to create the token
// CRTSQLRPGI PGM(MDRTST14/MDRTKNAPI) SRCFILE(MDRTST14/EXAMPLE) -
// SRCMBR(MDRTKNAPI) OBJTYPE(*PGM) DBGVIEW(*SOURCE) TGTRLS(*CURRENT)
ctl-opt dftactgrp(*no) actgrp(*caller) option(*srcstmt)
bnddir('MDRFRAME');
/copy MDRFRAME
/copy MDRTOKEN
/copy MDRYAJL_H
dcl-ds dftExpiry dtaara('MDRTKNEXP') qualified;
tokenExpPeriod char(10);
expUnit char(10);
refreshExpPeriod char(10);
end-ds;
dcl-pi *n;
handle like(MDR_Handle_t);
method char(32) const;
body varchar(500000) ccsid(*utf8); // MAxSize: 16000000
end-pi;
select;
when method = 'POST';
mdr_post();
endsl;
*inlr = *on;
return;
//----------------------------------------------------------------------------
// Process post Method
//----------------------------------------------------------------------------
dcl-proc mdr_post;
// Define common use variables
dcl-s errorMsg varchar(100:4);
// Variable declaration to parse json string
dcl-s docNode pointer;
// Variables used to fetch Json generated by YAJL functions
dcl-s jsonDataPtr pointer;
dcl-s jsonPtrLen uns(10);
dcl-ds creds likeds(MDRDCRED_t);
dcl-s errMsg varchar(200);
dcl-s newEpochTime int(20);
dcl-s baseEpochTime int(20);
dcl-s success ind;
dcl-s setSeconds ind;
dcl-s claimsExpFound ind;
dcl-s claimsIatFound ind;
dcl-s LocalIdx1 int(10);
dcl-s Entrykeyval varchar(1024);
dcl-s KeyNodePtr pointer;
dcl-s ExpDuration int(10);
dcl-ds claims_ds qualified dim(99);
key varchar(50);
value varchar(4096);
end-ds;
// no of clains in JWT claims payload
dcl-s claimsCount int(5) inz(1);
// Clear the credentials data struture first
clear creds;
// Fetch the header parameters
// jwtalgorithm can have below possible values
// RS256-DCM : Create RS256 token using certificate from DCM application
// in digital certificate manager of your IBMi machine
// RS256-KST: Create RS256 using keystore file. It requires keystore file
// library and label
// RS256-PVT: Create RS256 token using public and private keys. Not yet
// implemented
// HS256: Create HS256 token. It requires clientSecret as 32 byte key for
// encryption
// UUID: Create a token as random 36 byte characters
if MDR_getHeader(handle: 'x-jwtalgo') <> '*NOTFOUND';
creds.algor = MDR_getHeader(handle: 'x-jwtalgo');
endif;
// Client Id and application ID are any unique identifiers for making the
// DB entry for the token being generated
if MDR_getHeader(handle: 'x-clientId') <> '*NOTFOUND';
creds.clientid = MDR_getHeader(handle: 'x-clientId');
endif;
if MDR_getHeader(handle: 'x-appId') <> '*NOTFOUND';
creds.appid = MDR_getHeader(handle: 'x-appId');
endif;
// DCM application name from digital certificate manager. Required when
// RS256-DCM algorithm is used
if MDR_getHeader(handle: 'x-dcmApp') <> '*NOTFOUND';
creds.dcmapp = MDR_getHeader(handle: 'x-dcmApp');
endif;
// Token type can be JWT: Json Web Token, PAT: Personal Access Token or
// JWS: Json Web Signature
if MDR_getHeader(handle: 'x-tokenType') <> '*NOTFOUND';
creds.tkntyp = MDR_getHeader(handle: 'x-tokenType');
endif;
// Client Secret is any 32 byte string required when HS256 algorithm is
// supplied for token generation
if MDR_getHeader(handle: 'x-clientSecret') <> '*NOTFOUND';
creds.cliSecret = MDR_getHeader(handle: 'x-clientSecret');
endif;
// Assign the JSON request body as claims
creds.claims = body;
// Below three blocks show the retrieval of token expiry unit, auth token
// expiry and refresh token expiry. You can also supply them fixed values
// instead requesting from the API user to provide these values
// Extract expiry unit from the http header. It can be "*seconds" (or "*s"),
// "*minutes" (or "*m"), "*hours" (or "*h"), "*days" (or "*d")
creds.expiryUnit = MDR_getHeader(handle:'x-expiryUnit');
if creds.expiryUnit <> '*seconds' and creds.expiryUnit <> '*minutes' and
creds.expiryUnit <> '*hours' and creds.expiryUnit <> '*days' and
creds.expiryUnit <> '*s' and creds.expiryUnit <> '*m' and
creds.expiryUnit <> '*h' and creds.expiryUnit <> '*d';
// If invalid token expiry has been supplied, use *seconds. We can
// also throw an error alternatively depending on requirement
clear creds.expiryUnit;
endif;
// Supply auth token expiry and refresh token expiry. If not supplied,
// auth token expiry is taken as 180 seconds and refresh token expiry
// as one day and the unit value is calculated in seconds
if MDR_getHeader(handle: 'x-authTokenExpiry') <> '*NOTFOUND';
monitor;
creds.atokenExp = %dec(MDR_getHeader(handle:'x-authTokenExpiry'):12:0);
on-error;
// Just in case non-numeric value is supplied from header, set to 180
// and likewise, set the expiry unit as "*seconds" even if any other
// expiryUnit value is supplied from query parameter
creds.atokenExp = 0;
endmon;
else;
// Query parameter for authTokenExpiry not supplied. Set to some default
// value which is being set as 180 seconds in this example
creds.atokenExp = 0;
endif;
if MDR_getHeader(handle: 'x-refreshTokenExpiry') <> '*NOTFOUND';
monitor;
creds.rtokenExp = %dec(MDR_getHeader(handle:'x-refreshTokenExpiry'):12:0);
on-error;
// If non-numeric value is supplied for the refresh token, set as 1800 seconds
creds.rtokenExp = 0;
endmon;
else;
// Query parameter for refreshTokenExpiry not supplied. Set to some default
// value which is being set as 1800 seconds in this example
creds.rtokenExp = 0;
endif;
// Fetch data area if the auth or refresh token expiry not supplied or
// expiry duration is blank
if creds.atokenExp = *zeros or creds.rtokenExp = *zeros or
creds.expiryUnit = *blanks;
In(e) dftExpiry;
if %error;
errorMsg = 'MDRTKNEXP data area not found in *LIBL';
MDR_setError( handle: 'MDRTKNAPI': errorMsg: 166: 30 : 221);
*inlr = *On;
return;
endif;
endif;
if creds.expiryUnit = *blanks;
creds.expiryUnit = dftExpiry.expUnit;
endif;
// If for some reason, the expiry unit is still blank, set it to *seconds
if creds.expiryUnit = *blanks;
creds.expiryUnit = '*seconds';
endif;
if creds.atokenExp = *zeros;
monitor;
ExpDuration = %dec(dftExpiry.tokenExpPeriod:10:0);
on-error;
ExpDuration = 0;
endmon;
creds.atokenExp = ExpDuration;
endif;
if creds.rtokenExp = *zeros;
monitor;
ExpDuration = %dec(dftExpiry.refreshExpPeriod:10:0);
on-error;
ExpDuration= 0;
endmon;
creds.rtokenExp = ExpDuration;
endif;
// Logic to parse request body. The purpose here is just to make sure
// its a valid JSON and then extract claims info
if body <> *blanks;
docNode = MDR_tree_parse( handle: *omit: *omit: errorMsg);
endIf;
if body <> *blanks and docNode = *null;
MDR_setError( handle: 'MDRTKNAPI': errorMsg: 211: 30 : 433);
*inlr = *On;
return;
endIf;
// Thsi logic exttrcats all of the name value pairs
dow Yajl_Object_loop2(docNode:LocalIdx1:Entrykeyval:KeyNodePtr);
claims_ds(claimsCount).key = entryKeyVal;
if yajl_is_number(keyNodePtr);
claims_ds(claimsCount).value = yajl_getNumStr(keyNodePtr);
elseif yajl_is_string(keyNodePtr);
claims_ds(claimsCount).value = yajl_get_string(keyNodePtr);
elseif yajl_is_true(keyNodePtr);
claims_ds(claimsCount).value = 'true';
elseif yajl_is_false(keyNodePtr);
claims_ds(claimsCount).value = 'false';
elseif yajl_is_null(keyNodePtr);
claims_ds(claimsCount).value = 'null';
endif;
// validate claims exp value to be valid and greater than current
// epoch timestamp
if entryKeyVal = 'exp';
newEpochTime = CvtToEpochTimestamp(%timestamp());
claimsExpFound = *On;
monitor;
creds.claimsExp = %dec(claims_ds(claimsCount).value:10:0);
on-error;
creds.claimsExp = *zeros;
errorMsg = 'Invalid claim value submitted: conversion failed';
MDR_setError( handle: 'MDRTKNAPI': errorMsg: 237: 30 : 221);
*inlr = *On;
return;
endmon;
if creds.ClaimsExp < newEpochTime;
errorMsg = 'Invalid claim value submitted: exp is less than current time';
MDR_setError( handle: 'MDRTKNAPI': errorMsg: 246: 30 : 221);
*inlr = *On;
return;
endif;
endif;
// validate claims iat value to be valida and greater than the
// timestamp after 1970-01-01-00.00.00.000000
if entryKeyVal = 'iat';
baseEpochTime = CvtToEpochTimestamp(%timestamp('1970-01-01-00.00.00.000000'));
newEpochTime = CvtToEpochTimestamp(%timestamp()+ %years(1));
claimsIatFound = *On;
monitor;
creds.claimsIat = %dec(claims_ds(claimsCount).value:10:0);
on-error;
creds.claimsIat = *zeros;
errorMsg = 'Invalid claim value submitted: iat';
MDR_setError( handle: 'MDRTKNAPI': errorMsg: 261: 30 : 221);
*inlr = *On;
return;
endmon;
if creds.ClaimsIat < baseEpochTime or creds.ClaimsIat > newEpochTime or
%scan('.':claims_ds(claimsCount).value) > *zeros or creds.claimsIat < 0;
errorMsg = 'Invalid claim value submitted: iat';
MDR_setError( handle: 'MDRTKNAPI': errorMsg: 269: 30 : 221);
*inlr = *On;
return;
endif;
endif;
claimsCount += 1;
enddo;
// If both the "iat" and "exp" are not found
if claimsIatFound = *Off and claimsExpFound = *Off;
errorMsg = 'Mandatory claims are either: iat OR exp';
MDR_setError( handle: 'MDRTKNAPI': errorMsg: 281: 30 : 221);
*inlr = *On;
return;
elseif claimsExpFound = *off;
ExpDuration = creds.atokenExp;
creds.claimsExp = CalculateNewEpochTime(creds.claimsIat:ExpDuration:
creds.expiryUnit);
endif;
// At this point, all the values are loaded, proceed to create the token
success = MDR_createToken(creds:errMsg);
// Logic to generate response
// Start writing the JSON using YAJL functions.
mdr_startJson(*OFF:*OFF);
mdr_beginObject();
if success = *On;
mdr_addChar('authToken':creds.authtoken);
mdr_addNum('authTokenExpiry':%char(creds.atokenexp));
mdr_addChar('refreshToken':creds.refreshtkn);
mdr_addNum('refreshTokenExpiry':%char(creds.rtokenexp));
mdr_addChar('expiryUnit':%trim(creds.expiryUnit));
else;
mdr_addChar('status':'Token generation failed');
mdr_addChar('message':%trim(errMsg));
endif;
mdr_endObject();
// Retrieve JSON loaded in buffer memory
mdr_getJsonptr(jsonDataPtr:jsonPtrLen);
// Write the JSON data to standard output as response
mdr_len_write(handle:jsonDataPtr:jsonPtrLen);
// Release the memory allocated by JSON generator
mdr_endJson();
end-proc;