OAuth 2.0 and JWT Authentication from IBM i: Client Credentials Flow, Bearer Token Management, and Calling OAuth-Protected REST APIs from ILE RPG in 2026

The previous post covered DB2 for i query optimization — the SQE and CQE query engines, Visual Explain in ACS for analyzing query plans, index design strategies including encoded vector indexes, STRDBMON for capturing optimizer data, reading the SQL plan cache with QSYS2 catalog views, and collecting statistics to prevent stale optimizer decisions. This post covers OAuth 2.0 and JWT authentication from IBM i: implementing the client credentials flow from ILE RPG, managing Bearer tokens, calling OAuth-protected REST APIs using DB2 HTTP functions and HTTPAPI, understanding JWT structure, and managing SSL certificates in the IBM i digital certificate store in 2026.

OAuth 2.0 Flows and IBM i Use Cases

OAuth 2.0 is the industry-standard protocol for delegated API authorization. Most modern REST APIs — Salesforce, Microsoft 365, ServiceNow, Workday, SAP, AWS — require OAuth 2.0 Bearer tokens rather than basic username/password authentication. IBM i programs that call external REST APIs must implement OAuth 2.0 to authenticate.

The two OAuth 2.0 flows relevant to IBM i server-to-server integrations:

  • Client Credentials — the IBM i application authenticates directly as itself using a client ID and client secret. No user interaction. This is the correct flow for batch jobs, RPG service programs, and CL programs that call external APIs on behalf of the system (not on behalf of an individual user). Used with: Salesforce Connected Apps, Microsoft Entra app registrations, Google Service Accounts.
  • Authorization Code + PKCE — requires a browser redirect and user login. This flow is not applicable to IBM i batch/RPG programs directly; it is used when building web applications that run on IBM i (Node.js/Python in PASE) that authenticate real users.

HTTPS from IBM i — Options

Before implementing OAuth, you need a mechanism for making HTTPS POST and GET requests from IBM i. There are three main options in 2026:

  • HTTPAPI — an open-source ILE C service program by Scott Klement; callable from RPG. Supports HTTPS, POST with body, custom headers. The most commonly used option for RPG-based HTTP calls. Requires installation from the HTTPAPI project.
  • DB2 for i HTTP functions (SYSTOOLS.HTTPGETCLOB, SYSTOOLS.HTTPPOSTCLOB) — SQL table functions built into DB2 for i 7.3+. No additional software needed. Callable from any SQL context: embedded SQL in RPG, SQL scripts, stored procedures. Limited compared to HTTPAPI but sufficient for many OAuth use cases.
  • PASE curl / Python requests / Node.js axios — PASE-based HTTP clients. Full-featured, support all OAuth flows, but require the program logic to be in PASE (Python/Node.js) rather than ILE RPG.

Client Credentials Flow Using DB2 HTTP Functions

The simplest approach for many shops: use DB2 for i’s built-in HTTP functions to get an OAuth token and call APIs, entirely from SQL embedded in RPG:

**FREE
// APPLIB/SFTOKEN — Get Salesforce OAuth token using client credentials
// Requires: IBM i 7.3+, SYSTOOLS HTTP functions

ctl-opt dftactgrp(*no) actgrp('SFGRP');

dcl-s wTokenResp  varchar(4096);
dcl-s wAccessTok  varchar(2048);
dcl-s wTokenType  varchar(50);
dcl-s wExpiresIn  int(10);

dcl-c SF_TOKEN_URL  'https://login.salesforce.com/services/oauth2/token';
dcl-c SF_CLIENT_ID  'your_connected_app_client_id';
dcl-c SF_CLIENT_SEC 'your_connected_app_client_secret';

// Step 1: POST to the token endpoint using SYSTOOLS.HTTPPOSTCLOB
exec sql
  SET :wTokenResp = SYSTOOLS.HTTPPOSTCLOB(
    :SF_TOKEN_URL,
    'Content-Type: application/x-www-form-urlencoded',
    'grant_type=client_credentials' ||
    '&client_id=' || :SF_CLIENT_ID ||
    '&client_secret=' || :SF_CLIENT_SEC
  );

