JSON Web Token (JWT) is a proposed Internet standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims. The tokens are signed either using a private secret or a public/private key
What does a JWT look like?
A JWT has three parts
- Header: Information about the JWT itself, such as the Algorithm type.
- Payload: This contains information such as creation datetime, expiry datetime, secret key, userid.
- Encrypted checksum: (the signature) of parts 1 and 2.
A header looks like
{
"alg": "RS256"
}
A payload looks like
{
"sub": "IBMUSER",
"iat": 1753381341,
"exp": 1753410141,
"iss": "APIML",
"jti": "699cdd3b-832a-4b27-95f6-2a0a9fd5375a",
"dom": "security-domain"
}
Where
- sub: is the principal (userid) within the payload
- iat: issued time
- exp: expiry time
- jti: is unique value
- dom: is the security domain.
These are converted to base64 where each 3 bytes are encoded as 4 printable characters.
The payload could include a userid and a file name, and because an authentication server has created the JWT, the server can send the file to the user without much more authentication.
How does it work?
I understand (from reading the limited online documentation), that it works as follows.
- Create your payload
- Create your header
- Calculate the signature
- Creating the JWT
- Send the JWT to the recipient
- The server authenticates the JWT
- Use the information in the JWT payload.
Create your payload.
Some fields you might specify are
- sub: subject – such as a userid
- iat: The issued time
- exp: The expiry time
- jti: A unique value for the JWT
- You might put in a public certificate name
- you can include a public key as a JSON Web Key (JWK) representation of cryptographic key.
Create your header
This will include the algorithm used to sign the JWT.
Calculate the signature
You use a private key, and the algorithm, and calculate the signature (encrypted checksum) of the header and payload, and append it to the header and payload.
How long should it be valid for?
The JWT has the creation time, and the expiry time, after which the JWT is no longer valid.
You need to consider what valid interval to use. Remember a JWT is a pre-authorised userid token. It can be used multiple times before it expires. A hacker could copy it and use it.
You might chose a short time if it is just used to establish a session, and is not used again.
You might use a longer interval if you keep connecting and disconnecting from servers. Once it expires you have to request another JWT. The longer the expiry interval, the longer a hacker can use the JWT.
Creating the JWT
There are different ways of creating the JWT. For example
- You can use Java
- use the Python package pyJWT
- Use RACF services. This creates a JWT. It requires supervisor state, so is not easy to set up. You can create RACF profiles which are used to create the JWT.
Send the JWT to the requester
The JWT is sent to the client which can use it to authenticate with a back end server which supports JWTs.
The server authenticates the JWT
This is server dependent.
- One approach is to go through every public certificate in the key store, until the right one is found
- You could specify the name of the public certificate in the header, and use that.
Note. This is different to TLS and certificates. With TLS the public certificate is sent as part of the handshake. The public key is authenticated by the Certificate Authority (CA) at the recipient. With TLS you only needs the CA certificate, not the individual certificates to validate. (Except for self signed which you should not be using). With a JWT you need the public certificate, it is not passed within the JWT
Note 2. There is discussion about a server sending the jti value in the JWT to an authorisation server, to get back the public key, which can be validated with a CA etc.
The back end may need to validate with the authorisation server. The request may be along the lines of “I’ve received userid COLIN, and the JTI from the JWT value is COLIN123456… is this valid?” This could be a cross memory request, for example using a RACF service, or it could be a TLS request to a server.
If the response is positive, then you can trust the information in the payload.
Use the information in the JWT payload.
The backend should not just use the userid in the payload, because I could easily create my own JWT (such as with pyJWT), as long as the server has my public key, the signature matches and verifies my unofficial JWT, and so I get unauthorised access.
With the openidConnectClient support in Liberty ( used by z/OSMF, MQWEB, and Zowe) you can have multiple openidConnectClient definitions. Each definition can have a filter, so you can restrict each openidConnectClient definition to a particular IP address range, or URL, (or both).
With openidConnectClient you specify the realm name to be used.
On midrange machines, it looks like the userid may be taken from the payload, and used!
Mapping name information to RACF userid.
Once the JWT has been validated in the openidConnectClient definition, the code looks up the JWT userid and realm combination to find which z/OS userid should be used.
There is a RACF service R_usermap (IRRSIM00): Map application user which maps a name and realm to a RACF userid.
You can define the mapping using the RACF command RACMAP, for example:
RACMAP ID(COLIN) MAP USERDIDFILTER(NAME('COLIN')) -
REGISTRY(NAME('PRODZOSMF')) -
WITHLABEL('COLIN')
RACMAP ID(ADCDA) MAP USERDIDFILTER(NAME('CN=HACKER')) -
REGISTRY(NAME('*')) -
WITHLABEL('ADCDA')
RACMAP ID(COLIN) LISTMAP
RACMAP ID(ADCDA) LISTMAP
SETROPTS RACLIST(IDIDMAP) REFRESH
This says
- if userid COLIN from the JWT, and the realm PRODZOSMF, then use userid COLIN.
- If userid HACKER from the JWT in any realm, then use userid ADCDA.
If you get RACF abend 648 reason 0000004 see here.
To use this the userid using r_usermap needs permission.
PERMIT IRR.IDIDMAP.QUERY CL(FACILITY) ID (IBMUSER) ACCESS(READ)
SETROPTS RACLIST(FACILITY) REFRESH
Note. The input to r_usermap is UTF8 (ascii) so the COLIN was passed in as 0x434F4C494E.
Use of different registries.
Because there may be multiple sites sending a JWT, you can use the REGISTRY name to further qualify the mapping. The registry is passed (in ASCII) on the r_usermap call – but you can specify a generic * (ASCII 0x2a) for any registry.
In the JWT above it had “dom”: “security-domain”. You could specify this as the registry.
Planning is required
To use the r_usermap service you need to plan before you start.
- What format “userid” strings will be used? Is this just a string, or you you create a compound string “origin”.”userid” where origin and userid are taken from the JWT
- Use of the registry field. If you want to use the registry field to identify different originators machines, you need to ensure that the JWTs are produced with the appropriate domain. If all requests have “dom”:”security-domain” it adds no value.