ML07-0001-0

Chapter 6. Authentication and Security

HTTPD checks every request against the routes of the Parmlib member (Chapter 5, “Routes and Server Modules”). A route can ask the client for a user ID and a password, and it can ask the security product whether that user may use a resource. This chapter shows the four ways a route can ask, what the client sees, how long a login lasts, which identity the server itself uses, and what HTTPD does not do. The syntax of the route lines is in Chapter 5, “Routes and Server Modules”. The display modules that show the logins are in Chapter 8, “Operating HTTPD”. The rules are described in full in ‘Routes and Authentication Policy’ in the HTTPD Reference.

The examples use the member from “The Standard Module: mvsMF”, the user ID IBMUSER and the host name localhost. The passwords in the captures are masked.

How a Request Is Checked

HTTPD checks a request in two stages, before it serves a file or links to a module. This holds for the methods GET, HEAD, PUT, POST and DELETE, for MOD routes and for LOC routes alike.

  1. Who are you? If the route needs a login and the client has none, HTTPD answers with a request to log in. The answer depends on the AUTH= mode of the route.
  2. May you? This stage applies only to a route with RES=. HTTPD asks the security product whether the user may read the resource. If not, the answer is 403.

HTTPD finds the user in the request itself. It tries these sources in this order and takes the first one that gives a user:

  1. the cookie Sec-Token,
  2. the cookie LtpaToken2,
  3. the header Authorization: Bearer with a session token,
  4. the header Authorization: Basic with user ID and password.

The login is bound to the address of the client. A token that was issued to one address does not work from another. Every route accepts every source. The AUTH= mode of a route does not choose the source. It decides only what a client without a login gets.

HTTPD changes the user ID and the password to upper case and uses the first 8 characters of each.

AUTH=: What a Route Asks For

Each route has one of four modes.

ModeClient without a loginUse it for
NONEServed.Public content.
BASIC401 with a Basic challenge.Tools such as curl, and browsers that show a password dialog.
FORM302 to the login page.Pages for people at a browser.
TOKEN401 without a challenge.REST clients and scripts.

Table 18. The values of AUTH=

A route without AUTH= and without RES= is NONE. A route with RES= and without AUTH= is BASIC. Write the mode on every route, so that the member says what you mean. A mode that does not exist refuses the start (Chapter 4, “Configuring HTTPD”).

NONE

The route is public. HTTPD checks no login. The same holds for a path that no route claims.

$ curl -i http://localhost:8080/pub/notice.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:52:44 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 23

This notice is public.

BASIC

A client without a login gets the status 401 and a Basic challenge. The challenge names the realm (“The Realm”). A browser shows a password dialog. The body is the text 401 Unauthorized, and the response has Cache-Control: no-store.

$ curl -i http://localhost:8080/private/secret.txt
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:52:44 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized

A wrong password and an unknown user get the same answer, so a client cannot tell which of the two it was.

$ curl -i -u IBMUSER:wrongpw http://localhost:8080/private/secret.txt
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:52:44 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized
$ curl -i -u NOSUCH:wrongpw http://localhost:8080/private/secret.txt
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:52:44 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized

A correct password gets the file.

$ curl -i -u IBMUSER:****** http://localhost:8080/private/secret.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:52:44 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 43

private: any logged-in user may read this.

If the request has the header X-CSRF-ZOSMF-HEADER, whatever its value, HTTPD leaves out the WWW-Authenticate header. The answer is still 401. A browser then shows no password dialog.

FORM

A client without a login that sends a GET is sent to the login page. HTTPD answers 302 with Location: /login. It stores the address that the client asked for in the cookie Sec-Uri, encoded in base64, so that it can send the user back after the login.

$ curl -i -c cookies.txt http://localhost:8080/form/page.html
HTTP/1.1 302 Moved Temporarily
Date: Tue, 27 Oct 2026 05:52:56 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Location: /login
Set-Cookie: Sec-Uri=YYaWmZRhl4GHhUuIo5ST; path=/
Transfer-Encoding: chunked

The login page asks for a user ID and a password. The fields allow 8 characters.

