OpenID Connect and JSON Web Token (JWTs) are trendy security technologies that many companies choose to use. And believe it or not, you can also use these standards to secure access to CICS applications.
In the first part of this blog we'll introduce you to OpenID Connect and JSON Web Tokens (JWTs) and highlight how these technologies offer an increasingly popular way to federate identity across different platforms and applications.
In the second part, we’ll explore the use of OpenID Connect in a CICS Liberty JVM server. We’ll explain how CICS can play a part in an OpenID Connect flow and we’ll show an example configuration using the openidConnectClient-1.0 Liberty feature.
If you've kept abreast with the latest standards for enterprise authentication, authorization, and federated identity - then well done to you! But most people haven't, and its little wonder the rest of us struggle to navigate the choices. We're in a world of competing standards, evolving protocols, open frameworks, and workgroup initiatives; all of which contribute to a specification spaghetti. In this blog, we hope to offer some insight into the choices, demonstrate why OpenID Connect is a popular choice, and explain how you can take advantage of this open security standard in a CICS Liberty environment.
We'll start with some simplistic definitions: Authentication is proving WHO you are, authorization is WHAT you have permission to do. They both shorten to 'auth' which can be really inconvenient. We can relate the 'auths' to two main standards: OpenID and OAuth. Generally speaking, OpenID is for federated authentication (identity), while OAuth is for delegated and bounded authorization (permission). Just to confuse you there is also OATH ("oath" not "oauth") - a reference architecture for the adoption of strong authentication - we won't concern ourselves with that in this blog.
Both OpenID and OAuth have version histories. You may have heard reference to OpenID 1.0 and OpenID 2.0, they are just prior versions of the OpenID Connect standard. There's also OAuth 2.0 which replaced OAuth 1.0 but is not backwards compatible - again we won't concern ourselves with that.
Taking a closer look at the two main technologies, OpenID can be compared to border control checks and is about trusting who you are. When you go through passport control, you authenticate to the airport security personnel by showing them your Passport (ID token). The Passport is issued by a trusted national authority (Identity Provider) and it contains claims about you (name, age, nationality, etc.).
OAuth on the other hand is an open standard for access delegation and is about permitting who can receive or use your information. It is commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords. For example, OAuth allows a resource provider (e.g. Facebook, Google, etc.) to be notified that the resource owner (you) grant permission to a third-party (an application, shopping site, or user-group perhaps) to access portions of your information (e.g. your name, email address, title, delivery address, or list of friends).
OpenID Connect, the third version of the OpenID standard, is based on OAuth 2.0 (an authorization protocol) combined with elements of OpenID 2.0 and OpenID Attribute Exchange 1.0 into a single protocol to provide both authentication and authorization.
In summary, OpenID is about verifying a person's identity. OAuth is about accessing a person's stuff. OpenID Connect does both.
The benefits of OpenID Connect (OIDC) are compelling. OIDC provides a single sign-on (SSO) solution, it allows enterprises to authenticate and authorize their users across their applications and between different platforms. A centralized server can be maintained internally, or you can use a trusted external server - in both cases access is provided through standardized RESTful services. Without OIDC, you have to trust that every application has your user's best interests and privacy in mind, you have to trust that identities are protected, and that security best practices are upheld in each application. With OIDC, those security concerns are avoided, users can prove who they are and grant applications access to their resources - without that application ever seeing, or compromising, their credentials.
The industry appears to be converging upon OIDC, not just because of its capabilities and standardized access methods, but also because of the simplicity of the tokens and the well-defined, secure flows.
In a typical OIDC scenario there are different 'actors' - the user, an OpenID Connect Provider, a Relying Party and a Resource Server.
- The end user is the human participant
- An OpenID Connect Provider (OP) is an OAuth 2.0 authorization server that offers authentication as a service
- A Relying Party (RP) is the client application that relies on the OP in order to authenticate users and request claims
- A Resource Server (RS) manages resources (data, programs etc.) that are accessed by end users directly or by the RP acting on behalf of a user
In OIDC terms, an identity is a set of claims (attributes) related to an entity (person, service, machine). The end user typically runs an application used to interact with a set of services (sometimes it is a web browser). An application that wishes to take part in an OIDC flow must first register with the OP. When doing so, it receives a client ID, which is public, and a client secret which should be kept private. This registration is usually a one-off operation performed before deployment of the application and allows the two to communicate securely.
Let's talk about tokens. Tokens are OIDC's way of passing information around. OIDC uses three token types, an identity token to represent a user or identity, an access token to represent access permissions, and a refresh token to re-generate the time-bounded access tokens.
The JWT spec, released in 2015, included provisions for cryptographically signed JWTs (called JWSs) and encrypted JWTs (called JWEs). A signed JWT is particularly useful in application development because you can have a high degree of confidence that the information encoded into the JWT has not been tampered with.
Figure 2 shows an example signed JWT which consists of three parts: a header, payload and signature.
The header contains the algorithm being used, such as HMAC SHA256 or RSA SHA256, and is Base64Url encoded to form the first part of the JWT.
The payload contains the claims. There is a set of predefined claims, for example: iss (issuer), exp (expiration time), sub (subject) and aud (audience). These are not mandatory but recommended, to provide a set of useful, interoperable claims. The payload may also include additional attributes that define custom claims such as employee role. Typically, the sub claim is used to create the OpenID Connect user subject.
In the above example:
- The iss (issuer) claim idg identifies the principal that issued the JWT.
- The sub (subject) claim is the identity cn=JeanLeclerc,ou=employees,o=ibm,c=fr.
- The aud (audience) claim urn:myEntity identifies the recipient that the JWT is intended for.
Note: The aud claim is optional. It can be used to identify a target application, a commercial entity, or any other entity defined by business processes.
- The exp (expiration time) claim identifies the expiration time on or after which the JWT must not be accepted for processing.
- The iat (issued at) claim identifies the time at which the JWT was issued.
The payload is Base64Url encoded to form the second part of the JWT.
To create the signature part the encoded header and payload are signed using the signature algorithm from the header. The signature is used to verify that the issuer of the JWT is who it says it is and to ensure that the message wasn't changed along the way.
OpenID Connect Flows
There are three different flows defined by OIDC: Authorization Code flow, Implicit flow and Hybrid flow.
- The Authorization Code flow returns grant code to the client, which can then exchange it for an ID Token and an access token directly. This provides the benefit of not exposing any tokens to the User Agent (normally a browser) and possibly other malicious applications with access to the User Agent.
- The Implicit flow is mainly used by clients implemented in a User Agent (normally a browser) using a scripting language. The access token and ID token are returned directly to the client, which may expose them to the end user and applications that have access to the browser.
- The Hybrid flow is a combination of both Authorization Code flow and Implicit flow and is rarely used.
It might help to consider flows as scenarios, or use-cases. The three different flows are typically for different application architectures and are chosen based on how well the client application can keep a secret (architecturally speaking).
If you are using a traditional web application where some information is passed around on the front end (and anyone can peek at it) but it also has back end code that can talk to the OP in secret, you would normally use the Authorization Code Flow.
Let's take a closer look at the Authorization Code flow:
Figure 3 shows the following steps:
- The user makes a request to the client application or Relying Party (RP).
- The RP redirects the request to the OpenID Connect Provider (OP).
- The OP sends an authentication and authorization request to the user.
- The user authenticates and authorizes the client application to access the resource.
- The OP redirects the user to the RP with a grant code.
- The RP sends a request to the OP to exchange the grant code for an ID token (in the form of a JWT), access token (which could also be in the form of a JWT) and a refresh token.
- The OP sends the ID token, access token and refresh token to the RP.
- The RP makes the request to the Resource Server (RS) with the access token which is used for identifying the user and authorizing access to the resource.
- The RS verifies the access token and sends the response to the RP.
- The response is sent from the RP to the user.
If you are not an enterprise security architect, maybe this is the point at which the eyes start to glaze over? If so, don’t worry! In most cases, CICS plays the role of the Resource Server so if you are a CICS systems programmer you only need to care about steps 8 and 9 and we’ll show you how to configure this part next. But maybe it’s interesting for you to see the end to end flow.
The Authorization Code flow is suitable for traditional web apps as well as native/mobile apps and involves an initial browser redirection to/from the OP for user authentication and consent. A second back-channel request (steps 6 and 7 between the RP and OP) is then made to retrieve the tokens. This flow offers optimal security, not just because the user can be authenticated, but more importantly that tokens aren't revealed to the browser.
Within the flow, we see clearly how the different tokens are used. The ID token (JWT) is used for authentication of the user in the RP, the access token authorizes the RP to access a resource on behalf of the user, and the refresh token is used by the RP to obtain a new access token when the old one expires.
It may not be immediately obvious why a refresh token and an access token are used. The access token must be short lived, it has a small window before expiration to reduce the risk of someone capturing the token and masquerading as the user. The RP however will want to make many requests to the OP on behalf of the user without having to keep prompting the user to log-in. So when the access token expires, the RP can provide the refresh token to the OP to get a new access token, the user is unaware - and because access to the refresh token requires the client-secret, it prevents someone else or something else using the refresh token. Additionally, should the user account be compromised or revoked, the OP can reject the request from the client application by invalidating the refresh token.
The access token is sent to the RS as a Bearer token (“give access to the bearer of this token”). It is becoming more and more common for access tokens to be generated in JWT format. A JWT is self-contained and can be verified and consumed directly in the RS rather than having to make a call to a token validation service. A JWT can also be extended to include custom claims.
Next, we’ll see how a JWT can be sent to CICS and used to securely identify an end user; in this scenario we are effectively using the JWT for third-party authentication where the OP is the trusted authentication server and the RP sends the access token (JWT) to CICS.
Configuring OpenID Connect with CICS Liberty
CICS Liberty supports OIDC and can play a role as a RP, OP or RS. In this blog we show an example OIDC configuration for a CICS Liberty JVM server acting as a RS.
In figure 4, we see how a JWT can be sent to CICS and used to securely identify an end user; in this scenario we are effectively using the JWT for third-party authentication where the OP is the trusted authentication server and the RP sends the access token (JWT) to CICS.
Before we look at how to configure the JVM server to support this scenario, let’s consider again the example JWT shown in figure 2 above, in particular, the sub claim cn=JeanLeclerc,ou=employees,o=ibm,c=fr. If, as is normally the case, the Liberty JVM server is configured with a RACF user registry, how do we authorize access to CICS resources using this distributed (X.500 format) identity? The answer is that first we need to map the identity to a RACF ID contained in the RACF user registry.
The steps below outline a Liberty JVM server configuration for verifying a JWT access token that is sent to the JVM server in the HTTP Authorization header as a Bearer token, identifying the user based on the sub claim of the JWT and mapping the distributed identity to a RACF ID.
- Add the openidConnectClient-1.0 Liberty feature to the server.xml file and make sure that the cicsts:security and appSecurity features are also configured.
- Configure an openidConnectClient element, for example:
<openidConnectClient id="RS" clientId="RS-JWT-CICS" inboundPropagation="required" signatureAlgorithm="RS256" trustStoreRef="JWTTrustStore" trustAliasName="JWTTrustCert" userIdentifier="sub" mapIdentityToRegistryUser="false" issuerIdentifier="idg" disableLtpaCookie ="true" audiences="urn:myEntity"/>
In the above example:
- id and clientId are element identifiers.
- inboundPropagation is set to required. This is an important setting. It effectively says to the Liberty server that a JWT must be present in the request. The JWT is then used to create the authenticated subject.
- signatureAlgorithm specifies the algorithm to be used to verify the JWT signature.
- trustStoreRef specifies the id of the keyStore element that defines the location of the validating certificate.
- trustAliasName gives the label of the certificate to be used for signature validation.
- userIdentifier indicates the claim to use to create the user subject. This identity is then used for running the request. Typically, this is the sub claim, but it can be overridden using this attribute if required.
- mapIdentityToRegistryUser indicates whether to map the retrieved identity to the registry user. We set this to false because the identity in the JWT is a distributed identity from a different user registry, and needs to be mapped to a user in the RACF user registry.
- issuerIdentifier defines the expected issuer.
- disableLtpaCookie indicates whether an LTPA token should be created. We do not want an LTPA token to be created so we set this to true.
- audiences defines a comma-separated list of target audiences (the audience in the JWT must match one of the defined audiences)
- Configure a truststore that is used to validate the JWT signature. In this example a private key is used to sign the JWT so the X.509 certificate containing the public key must be stored in the Liberty JVM server truststore.
- Add a keyStore element to the server.xml configuration file for the truststore.
The id attribute value of the keyStore element must match the value specified on the trustStoreRef attribute of the openidConnectClient element. Below we define a RACF Keyring to be used as the truststore.
<keyStore id="JWTTrustStore" fileBased="false" location="safkeyring:///myKeyRing" password="password" readOnly="true" type="JCERACFKS" />
- Add the X.509 certificate that contains the public key required to validate the JWT signature into the truststore as a trusted certificate. For a RACF Keyring, we connect the X.509 certificate with a usage of CERTAUTH.
- Set the attribute mapDistributedIdentities="true" on the safCredentials element in the server.xml configuration file:
This configures the JVM server to use distributed identity filters to map distributed identities to RACF user IDs.
- Activate the RACF IDIDMAP class. Enter the following RACF command:
SETROPTS CLASSACT(IDIDMAP) RACLIST(IDIDMAP)
- Define a distributed identity filter in RACF to map the distributed user ID from the sub claim to a RACF user ID.
For example, enter the following command:
RACMAP ID(EMPLOY1) MAP USERDIDFILTER(NAME('cn=JeanLeclerc,ou=employees,o=ibm,c=fr')) REGISTRY(NAME('*')) WITHLABEL('Test Mapping EMPLOY1')
In this example, the following values are used:
For more information about the RACMAP command, see the z/OS Security Server RACF Command Language Reference.
- EMPLOY1 is the RACF user ID to which the distributed user ID from the JWT identity claim is to be mapped.
- cn=JeanLeclerc,ou=employees,o=ibm,c=fr is the distributed identity from the JWT sub claim to be mapped.
- REGISTRY(NAME('*')) will match any registry realm name. Alternatively, to match a specific realm, replace the * with the value specified on the realmName attribute of the openidConnectClient
Note: This example shows a one-to-one mapping. You could also use a many-to-one mapping, for example, to map all employees to the same RACF user ID
- Refresh the RACF IDIDMAP class.
For the RACF changes to take effect, enter the following RACF command:
SETROPTS RACLIST(IDIDMAP) REFRESH
Note: Steps 4 to 7 above are only necessary if the sub claim in the JWT is not a RACF ID and therefore needs to be mapped to a RACF ID.
In this blog, we have introduced OpenID Connect and explained how the openidConnectClient-1.0 feature can be used to securely identify end users accessing a CICS Liberty application.
End users authenticate with an OpenID Connect Provider and then all back end services are accessed with a trusted JWT. Even if an end user has authenticated with a distributed identity that is not known to CICS, we can configure a CICS Liberty JVM server to map the user’s distributed identity to a RACF ID. This RACF ID can then be used to authorize the end user to CICS resources.
Look out for more blogs on how to configure a CICS Liberty JVM server as an OP or RP within an OIDC flow.
For more information on using OpenID Connect with Liberty z/OS see https://www.ibm.com/support/knowledgecenter/SS7K4U_liberty/com.ibm.websphere.wlp.zseries.doc/ae/rwlp_using_oidc.html
This blog has been written by Ivan Hargreaves, Nigel Williams and Eric Phan, with the contributions of Phil Wakelin and Mark Cocker.