if sqlcode  0;
  // Handle HTTP call failure
  dsply 'Token request failed';
  *inlr = *on;
  return;
endif;

// Step 2: Parse the JSON response to extract the access_token
// Response format: {"access_token":"...","token_type":"Bearer","expires_in":7200}
exec sql
  SET :wAccessTok = JSON_VALUE(:wTokenResp, '$.access_token');

exec sql
  SET :wTokenType = JSON_VALUE(:wTokenResp, '$.token_type');

// Step 3: Store the token in a DB2 table for reuse by other programs
exec sql
  MERGE INTO APPLIB.OAUTHTOK AS T
  USING (VALUES('SALESFORCE', :wAccessTok, :wTokenType,
                CURRENT_TIMESTAMP + 115 MINUTES))  -- 7200s - buffer
        AS S(SVC_NAME, ACCESS_TOKEN, TOKEN_TYPE, EXPIRES_AT)
  ON T.SVC_NAME = S.SVC_NAME
  WHEN MATCHED THEN UPDATE SET
    ACCESS_TOKEN = S.ACCESS_TOKEN,
    TOKEN_TYPE   = S.TOKEN_TYPE,
    EXPIRES_AT   = S.EXPIRES_AT
  WHEN NOT MATCHED THEN INSERT VALUES
    (S.SVC_NAME, S.ACCESS_TOKEN, S.TOKEN_TYPE, S.EXPIRES_AT);

exec sql COMMIT;

*inlr = *on;

Token Storage Table Design

Storing tokens in a DB2 table enables all programs in the activation group (or all jobs) to share a valid token without each one obtaining its own. This reduces API rate-limit consumption and speeds up batch jobs:

-- Token storage table
CREATE TABLE APPLIB.OAUTHTOK (
  SVC_NAME      VARCHAR(50)    NOT NULL,   -- e.g., 'SALESFORCE', 'M365', 'SVCNOW'
  ACCESS_TOKEN  VARCHAR(4096)  NOT NULL,
  TOKEN_TYPE    VARCHAR(50)    NOT NULL DEFAULT 'Bearer',
  EXPIRES_AT    TIMESTAMP      NOT NULL,
  REFRESH_TOKEN VARCHAR(4096),             -- Only for Authorization Code flow
  CREATED_AT    TIMESTAMP      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT PK_OAUTHTOK PRIMARY KEY (SVC_NAME)
);

Retrieving and refreshing a token in RPG:

**FREE

dcl-s wToken      varchar(4096);
dcl-s wExpires    timestamp;
dcl-s wNeedNew    ind inz(*off);

// Check if we have a valid token that has not expired
exec sql
  SELECT ACCESS_TOKEN, EXPIRES_AT
  INTO   :wToken, :wExpires
  FROM   APPLIB.OAUTHTOK
  WHERE  SVC_NAME = 'SALESFORCE'
  AND    EXPIRES_AT > CURRENT_TIMESTAMP + 5 MINUTES; -- 5-minute buffer

if sqlcode = 100;             // No row found — token missing or expired
  wNeedNew = *on;
elseif sqlcode  0;
  // DB error handling
  wNeedNew = *on;
endif;

if wNeedNew;
  // Call the token-fetch program
  CALLP GetSalesforceToken();
  // Re-read the fresh token
  exec sql
    SELECT ACCESS_TOKEN INTO :wToken
    FROM APPLIB.OAUTHTOK
    WHERE SVC_NAME = 'SALESFORCE';
endif;

// Now use wToken as the Bearer token for API calls

Calling an OAuth-Protected API

Once you have a Bearer token, attach it as the Authorization header on each API call:

**FREE

dcl-s wBearerHdr   varchar(4200);
dcl-s wApiUrl      varchar(500);
dcl-s wResponse    varchar(32766);
dcl-s wRecordId    varchar(20);