$ curl http://localhost:8080/login | grep -E "<title>|<form|<input|<button"
  <title>Login Required</title>
   <form class="modal-content animate" action="/login" method="post">
      <input type="text" placeholder="Enter Userid" name="userid" maxlength="8" required>
      <input type="password" placeholder="Enter Password" name="password" maxlength="8" required>
    <input type="hidden" name="uri" value="/">
     <button type="submit">Login</button>

The form sends a POST to /login with the fields userid and password. A wrong password gets the page again, with the text Invalid userid or password. The status is 200. HTTPD closes the connection after every POST that has a body, so it does so after a failed login and after a successful one:

$ curl -i -b cookies.txt -d userid=IBMUSER -d password=wrongpw http://localhost:8080/login | grep -E "^HTTP|^Connection|Invalid"
HTTP/1.1 200 OK
Connection: close
<h2 class="message">Invalid userid or password</h2>

A correct password gets the status 303 back to the address in Sec-Uri. HTTPD deletes Sec-Uri and sets the cookie Sec-Token. The console shows HTTPD007I (“Console Messages for Logins”).

$ curl -i -b cookies.txt -c cookies.txt -d userid=IBMUSER -d password=****** http://localhost:8080/login
HTTP/1.1 303 See Other
Date: Tue, 27 Oct 2026 05:52:56 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: close
Cache-Control: no-store
Location: /form/page.html
Set-Cookie: Sec-Uri=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: Sec-Token=<token>
Transfer-Encoding: chunked

The client sends the cookie with every later request, and the page is served:

$ curl -i -b cookies.txt http://localhost:8080/form/page.html
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:52:57 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Content-Type: text/html
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked

<!DOCTYPE html>
<html><head><title>Form</title></head>
<body><h1>Page behind the login form</h1></body></html>

A GET of /logout ends the login. HTTPD clears both cookies:

$ curl -i -b cookies.txt -c cookies.txt http://localhost:8080/logout
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:52:57 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Set-Cookie: Sec-Uri=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: Sec-Token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT
Content-Type: text/plain
Transfer-Encoding: chunked

You have been logged out.

After that the page asks for a login again.

$ curl -i -b cookies.txt http://localhost:8080/form/page.html
HTTP/1.1 302 Moved Temporarily
Date: Tue, 27 Oct 2026 05:52:57 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Location: /login
Set-Cookie: Sec-Uri=YYaWmZRhl4GHhUuIo5ST; path=/
Transfer-Encoding: chunked

A request with another method than GET or POST to a FORM route gets the status 200 and a text that says Invalid REQUEST_METHOD. The form is for browsers. Use BASIC or TOKEN for a program.

TOKEN

A client without a login gets the status 401, and HTTPD never sends a challenge, whatever the request holds.

$ curl -i http://localhost:8080/token/api.txt
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:52:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized

The mode does not mean that Basic is refused. A request with a Basic header is checked and served, like any other route.

$ curl -i -u IBMUSER:****** http://localhost:8080/token/api.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:52:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 38

token area: bare 401 without a login.

A client that wants a token asks mvsMF for it. A POST to /zosmf/services/authenticate with a Basic header returns a session cookie LtpaToken2:

$ curl -i -c tok.txt -X POST -u IBMUSER:****** -H "X-CSRF-ZOSMF-HEADER: x" http://localhost:8080/zosmf/services/authenticate
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:53:06 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Set-Cookie: LtpaToken2=<token>; Path=/; HttpOnly; SameSite=Strict
Content-Type: application/json
Content-Length: 52
Cache-Control: no-store
Pragma: no-cache
X-Content-Type-Options: nosniff
Content-Language: en
Set-Cookie: LtpaToken2=<token>; Path=/; HttpOnly; SameSite=Strict

{"returnCode":0,"reasonCode":0,"message":"Success."}

HTTPD accepts that cookie on its own TOKEN routes, too:

$ curl -i -b tok.txt http://localhost:8080/token/api.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:53:06 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 38

token area: bare 401 without a login.

The same cookie opens /zosmf/info. The next capture sends the cookie without a password:

$ curl -i -b tok.txt -H "X-CSRF-ZOSMF-HEADER: x" http://localhost:8080/zosmf/info
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:53:06 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: application/json
Content-Length: 184
Cache-Control: no-store
Pragma: no-cache
X-Content-Type-Options: nosniff
Content-Language: en

{"zosmf_hostname":"localhost","zosmf_port":"8080","zosmf_version":"1","zosmf_full_version":"1.3.0","plugins":[],"zosmf_saf_realm":"SAFRealm","api_version":"1","zos_version":"MVS 3.8j"}

The route /zosmf/services/authenticate is AUTH=NONE in the member. HTTPD does not ask for a login there, because mvsMF does it itself. A wrong password gets a 401 from mvsMF, with a JSON body:

$ curl -i -X POST -u IBMUSER:wrongpw -H "X-CSRF-ZOSMF-HEADER: x" http://localhost:8080/zosmf/services/authenticate
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:53:06 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: application/json
Content-Length: 188
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Pragma: no-cache
X-Content-Type-Options: nosniff
Content-Language: en

{"returnCode":8,"reasonCode":1,"message":"Login failed. Check whether the user ID and password you use for the Basic Auth is correct, and if the user ID has the required SAF permissions."}

When a request that mvsMF does not own has no login, you see the bare 401:

$ curl -i http://localhost:8080/zosmf/restfiles/ds?dslevel=SYS1.PARMLIB
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:53:01 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized

When to Use TOKEN

Use TOKEN for routes that programs call, such as the REST API of mvsMF. A program should get a plain 401 and handle it, not a login page or a Basic challenge that it does not understand. A user at a browser does better with FORM. A user of curl is fine with BASIC. The three modes accept the same credentials, so a mix on one server causes no conflict.

The Realm

The keyword REALM names the server in the Basic challenge. The captures above show realm="MVS.EXAMPLE". The default is the SMF ID of the system, with at most four characters. If the system has none, the default is MVS. A value has 1 to 64 characters. It must not contain a control character or one of the characters double quote, backslash, <, > and &. Another value gives HTTPD424W, and the default stays. The line HTTPD136I of D C shows the realm in effect (Chapter 4, “Configuring HTTPD”).

How Long a Login Lasts

HTTPD keeps what a client proved. For a Basic request it keeps an entry that it finds by the address, the user ID and the password, with the RACF identity that RACF returned. For a form login and for a token it keeps a session of its own. Two keywords limit the time:

KeywordDefaultMeaning
SESSION_TIMEOUT30Minutes without a request. Every request starts the time again.
SESSION_MAXAGE480Minutes since the login, whatever the client does. Requests do not extend it.

Table 19. The limits of a login

Both values are in minutes. The value 0 switches a limit off. Both values go from 0 to 65535. HTTPD removes the expired logins about once a minute, so a login can last up to a minute longer than the limit says.

The limits must be longer than CLIENT_TIMEOUT, which counts in seconds. A login that expires while a request is still read would be dropped under the request. HTTPD compares the limits at the end of the start and warns:

S HTTPD,M=PRMSESS
$HASP100 HTTPD    ON STCINRDR
$HASP373 HTTPD    STARTED
IEF403I HTTPD - STARTED - TIME=05.30.24
HTTPD000I HTTPD 4.5.1 (nnnnnnn) STARTING
HTTPD005I LIBC370 2.26.0 (3AA3B28)
HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8)
HTTPD004I STC IDENTITY SET TO HTTPD/USER VIA RACINIT
RAKF0004 INVALID ATTEMPT TO ACCESS SYSTEM:  USER:HTTPPRB
HTTPD054I LISTENING ON ANY PORT 8080
HTTPD026E SESSION_TIMEOUT (1 MIN) <= CLIENT_TIMEOUT (120 SEC), RAISE IT
HTTPD032E SESSION_MAXAGE (2 MIN) <= CLIENT_TIMEOUT (120 SEC), RAISE IT
HTTPD061I STARTING SOCKET THREAD    TCB(9CDB00) TASK(100FC8) STACKSIZE(32768)
HTTPD061I STARTING WORKER(0DD1A8)   TCB(9CD410) TASK(114FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD0A8)   TCB(9CD0E0) TASK(125FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD028)   TCB(9BBCF8) TASK(136FC8) STACKSIZE(65536)
HTTPD001I HTTPD 4.5.1 READY - SERVING /www

