IBM i Architecture · Security

IBM i Security in the API Era: Protecting Your Systems in a Connected World

Last Updated: March 2026

You have exposed your IBM i programs as REST APIs. Your mobile app is working. Your cloud integrations are live. Then someone asks: "How secure is this?"

Opening your IBM i to the modern world also opens it to modern threats. This article explores practical, layered security strategies for IBM i in a connected world — extending the platform's already strong foundation to cover the API era.

🔒 IBM i Security: Already Strong by Design

Before addressing modern threats, it is important to recognize what IBM i already provides. It is one of the most secure enterprise platforms available.

📌 Key principle: The challenge is not replacing IBM i security — it is extending it to cover modern API-driven access patterns. This is about evolution, not revolution.

How the Threat Landscape Has Changed

Traditional IBM i security was designed for an era of 5250 terminals, on-premise applications, and a clearly defined network perimeter. Today's reality is fundamentally different.

APIs are accessible from anywhere — mobile apps, cloud services, partner systems, and IoT devices. Threats have evolved to match: SQL injection, API abuse, credential stuffing, data exfiltration, and zero-day exploits. Compliance requirements have grown stricter, with GDPR, SOC 2, PCI-DSS, and HIPAA each demanding specific security controls.

Real-world example: In one client environment, a missing rate limit on a public API resulted in over 2 million requests in under an hour — effectively bringing down the system. Not a breach, but a complete lack of basic protection.

🧱 Defense in Depth: A Layered Approach

Security is not a single solution — it is multiple layers working together. If one layer fails, the others continue to defend. Modern IBM i security follows a Zero Trust model: never trust, always verify — even for internal traffic.

Internet
Firewall / WAF
API Gateway (Rate Limiting, Authentication)
Application Layer (Validation, Authorization)
IBM i (Object Authority, Exit Programs)
Database (Encryption, Audit)

The sections below address each layer in turn.


🌐 Layer 1: Network Security

The first line of defense is controlling what traffic can reach your system at all. The principle is simple: only allow what is explicitly necessary.

Firewall — Allow HTTPS only, block direct database ports
# Allow HTTPS only from the internet
ALLOW TCP 443 FROM ANY TO IBM_I_IP

# Block direct database access from internet
DENY TCP 8471 FROM ANY TO IBM_I_IP  # DRDA port
DENY TCP 446  FROM ANY TO IBM_I_IP  # DDM port

# Allow SSH for administration from restricted IPs only
ALLOW TCP 22 FROM ADMIN_IPS TO IBM_I_IP

A Web Application Firewall (WAF) sits in front of your API and filters malicious HTTP requests before they reach your application — blocking SQL injection attempts, cross-site scripting, known attack patterns, and malformed requests.


🚪 Layer 2: API Gateway

Your IBM i system should never be directly exposed to the internet. An API Gateway is the single point of entry for all external traffic, providing centralized authentication, rate limiting, IP filtering, request validation, and logging.

Non-negotiable rule: Internet → API Gateway → API Layer → IBM i. Never Internet → IBM i directly.

Rate Limiting

JavaScript — Express Rate Limiter
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15-minute window
    max: 100,                  // Max 100 requests per IP
    message: 'Too many requests from this IP, please try again later'
});

app.use('/api/', limiter);

API Key Validation

Every API consumer must be identified. Validate API keys against IBM i to keep authentication centralized on the platform.

RPGLE — API Key Validation Procedure
**FREE

Dcl-Proc validateApiKey Export;
    Dcl-Pi *N Ind;
        apiKey Char(64) Const;
    End-Pi;

    Dcl-S isValid Ind;

    Exec SQL
        SELECT CASE
            WHEN EXPIRY_DATE >= CURRENT DATE THEN '1'
            ELSE '0'
        END
        INTO :isValid
        FROM APIKEYS
        WHERE API_KEY = :apiKey
          AND STATUS = 'ACTIVE';

    If SQLCODE <> 0;
        Return *Off;
    EndIf;

    Return isValid;
End-Proc;

🔑 Layer 3: Authentication

JWT tokens are the recommended approach for external-facing APIs. Users authenticate once, receive a time-limited token, and include that token in all subsequent requests.

JavaScript — JWT Login Endpoint
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;
    const user = await validateIBMiUser(username, password);

    if (!user) return res.status(401).json({ error: 'Invalid credentials' });

    const isValidPassword = await bcrypt.compare(password, user.hash);
    if (!isValidPassword) return res.status(401).json({ error: 'Invalid credentials' });

    const token = jwt.sign(
        { username: user.username, role: user.role },
        process.env.JWT_SECRET,
        { expiresIn: '1h' }
    );
    res.json({ token });
});

For internal APIs, IBM i User Profile validation keeps authentication on the platform itself using the QSYGETPH API.


👤 IBM i Service Profile Strategy

APIs should never run under powerful profiles like QSECOFR or application owners. Instead, create dedicated service profiles with the minimum required authority — the least privilege principle.

CL — Create Dedicated API Service Profile
/* Create a profile with no special authority */
CRTUSRPRF USRPRF(APIUSER)
          PASSWORD(*NONE)
          USRCLS(*USER)
          SPCAUT(*NONE)
          TEXT('API Service Profile')

/* Grant access only to the required service program */
GRTOBJAUT OBJ(MYLIB/CUSTSVC)
          OBJTYPE(*SRVPGM)
          USER(APIUSER)
          AUT(*USE)
📌 Why this matters: If the API profile is ever compromised, the blast radius is limited to only what that profile can access. Audit trails are also cleaner — API actions are clearly separated from user actions.

