Chris Malek is a PeopleTools® Technical Consultant with two decades of experience working on PeopleSoft enterprise software projects. He is available for consulting engagements.
About Chris Work with ChrisIntroducing a small but powerful PeopleSoft bolt-on that makes web services very easy. If you have a SQL statement, you can turn that into a web service in PeopleSoft in a few minutes.
OAuth 2.0 is a protocol that allows a user to grant limited access to their resources on one site to another site without having to expose their credentials. It is commonly used as a way for web services to authenticate and authorize users. It can be very confusing to set up and use across vendors as the terminology is dense and different vendors can have slightly different implementations and naming. I find the same thing with SAML.
PeopleSoft has added support for OAuth 2.0 in PeopleTools 8.59. A few different IDPs (Identity Providers) are supported out of the box, including:
OAuth 2.0 is a complex topic, and I will not be able to cover all the details in this guide. I will focus on how to set up OAuth in PeopleSoft and how to use it in your REST services.
This guide will cover:
What this guide will NOT cover:
Warnings and Caveats:
Random Facts
ig.AuthorizationServer
ig.JSONWebKey
First let’s talk about the mapping of the JWT claims to an OPRID. This mapping is important because the JWT token will have a claim that will be used to map to a valid OPRID in PeopleSoft. This is how PeopleSoft will know who the user is that is making the request and then basically authenticate the API call as a valid user. No code in PeopleSoft can run without some valid user context.
When using openid connect, the JWT token will have two important claims that are used to map to a valid OPRID.
sub
claim has to contain a value that maps to a valid OPRID. This is case-sensitive, and the IDP must know this value.email
claim has to contain the email address for the OPRID. This email address has to match the email address on the PSUSEREMAIL
table for the OPRID in the sub
claim.
PSUSEREMAIL
table which is not always common across the clients I have worked with. Additionally, in non-production environments the email address is often scrambled so non-production systems don’t send out emails.Make sure that your IDP and PeopleSoft can send and receive these claims before you proceed.
What I would like PeopleTools to be able to do is to have a custom PeopleCode function that can map a claim to an OPRID. This could also be accomplished with some sort of configuration file on the server that could map a claim to an OPRID. I find the current setup to be very limiting and not very flexible. It looks more like an MVP (Minimum Viable Product) than a full-featured solution. This is coming from working on many clients where the OPRID landscape has evolved over time and is not always clean and consistent.
First, we need to create a basic REST service in PeopleSoft. We will create a simple “Metadata” service that will return the metadata about the user running the service and information about the database. We will first test it with basic Authentication and then switch to OAuth.
Here is a screenshot of the service operation setup:
Here is the entire PeopleSoft Handler Code.
|
|
We create a standard PeopleSoft OPRID and password and create a basic authentication header. If this is new to you then please read the REST Security Section for more information.
Now we use an API tester like CURL to test the service.
Here is the full HTTP request and response.
GET https://34.16.144.215:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_METADATA.v1/ HTTP/1.1
accept-encoding: gzip, deflate, br
accept: */*
authorization: Basic Q0hHX0FQSV9URVNUOkFVTEQ2YXJlbmEzYWNhcHVsY28=
HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-length: 214
content-type: application/json; encoding=UTF-8
date: Fri, 13 Dec 2024 17:07:13 GMT
x-oracle-dms-ecid: 5c8484a0-32b4-4ec3-b376-56dab4e1f77d-000000b1
x-oracle-dms-rid: 0
{
"currentUser": "CHG_API_TEST",
"dbname": "CS92DEV",
"toolsRelease": "8.61.03",
"psftTransactionId": "b2ac0244-b974-11ef-a5b0-4552a628bb05",
"responseDTTM": "Sat, 14 Dec 2024 01:07:13 GMT",
"dbType": "ORACLE",
"serverTimeZone": "PST"
}
For PeopleSoft accept JWT tokens from an IDP like OKTA, we need to establish a trust between the two systems. This involves “registering” PeopleSoft as a “client” in OKTA and then configuring PeopleSoft to accept the JWT tokens from OKTA.
What we are going for here is to get a JWT token created that has information about a valid PeopleSoft user that can be used to execute the REST service.
In my testing, I created a new “Authorization Server” in OKTA which allows you to control and remap the JWT Claims.
Some Important settings in OKTA on the Authorization Server:
sub
maps to the OPRID using appuser.userName
. This is OKTA’s way of saying “username” for the “application”.
sub
claim with the value of the OPRID. Otherwise, PeopleSoft will not be able to map the JWT token to a valid OPRID.email
maps to the email for the OPRID using user.email
. This email address has to match the email address on the PSUSEREMAIL table for the OPRID in the sub
claim.The Authorization Server setup will give you a MetaData URI which you will need to configure in PeopleSoft. In my case that is:
https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/.well-known/oauth-authorization-server
issuer
- Used in the “Create OAuth2 Service Apps” page in PeopleSoft
iss
claim is set to.jwks_uri
- Used in integrationGateway.propertiesauthorization_endpoint
- Used in the “Create OAuth2 Service Apps” page in PeopleSoft. Your web application will use this to redirect the user to OKTA to authenticate and authorize the application.
issuer
is used to validate the JWT token. (I need to validate this)token_endpoint
- Used in the “Create OAuth2 Service Apps” page in PeopleSoft. You web application will use this to get the access token from OKTA.
issuer
is used to validate the JWT token. (I need to validate this){
"issuer": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8",
"authorization_endpoint": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/authorize",
"token_endpoint": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/token",
"registration_endpoint": "https://dev-126519.oktapreview.com/oauth2/v1/clients",
"jwks_uri": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/keys",
"response_types_supported": [
"code",
"token",
"id_token",
"code id_token",
"code token",
"id_token token",
"code id_token token"
],
"response_modes_supported": [
"query",
"fragment",
"form_post",
"okta_post_message"
],
"grant_types_supported": [
"authorization_code",
"implicit",
"refresh_token",
"password",
"client_credentials",
"urn:ietf:params:oauth:grant-type:device_code"
],
"subject_types_supported": [
"public"
],
"scopes_supported": [
"openid",
"profile",
"email",
"address",
"phone",
"offline_access",
"device_sso"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"none"
],
"claims_supported": [
"ver",
"jti",
"iss",
"aud",
"iat",
"exp",
"cid",
"uid",
"scp",
"sub"
],
"code_challenge_methods_supported": [
"S256"
],
"introspection_endpoint": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/introspect",
"introspection_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"none"
],
"revocation_endpoint": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/revoke",
"revocation_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"none"
],
"end_session_endpoint": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/logout",
"request_parameter_supported": true,
"request_object_signing_alg_values_supported": [
"HS256",
"HS384",
"HS512",
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512"
],
"device_authorization_endpoint": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/device/authorize",
"dpop_signing_alg_values_supported": [
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512"
]
}
There are a few steps to set up PeopleSoft to accept JWT tokens.
Remember that for this article we are limiting the setup to have a protected PeopleSoft web service that is only accessible by a valid JWT token from OKTA. So the setup here is limited to the information that PeopleSoft needs to validate and trust the JWT token and map to a valid OPRID.
First we need to set up PeopleSoft using a page to “register” OKTA as an IDP.
Next we need to change the integration broker web server integrationGateway.properties
file to point to the OKTA JWKS URI. This is the URL that OKTA uses to publish the public keys that are used to validate the JWT token. If you have more than one web server make sure you are making this change on the web service that is hosting serving the REST service.
The ig.JSONWebKey
value is the URL that OKTA uses to publish the public keys that are used to validate the JWT token. For OKTA, this is in the metadata URI that we got earlier in the jwks_uri
field.
ig.AuthorizationServer=OKTA
ig.JSONWebKey=https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8/v1/keys
You can only have one IDP setup in PeopleSoft. I tried to set up two different IDPs and it did not work. I think you can only have one IDP setup at a time.
While testing this, I highly recommend that you also turn on the highest logging level for the integration broker. This will give you important troubleshooting messages that will show in the msgLog.html and errorLog.html files on the Integration Broker web server.
That is done by setting the following property.
ig.log.level=5
After those changes bounce the integration broker web service.
First let’s see what happens when we try to submit a bear token when the service operation is still set to NONE for authentication.
I will not explain how to get the Bear token because your application developers will have to do that. This is a complex topic and is not covered here. We just assume you have an application that can generate a valid JWT token from OKTA and submit it in the HTTP header as a Bearer.
Let’s look at the HTTP request and response.
GET http://34.16.144.215:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_METADATA.v1/ HTTP/1.1
accept-encoding: gzip, deflate, br
accept: application/json
authorization: Bearer eyJraWQiOiJKRnF1MExEOWIxWEJLSV9ZLS1MRlpiQmN6ZFNiZGdMSURxVzBoSkhaWWtRIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULlhTNDY4ZlZ0a2RGTmNQdWxqMWJwNl9SUEdCWjkxLUxqcXJrd1BlT2trdTQiLCJpc3MiOiJodHRwczovL2Rldi0xMjY1MTkub2t0YXByZXZpZXcuY29tL29hdXRoMi9hdXMyOXp3ZTh3cThTVmw4ODBoOCIsImF1ZCI6ImNoZy10ZXN0IiwiaWF0IjoxNzM0MTIyODU5LCJleHAiOjE3MzQxMjY0NTksImNpZCI6IjBvYTI5eHBzcGxua2xTS0F5MGg4IiwidWlkIjoiMDB1NXJxaDR0bEkya3ZvczMwaDciLCJzY3AiOlsib3BlbmlkIl0sImF1dGhfdGltZSI6MTczNDEwOTc2Niwic3ViIjoiQ0hSSVNNQUxFSyIsImVtYWlsIjoiY2hyaXMubWFsZWtAY2VkYXJoaWxsc2dyb3VwLmNvbSJ9.R9yzqIijo4hD8fooiOMjT_ZVUYDOXLDA_05gfTErcU8EJYHQyG3Npkj-Tg4XLE3DJvMnJohPsKFdrj65nWgKTMyxGQTawSgKJzuu4sMWGbt87L9yf-VabvrUMxNLmSpTNygXSemj8Gy5zxR8qrdHs5pRMHZfd4PQy-dFzV9r7RFG2ilBkJW6BPLc1-YuySSwYG1aAFvin1bluTpJLaTPn5i08btj08SGfpzAYP_gRQLlmj-puF3reSSxYB5hOZ08ZA2SoNhCLyBJXftGjz27wvpsNw0iM1CIKziNmO1JbT-okN5oA2AXPsEvSQwfUZG7ZUNE6Uy8E5s9B65QrwGHIA
user-agent: httpyac
HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-length: 212
content-type: application/json; encoding=UTF-8
date: Fri, 13 Dec 2024 21:05:21 GMT
x-oracle-dms-ecid: 5c8484a0-32b4-4ec3-b376-56dab4e1f77d-000001b4
x-oracle-dms-rid: 0
{
"currentUser": "CHRISMALEK",
"dbname": "CS92DEV",
"toolsRelease": "8.61.03",
"psftTransactionId": "f6ec619a-b995-11ef-a5b0-4552a628bb05",
"responseDTTM": "Sat, 14 Dec 2024 05:05:20 GMT",
"dbType": "ORACLE",
"serverTimeZone": "PST"
}
What did we see? The curretUser
is set to CHRISMALEK
which is the OPRID that the JWT token is mapped to. Where did that come from? Let’s look at the Bearer token and see what it is.
If you take the Bearer token and decode it using something like jwt.io you will see the following:
The JWT Header:
{
"kid": "JFqu0LD9b1XBKI_Y--LFZbBczdSbdgLIDqW0hJHZYkQ",
"alg": "RS256"
}
The JWT Payload:
{
"ver": 1,
"jti": "AT.XS468fVtkdFNcPulj1bp6_RPGBZ91-LjqrkwPeOkku4",
"iss": "https://dev-126519.oktapreview.com/oauth2/aus29zwe8wq8SVl880h8",
"aud": "chg-test",
"iat": 1734122859,
"exp": 1734126459,
"cid": "0oa29xpsplnklSKAy0h8",
"uid": "00u5rqh4tlI2kvos30h7",
"scp": [
"openid"
],
"auth_time": 1734109766,
"sub": "CHRISMALEK",
"email": "chris.malek@cedarhillsgroup.com"
}
sub
claim is set to CHRISMALEK
which is the OPRID that the JWT token is mapped to.email
claim is set to chris.malek@cedarhillsgroup.com
which is the email address for the OPRID in the sub
claim.If both of these do NOT match then the authentication will not work and the web service will NOT run..
Let see if we change the casing on the email address in PeopleSoft from chris.malek@cedarhillsgroup.com
to Chris.malek@cedarhillsgroup.com
(capital C) and then try to run the service again.
GET http://34.16.144.215:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_METADATA.v1/ HTTP/1.1
accept-encoding: gzip, deflate, br
accept: application/json
authorization: Bearer eyJraWQiOiJKRnF1MExEOWIxWEJLSV9ZLS1MRlpiQmN6ZFNiZGdMSURxVzBoSkhaWWtRIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULlhTNDY4ZlZ0a2RGTmNQdWxqMWJwNl9SUEdCWjkxLUxqcXJrd1BlT2trdTQiLCJpc3MiOiJodHRwczovL2Rldi0xMjY1MTkub2t0YXByZXZpZXcuY29tL29hdXRoMi9hdXMyOXp3ZTh3cThTVmw4ODBoOCIsImF1ZCI6ImNoZy10ZXN0IiwiaWF0IjoxNzM0MTIyODU5LCJleHAiOjE3MzQxMjY0NTksImNpZCI6IjBvYTI5eHBzcGxua2xTS0F5MGg4IiwidWlkIjoiMDB1NXJxaDR0bEkya3ZvczMwaDciLCJzY3AiOlsib3BlbmlkIl0sImF1dGhfdGltZSI6MTczNDEwOTc2Niwic3ViIjoiQ0hSSVNNQUxFSyIsImVtYWlsIjoiY2hyaXMubWFsZWtAY2VkYXJoaWxsc2dyb3VwLmNvbSJ9.R9yzqIijo4hD8fooiOMjT_ZVUYDOXLDA_05gfTErcU8EJYHQyG3Npkj-Tg4XLE3DJvMnJohPsKFdrj65nWgKTMyxGQTawSgKJzuu4sMWGbt87L9yf-VabvrUMxNLmSpTNygXSemj8Gy5zxR8qrdHs5pRMHZfd4PQy-dFzV9r7RFG2ilBkJW6BPLc1-YuySSwYG1aAFvin1bluTpJLaTPn5i08btj08SGfpzAYP_gRQLlmj-puF3reSSxYB5hOZ08ZA2SoNhCLyBJXftGjz27wvpsNw0iM1CIKziNmO1JbT-okN5oA2AXPsEvSQwfUZG7ZUNE6Uy8E5s9B65QrwGHIA
user-agent: httpyac
HTTP/1.1 401 Unauthorized
cache-control: no-store, must-revalidate, private
connection: close
content-length: 0
date: Fri, 13 Dec 2024 21:15:19 GMT
www-authenticate: Bearer realm="PeopleSoft Enterprise PeopleTools", error="invalid_token"
x-oracle-dms-ecid: 5c8484a0-32b4-4ec3-b376-56dab4e1f77d-000001ba
x-oracle-dms-rid: 0
If we look at the errorLog.html we will see two messages that are not super helpful.
Invalid Access Token Passed as part of request: OAUTH2 Authentication failed for Service Operation CHG_METADATA.v1. (158,461)
That is a bit confusing because the token is valid. The problem is that the email address in the JWT token does not match the email address in the PSUSEREMAIL table for the OPRID in the sub
claim. It does NOT tell you this anywhere.
In the application server log you will see some cryptic message that points to the problem. However, if you did NOT notice that the casing was different you would not know what the problem was.
(1) PSOAuth2: isInboundOAuth2Subject function: Failed to find Peoplesoft User for OAuth2 User <
(5) PSOAuth2: Payload String is empty.
(1) PSOAuth2: isInboundOAuth2Subject function: Failed to find Peoplesoft User for OAuth2 User <chris.malek@cedarhillsgroup.com>.
(1) PSOAuth2: OAuth2 user is not exist in peoplesoft env!
If we change the Service Operation “Req Verification” to OAuth2 it will still work but now the Basic Authentication will NOT work. This is because the service operation is now set to only accept the JWT token.
In my testing, if you try to do Basic Authentication with the service operation set to OAuth2 the web service send back an HTTP response like this:
HTTP/1.1 401 Unauthorized
cache-control: no-store, must-revalidate, private
connection: close
content-length: 0
date: Fri, 13 Dec 2024 22:04:49 GMT
www-authenticate: Bearer realm="PeopleSoft Enterprise PeopleTools", error="invalid_token"
x-oracle-dms-ecid: dd289cd9-8ec9-4d2e-b4a1-1868bf4cb208-0000003f
x-oracle-dms-rid: 0