HTTPD026E is for SESSION_TIMEOUT and HTTPD032E for SESSION_MAXAGE. The server starts. Raise the value. Chapter 4, “Configuring HTTPD” shows the member.

HTTPD keeps at most 256 logins. When the store is full, a new login is rejected. A single client can hold at most 16 sessions. If it asks for a 17th, HTTPD ends the client’s session that has been idle the longest, but only if it has been idle for at least 60 seconds. Otherwise the new login is refused, and the form answers Login failed, no session available. Log out when a script is done, so that it does not use up its places.

A Changed RACF Password and the Cached Login

A Basic request does not call RACF every time. HTTPD looks up the address, the user ID and the password in its cache first. If it finds them, the request is answered from the cache. This is why a Basic route is cheap. It has a consequence when a password changes in RACF.

To make a changed password take effect for everyone at once, stop and start HTTPD. That drops all logins.

From HTTPD 4.5, a login that starts a session of its own checks the cached password with RACF again. This happens with a form login and with the authenticate request of mvsMF. If RACF refuses it, HTTPD marks the cached login as stale:

A plain Basic request with the old password is not checked against RACF. It stays in the cache until it ends or until a login of the kinds above marks it.

Note: HTTPD 4.4 and earlier never mark a login as stale. The old password stays valid for Basic requests until the cache entry expires.

The Identity of the Server

RAKF has no table of started procedures. It gives every started task the same account, STC/STCGROUP. On a standard RAKF profile set this account has ALTER access to every data set. HTTPD does not want that. At the start it logs on as a user ID of its own, with RACINIT and no password.

The user ID and the group come from the PARM string of the procedure, not from the Parmlib member:

//HTTPD    EXEC PGM=HTTPD,REGION=8M,TIME=1440,
//            PARM='STCUSER=&STCUSER STCGROUP=&STCGRP'

The procedure has the symbols STCUSER (default HTTPD) and STCGRP (default USER). You can override them in the START command:

S HTTPD,STCUSER=WEBSRV,STCGRP=WEBGRP

The messages tell what happened. They appear in every start capture of this guide:

S HTTPD
$HASP100 HTTPD    ON STCINRDR
$HASP373 HTTPD    STARTED
IEF403I HTTPD - STARTED - TIME=05.44.51
HTTPD000I HTTPD 4.5.1 (nnnnnnn) STARTING
HTTPD005I LIBC370 2.26.0 (3AA3B28)
HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8)
HTTPD004I STC IDENTITY SET TO HTTPD/USER VIA RACINIT
RAKF0004 INVALID ATTEMPT TO ACCESS SYSTEM:  USER:HTTPPRB
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/info
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/services/authenticate
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/restconsoles/*
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/*
HTTPD054I LISTENING ON ANY PORT 8080
HTTPD061I STARTING SOCKET THREAD    TCB(9CD7C8) TASK(152FC8) STACKSIZE(32768)
HTTPD061I STARTING WORKER(164D00)   TCB(9BBEB8) TASK(166FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(164C00)   TCB(9BBB88) TASK(177FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(164B80)   TCB(9BB858) TASK(188FC8) STACKSIZE(65536)
HTTPD001I HTTPD 4.5.1 READY - SERVING /www

The user ID of STCUSER needs READ access to the Parmlib member and to the document root. A server module that sets no identity of its own uses the identity of the task.

At the stop the server puts the old identity back before it releases its APF authorization. The authorization was taken under the old account and is given back under the same account. For this reason the user ID of STCUSER needs no access to the profile for SVC 244. ‘The Started Task and the Package’ in the HTTPD Reference explains the order.

A Security System That Accepts Any User ID

HTTPD has no user database. A login is a question to the security product: is this user ID valid with this password? The answer is only worth something if the product says no to a user it does not know.

RAKF 1.2.x can say yes to everybody. RAKF 1.2.x, the version on TK5, keeps its users in a table that it loads at the IPL from the member SYS1.SECURE.CNTL(USERS). When the table is missing or invalid, RAKF 1.2.x has nobody to compare with. It answers every logon with return code 0 and hands back the account STC in the group STCGROUP, the account that it gives to every started task. RAKF 2.0.0 (MVS/CE) behaves differently: it refuses a user that it does not know.

An invalid table is easy to make. On RAKF 1.2.x a line with a blank password is enough: the load stops with RAKFUIDS2 and RAKFUIDSX (“Defining the Users and Profiles in RAKF”), and nothing of the table is loaded.

Note: This was measured on TK5. TK4- was not measured. The statements in this section about RAKF 1.2.x are for TK5.

How HTTPD Reacts

HTTPD 4.5.1 and later look for this state themselves. At the start, after the logon of the started task, the server makes one logon with the user ID HTTPPRB and a password that cannot be right. A security product that checks refuses it. RAKF writes RAKF0004 for it, and that is the line you see in every healthy start (“A healthy start”). A product that gives an ACEE back for it accepts any user ID. The server then writes

HTTPD042W SECURITY SYSTEM ACCEPTS ANY USERID, ALL LOGINS REFUSED

and goes on with the start. The server runs, and it refuses every login:

The refusal is the same for every user ID, also for the ones that you have defined. HTTPD writes no message for a refused login, and RAKF writes none either, because the server does not ask it. Only HTTPD042W tells you, once, at the start. The next capture shows a start in this state. The system had neither the user table nor the profile table of RAKF:

S HTTPD
$HASP100 HTTPD    ON STCINRDR
$HASP373 HTTPD    STARTED
IEF403I HTTPD - STARTED - TIME=00.27.29
RAKF0003 RESOURCE TABLE INVALID/MISSING: ALLOWED:STC
RAKF0009  STC     ,HTTPD   ,DATASET ,MVSMF.LINKLIB
RAKF0003 RESOURCE TABLE INVALID/MISSING: ALLOWED:STC
RAKF0009  STC     ,HTTPD   ,DATASET ,HTTPD.LINKLIB
RAKF0003 RESOURCE TABLE INVALID/MISSING: ALLOWED:STC
RAKF0009  STC     ,HTTPD   ,FACILITY,SVC244
HTTPD000I HTTPD 4.5.1-DEV (FA5FD19) STARTING
HTTPD005I LIBC370 2.26.0 (3AA3B28)
HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8)
RAKF0001 USERID TABLE INVALID OR MISSING
RAKF0002 STC ACCESS ALLOWED: USER: HTTPD
HTTPD004I STC IDENTITY SET TO HTTPD/USER VIA RACINIT
RAKF0001 USERID TABLE INVALID OR MISSING
RAKF0002 STC ACCESS ALLOWED: USER: HTTPPRB
HTTPD042W SECURITY SYSTEM ACCEPTS ANY USERID, ALL LOGINS REFUSED
RAKF0003 RESOURCE TABLE INVALID/MISSING: ALLOWED:STC
RAKF0009  STC     ,HTTPD   ,DATASET ,SYS1.PARMLIB
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/info
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/services/authenticate
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/restconsoles/*
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/*
HTTPD054I LISTENING ON ANY PORT 8080
HTTPD061I STARTING SOCKET THREAD    TCB(9AD080) TASK(162FC8) STACKSIZE(32768)
HTTPD061I STARTING WORKER(174D48)   TCB(9AD828) TASK(176FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(174C48)   TCB(9A0CF8) TASK(187FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(174BC8)   TCB(9A09C8) TASK(198FC8) STACKSIZE(65536)
HTTPD001I HTTPD 4.5.1-DEV READY - SERVING /www

A client that tries to log in sees only the 401:

$ curl -i -u HERC01:****** -H "X-CSRF-ZOSMF-HEADER: true" http://localhost:8080/zosmf/restjobs/jobs
HTTP/1.1 401 Unauthorized
Date: Fri, 30 Oct 2026 21:58:30 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized

How to Recognise It

Look at the console at the start. These lines say that RAKF has no valid table:

MessageMeaning
HTTPD042WHTTPD found that the product accepts any user ID. This is the sign that counts. The server writes it once, at the start.
RAKF0001, RAKF0002RAKF has no valid user table (USERID TABLE INVALID OR MISSING) and lets the task in (STC ACCESS ALLOWED). In the capture the pair appears twice: for the logon of the started task, and for HTTPPRB.
RAKF0003, RAKF0009RAKF has no valid profile table (RESOURCE TABLE INVALID/MISSING). The second line names the data set or the resource that was checked. You see the pair for each check, also before HTTPD000I.

Table 20. Signs of a RAKF without tables

The two kinds of line do not always come together. A rejected user table gives RAKF0001, RAKF0002 and HTTPD042W and no RAKF0003. In a healthy start there is exactly one RAKF line from the server, RAKF0004 for HTTPPRB, and no HTTPD042W.

The display module HTTPDSRV shows the state in the row httpd->sec_open of the server block (“HTTPDSRV, the control blocks”). The row says NO in a healthy server and YES, LOGIN REFUSED in this state. You can read the row in a browser only if a route lets you reach HTTPDSRV without a login, because the display modules need a login like any other route. Otherwise read the start messages.

What to Do

  1. Correct the tables before the IPL. Check SYS1.SECURE.CNTL(USERS) and SYS1.SECURE.CNTL(PROFILES) against “Defining the Users and Profiles in RAKF”. Check the passwords, the columns and the order of the lines.
  2. After the IPL, read the log for the lines of the table load. The lines RAKFPROF7 RAKF PROFILES UPDATED and RAKFUIDS4 USER TABLE UPDATED say that the tables are loaded. The lines RAKFUIDS2 INPUT DATA INVALID OR OUT OF SEQ. and RAKFUIDSX ** PROGRAM TERMINATED ** say that the user table was rejected.
  3. Restart HTTPD (“Restarting”). The server makes its test once, at the start, and does not look again. A server that wrote HTTPD042W refuses logins until it is stopped and started, also after RAKF is repaired.
  4. Read the start again. A good start has RAKF0004 for HTTPPRB and no HTTPD042W.

RAKF has to be active before HTTPD asks it anything. On TK5 the member SYS1.PARMLIB(RAKFINIT) holds the word YES, and RAKF then becomes active at the IPL without a question at the console. This is an operating hint and not something that HTTPD checks. If RAKF was not active when HTTPD started, the logon of the started task fails with HTTPD004W RACINIT ENVIR=CREATE FAILED ... RC=32 (“The Server Has Not Its Own Identity”).

Two More Rules

The test needs the same supervisor state as the logon of the started task. If the server could not enter it (HTTPD003W), it makes no test and writes no HTTPD042W. Cure HTTPD003W first (“The Server Has Not Its Own Identity”).

HTTPD before 4.5.1 does not look for this state and has no HTTPD042W. Use HTTPD 4.5.1 or later where RAKF 1.2.x runs.

RES=: Checking a Profile

A route with RES=class:resource adds the second stage. After the user is known, HTTPD asks the security product whether the user may READ the resource. HTTPD asks under the identity of the user, not under that of the server. The profile belongs to you. HTTPD names none.

LOC=/admin/*    AUTH=BASIC RES=FACILITY:HTTPD.ADMIN
LOC=/denied/*   AUTH=BASIC RES=FACILITY:HTTPD.DENIED

On RAKF you define the profiles in the class FACILITY and permit the users. RAKF prints this when you do. It reminds you to activate the change:

RDEFINE MVSMF.CONSOLE CLASS(FACILITY) UACC(NONE)
RDEFINE: FACILITY profile defined: MVSMF.CONSOLE
RDEFINE: Run RAKFPROF to activate the changes
PERMIT MVSMF.CONSOLE CLASS(FACILITY) ID(ADMIN) ACCESS(READ)
PERMIT:  access READ    granted to FACILITY/MVSMF.CONSOLE
PERMIT: ID = ADMIN
PERMIT: Run RAKFPROF to activate the changes
RDEFINE HTTPD.ADMIN CLASS(FACILITY) UACC(NONE)
RDEFINE: FACILITY profile defined: HTTPD.ADMIN
RDEFINE: Run RAKFPROF to activate the changes
PERMIT HTTPD.ADMIN CLASS(FACILITY) ID(ADMIN) ACCESS(READ)
PERMIT:  access READ    granted to FACILITY/HTTPD.ADMIN
PERMIT: ID = ADMIN
PERMIT: Run RAKFPROF to activate the changes
RDEFINE HTTPD.DENIED CLASS(FACILITY) UACC(NONE)
RDEFINE: FACILITY profile defined: HTTPD.DENIED
RDEFINE: Run RAKFPROF to activate the changes
END

The lines say that the changes are not active until RAKFPROF has run.

A user who may read the resource gets the file:

$ curl -i -u IBMUSER:****** http://localhost:8080/admin/report.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:52:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 50

admin report: needs READ on FACILITY HTTPD.ADMIN.

A user who may not gets 403:

$ curl -i -u IBMUSER:****** http://localhost:8080/denied/data.txt
HTTP/1.1 403 Forbidden
Date: Tue, 27 Oct 2026 05:52:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

403 Forbidden

In this lab, IBMUSER may read HTTPD.ADMIN. The capture above shows no permit for HTTPD.DENIED, and the user gets the 403. The 403 has the body 403 Forbidden. A user without a login never reaches this stage. The user gets the 401 of the first stage.

Note: A profile that does not exist can permit everybody. When the security product answers that no profile covers the resource (return code 4), HTTPD counts the request as permitted. Whether the product gives that answer for an unprotected resource depends on its settings. If it refuses instead (return code 8 or above), the user gets the 403. A typing error in the resource name looks the same as a missing profile. A class that RAKF does not define depends on the level of RAKF. RAKF 2.0.0 (MVS/CE) gives the return code 8, so every user gets the 403 and HTTPD writes no HTTPD425W. RAKF 1.2.x (measured on TK5) gives the return code 4, so the class looks like a missing profile. HTTPD therefore asks once for each route with RES= at the start. For a route without a profile it writes HTTPD425W NO PROFILE FOR
class:resource -- path NOT GATED
. The route still works, and every user who is logged in passes. Look for HTTPD425W after each change. The probe is not made for AUTH=NONE routes. To make such a route refuse instead, set RES_NOPROFILE=DENY (“Refusing a Route Whose Profile Is Missing”).

HTTPD asks for the access READ for all methods. It has no separate permission for PUT, POST or DELETE.

Refusing a Route Whose Profile Is Missing

The default is the permissive reading of the note above. A route that was written to close a resource is open to every user who can log in as long as its profile is forgotten, misspelled or not loaded yet. The mvsMF console route (FACILITY:MVSMF.CONSOLE) is the case that matters: without its profile every user who can log in may issue operator commands through mvsMF up to 1.3.0.

The keyword RES_NOPROFILE=DENY turns the missing profile into a closed route. It is one setting for the whole server, not an option of a route. A switch on each route would have to be remembered on exactly the routes where a profile is forgotten. The setting applies to every route with RES= that is not AUTH=NONE. Put it in the member:

RES_NOPROFILE=DENY
LOC=/pub/*  AUTH=BASIC RES=FACILITY:HTTPD.TESTRES

The route is the same as before. At the start, HTTPD426W replaces HTTPD425W for each route without a profile. The next capture shows the lines that matter. The other lines of the start are left out.

HTTPD417I LOCATION /pub/* REGISTERED
HTTPD426W NO PROFILE FOR FACILITY:HTTPD.TESTRES -- /pub/* REFUSES ALL REQUESTS
HTTPD001I HTTPD 4.5.1 READY - SERVING /www

The server starts. A route that refuses is the safe state, and you can define the profile while the server runs. Until the profile exists, a request is answered like this:

RequestAnswer
Without a loginStatus 401. The first stage answers, as before.
A user who is logged in, whether or not the user would be permittedStatus 403 with the body 403 Forbidden: no RACF profile protects this resource. The class and the resource name are not in the answer. They are in HTTPD426W.

Table 21. The answers of a route whose profile is missing

Define the profile and permit the users, as in “Defining the Users and Profiles in RAKF”, and run RAKFPROF. Do not restart HTTPD. The check is made for each request against the profile as RAKF holds it then. After RAKFPROF has run, a user whom the profile permits gets the real answer. A user whom it does not permit gets the plain 403 Forbidden, the same answer as for a profile that refuses. Before RAKFPROF has run, RDEFINE and PERMIT change nothing the server can see, and the route still answers with the body about the missing profile. HTTPD426W belongs to the start and stays in the log. A start after the profile exists does not write it.

The two bodies tell you which case you have: no RACF profile protects this resource means that RAKF has no profile for the resource. Check the class and the name in the route against the profile, and check that RAKFPROF ran. The plain 403 Forbidden means that a profile exists and does not permit the user.

When to turn it on. Turn it on when every route with RES= has its profile and the start writes no HTTPD425W. On a new system, set it before the first start if the profile definitions are part of the installation. Do not turn it on while a route still depends on the permissive reading, for example a RES= that is kept for documentation and has no profile behind it. Drop that RES= first. Nothing changes without the keyword. A member that does not have it behaves as before, and D C shows HTTPD138I RES_NOPROFILE ALLOW.

What the setting does not do. HTTPD sees only the answer of the security product that says “no profile” (return code 4). It cannot tell why the product had none to apply, and with DENY every such answer refuses. This guide does not state in which conditions, other than a missing profile, a product gives that answer. The setting does not touch a route with AUTH=NONE, and it does not change the answer of a profile that exists.

A wrong value stops the start. A value that is neither ALLOW nor DENY is not read as ALLOW. It gives HTTPD427E and HTTPD420E, and the server does not start (“A Route Without a Valid Policy”).

What Needs No Login

Some requests pass without a login, whatever the route says.

Everything else needs the login that its route asks for. The display modules are no exception. They have routes with AUTH= like any module.

A route that stands above another one decides for the paths of both (“The Order of the Routes and the First Match”). Check a protected path after each change of the routes without a login. The answer must be 401, 302 or 403.

Console Messages for Logins

HTTPD writes the logins that matter to the operator and nothing about a failed password. That is the client’s concern, and it appears in the response. One message is about the security product and not about a login: HTTPD042W at the start (“A Security System That Accepts Any User ID”).

MessageMeaning
HTTPD007IA form login worked. The message gives the user ID, the IP address of the client and the address of the ACEE.
HTTPD008IA GET of /logout ended a login. The text is LOGOUT SUCCESSFUL.
HTTPD008WThe logout failed for a login that HTTPD knows. The browser sees the text Logout failed..
HTTPD009WFrom HTTPD 4.5. A login could not create a session because RACF failed for another reason than the password.
HTTPD042WFrom HTTPD 4.5.1. At the start. The security product accepted a logon with a user ID and a password that cannot be right. HTTPD refuses all logins until it is started again (“A Security System That Accepts Any User ID”).

Table 22. The messages for logins

A logout without a login answers You are not logged in. No logout
performed.
, and the console stays quiet.

The next capture shows a session on the console. The line from RAKF in the middle is not an HTTPD message. RAKF reports a login of an unknown user ID, here from a request with the user NOSUCH, in its own message RAKF0004. HTTPD writes the other two:

HTTPD001I HTTPD 4.5.1 READY - SERVING /www
RAKF0004 INVALID ATTEMPT TO ACCESS SYSTEM:  USER:NOSUCH
HTTPD007I USER IBMUSER  IP 192.0.2.17 LOGIN SUCCESSFUL ACEE(9BB190)
HTTPD008I USER IBMUSER  IP 192.0.2.17 LOGOUT SUCCESSFUL

The command D L lists the logins that exist (Chapter 8, “Operating HTTPD”).

What HTTPD Does Not Do

HTTPD is a small server. Know the limits before you open it to a network.