// Build the Authorization header
wBearerHdr = 'Authorization: Bearer ' + %trimr(wToken) +
             CRLF + 'Content-Type: application/json' +
             CRLF + 'Accept: application/json';

// Build the API endpoint URL
wApiUrl = 'https://myorg.salesforce.com/services/data/v59.0/query/?q=' +
          'SELECT+Id,Name,AccountNumber+FROM+Account+WHERE+AccountNumber=''12345''';

// Call the API using DB2 HTTP function
exec sql
  SET :wResponse = SYSTOOLS.HTTPGETCLOB(
    :wApiUrl,
    :wBearerHdr
  );

if sqlcode  0;
  dsply 'API call failed';
  return;
endif;

// Parse the response — extract the first record ID
exec sql
  SET :wRecordId = JSON_VALUE(:wResponse, '$.records[0].Id');

JWT Structure and Validation

A JSON Web Token (JWT) is a compact, self-contained token used as a Bearer token in many OAuth systems. A JWT has three Base64URL-encoded parts separated by dots:

header.payload.signature

// Header (decoded):
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-from-jwks-endpoint"
}

// Payload (decoded) — claims:
{
  "iss": "https://login.salesforce.com",
  "sub": "https://login.salesforce.com/id/00D.../005...",
  "aud": "https://login.salesforce.com",
  "exp": 1751500800,   // Unix timestamp — token expires at this time
  "iat": 1751497200,   // Issued at
  "jti": "unique-token-id"
}

// Signature: RS256 digital signature of header.payload using the issuer's private key

To validate a JWT on the IBM i side (when IBM i is the resource server receiving tokens from an external client), you need to:

  1. Split the token on the dots
  2. Base64URL-decode the header and payload
  3. Parse the payload JSON to extract claims (exp, iss, sub, aud)
  4. Check that exp is in the future (exp > CURRENT_TIMESTAMP after converting Unix timestamp)
  5. Verify the signature using the issuer’s public key from the JWKS endpoint

For IBM i acting as an API consumer (not a token issuer), step 5 is typically handled by the OAuth provider — you just use the token as given and check the expiry from your stored EXPIRES_AT column.

Certificate Management on IBM i

Making HTTPS calls from IBM i requires that the server’s TLS certificate is trusted. The trust is established through the IBM i Digital Certificate Manager (DCM), which manages the system’s certificate stores.

/* Check if the HTTPS endpoint's certificate is trusted */
/* From a PASE terminal — use curl to test the HTTPS connection */
curl -v https://login.salesforce.com/services/oauth2/token 2>&1 | head -30
# If you see "SSL certificate verify failed" — the cert is not in the IBM i trust store

/* Option 1: Use the *SYSTEM certificate store (recommended) */
/* In DCM: add the CA certificate for the target server to *SYSTEM */
/* DCM is accessed via a browser: https://my-ibm-i:2001/QIBM/ICSS/Cert/Admin/certmgr.html */

/* Option 2: Use SYSTOOLS HTTP functions with SSL disabled (dev/test only — NOT production) */
exec sql
  SET :wResp = SYSTOOLS.HTTPPOSTCLOB(
    :wUrl,
    'Content-Type: application/x-www-form-urlencoded' ||
    CRLF || 'IBM-SSLCHECK: N',   -- Disables SSL validation — DO NOT use in production
    :wBody
  );

/* Option 3: Use QSH/PASE to install well-known CA certificates */
/* yum install ca-certificates-mozilla */
/* This installs Mozilla's trusted CA bundle into /QOpenSys/pkgs/share/ca-certificates */

For production systems, always use Option 1: add the CA certificate for any external HTTPS endpoint to the IBM i *SYSTEM certificate store via DCM. This is a one-time administrative step per endpoint domain.

Next post: AI-Assisted RPG Modernization on IBM i in 2026 — converting fixed-format RPG to free-format using CVTRPGSRC and IBM Merlin, using LLMs like watsonx Code Assistant and GitHub Copilot to explain and refactor legacy RPG code, AI-generated unit tests for service programs, and the practical modernization workflow.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top