🔏 Layer 4: Authorization (RBAC)

Authentication establishes who you are. Authorization determines what you can do. Role-Based Access Control managed in DB2 for i keeps permissions centralized and auditable.

SQL — RBAC Schema
CREATE TABLE ROLES (
    ROLE_ID   INT GENERATED ALWAYS AS IDENTITY,
    ROLE_NAME VARCHAR(50) NOT NULL,
    PRIMARY KEY (ROLE_ID)
);

CREATE TABLE USER_ROLES (
    USER_ID VARCHAR(10) NOT NULL,
    ROLE_ID INT NOT NULL,
    PRIMARY KEY (USER_ID, ROLE_ID)
);

CREATE TABLE ROLE_PERMISSIONS (
    ROLE_ID   INT NOT NULL,
    RESOURCE  VARCHAR(100) NOT NULL,
    OPERATION VARCHAR(20) NOT NULL,
    PRIMARY KEY (ROLE_ID, RESOURCE, OPERATION)
);
JavaScript — Enforce Permission in API Middleware
const checkPermission = (resource, operation) => {
    return async (req, res, next) => {
        const authorized = await checkAuthorizationInIBMi(
            req.user.username, resource, operation
        );
        if (!authorized) return res.status(403).json({ error: 'Insufficient permissions' });
        next();
    };
};

// Applied per route — clear, explicit, auditable
app.post('/api/orders',
    authenticateJWT,
    checkPermission('orders', 'create'),
    createOrder
);

🛡️ Layer 5: Input Validation and SQL Injection Prevention

Never trust user input. Validate at both the API layer and the RPG layer — two checkpoints are better than one.

The SQL Injection Risk

❌ Vulnerable — never build SQL by string concatenation
// Attack input: "' OR '1'='1"
// Result: returns ALL customer records
sqlStmt = 'SELECT * FROM CUSTMAST WHERE CUSTNO = ''' + customerId + '''';
✅ Safe — always use parameter markers
// Database treats :customerId as data, never as SQL code
Exec SQL
    SELECT * INTO :customer
    FROM CUSTMAST
    WHERE CUSTNO = :customerId;
Rule: Always use parameter markers (:variable). Avoid dynamic SQL unless absolutely necessary. Never build SQL strings by concatenation with user-supplied input.

🔐 Layer 6: Encryption

Data in Transit — Always Use HTTPS

Apache — HTTPS Configuration on IBM i
Listen *:443

<VirtualHost *:443>
    ServerName api.yourcompany.com

    SSLEngine on
    SSLCertificateFile    /path/to/certificate.crt
    SSLCertificateKeyFile /path/to/private.key
    SSLCertificateChainFile /path/to/chain.crt

    # Strong cipher suites only — disable legacy protocols
    SSLCipherSuite HIGH:!aNULL:!MD5
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1

    ProxyPass /api http://localhost:3000/api
    ProxyPassReverse /api http://localhost:3000/api
</VirtualHost>

Data at Rest

Encrypt sensitive fields at the application layer for maximum control. This applies to Social Security numbers, payment card data, personal health information, and financial data. Store encryption keys securely — never in source code — and implement key rotation policies.


📊 Layer 7: Audit and Monitoring

Enable the Audit Journal

CL — Enable System Auditing
CRTJRNRCV JRNRCV(QAUDIT/AUDRCV0001) THRESHOLD(100000)
CRTJRN JRN(QAUDIT/QAUDJRN) JRNRCV(QAUDIT/AUDRCV0001)

CHGSYSVAL SYSVAL(QAUDLVL)
          VALUE(*AUTFAIL *CREATE *DELETE *SECURITY)

Alert on Suspicious Activity

JavaScript — Brute Force Detection
const failedAttempts = new Map();
const THRESHOLD = { count: 5, window: 300000 }; // 5 failures in 5 minutes

app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;
    const ip = req.ip;
    const user = await validateUser(username, password);

    if (!user) {
        const key = `${ip}:${username}`;
        const attempts = (failedAttempts.get(key) || [])
            .filter(t => Date.now() - t < THRESHOLD.window);
        attempts.push(Date.now());
        failedAttempts.set(key, attempts);

        if (attempts.length >= THRESHOLD.count) {
            await sendSecurityAlert({ type: 'BRUTE_FORCE', ip, username });
        }
        return res.status(401).json({ error: 'Invalid credentials' });
    }

    failedAttempts.delete(`${ip}:${username}`);
    // Generate and return token...
});

🚨 Common Vulnerabilities and Fixes

Hardcoded Credentials

❌ Never hardcode credentials in source code
const conn = new Connection({ host: 'ibmi.company.com', user: 'ADMIN', password: 'Password123' });
✅ Always load credentials from environment variables
const conn = new Connection({
    host: process.env.IBMI_HOST,
    user: process.env.IBMI_USER,
    password: process.env.IBMI_PASSWORD
});

Missing CORS Configuration

❌ Wildcard CORS allows any origin to access your API
app.use(cors({ origin: '*' }));
✅ Restrict CORS to known, trusted origins only
app.use(cors({
    origin: ['https://app.company.com', 'https://mobile.company.com'],
    credentials: true
}));

📋 Security Checklist Before Going Live

💡 Conclusion

IBM i security has always been strong. The challenge today is not securing the platform itself — it is securing how the platform is accessed. If your APIs are the new front door to your IBM i system, security is your first line of defense.


Apply security in layers, and keep these principles consistent across every API you build:


Extend IBM i's strong foundation for the API era, and you can safely connect to the modern world without compromising the integrity that makes the platform exceptional.