Using JWT User Assertion

To enable and use signed user assertions, you must:

  1. Generate a signed, encoded JWT user assertion using the private key that corresponds with the public certificate uploaded to the confidential application. There are several libraries available to generate and sign a JWT user assertion available here: https://jwt.io/libraries
  2. Generate the access token.

For requirements for the JWT header and payload, refer to your identity provider's documentation.

A user assertion includes a header, body, and signature.

The header must include the following attributes:

Name Value
kid

The key identifier identifies the trusted, third-party certificate for validating the assertion signature. The KID must match the certificateAlias of the public certificate in your Oracle IAM confidential application.

Choose either to use a KID or x5t. You do not need to use both.

x5t

Base64 URL encoded X.509 certificate SHA-1 thumbprint. Used to identify the trusted third-party certificate to validate the assertion signature.

Choose either to use a x5t or KID. You do not need to use both.

type The type identifies the type of assertion. For Primavera Data Service, use JWT.
alg The algorithm identifies the specific type of JWT signing algorithm being used. For Primavera Data Service, use RS256.

The body must include the following claims:

Name Value
sub The Primavera Data Service account user name.
iss

The client ID of the confidential application.

See: Prerequisite Setup

aud The audience defines the recipients for which the JWT is intended. For Primavera Data Service, use https://identity.oraclecloud.com.
exp The expiration time of the JWT assertion, specified in UNIX epoch time.
iat The date the assertion was issued, in UNIX epoch time.
jti The unique identifier for the JWT. A JWT ID can only be used once.

Here is an example JSON header and body:

{"kid":"MyCertificateAlias","type":"JWT","alg":"RS256"} 
      {"sub":"Primavera_Data_Service_UserName","iss":"MyClientID","aud":"https://identity.oraclecloud.com/","exp":1708778535,"iat":1708774935,"jti":12345}

The header and body are Base64-encoded, concatenated with a dot, and signed in the RS256 algorithm using your private key. The result is three Base64 strings separated by dots in the format of <header>.<body>.<signature>.

Here is an example signed user assertion:

eyJraWQiOiJNeUNlcnRpZmljYXRlQWxpYXMiLCJ0eXBlIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJQNldTX1VzZXJOYW1lIiwiaXNzIjoiTXlDbGllbnRJRCIsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHkub3JhY2xlY2xvdWQuY29tLyIsImV4cCI6MTcwODc3ODUzNSwiaWF0IjoxNzA4Nzc0OTM1LCJqdGkiOjEyMzQ1fQ.jaQ2NyGk8wOWWHMGi2QJTsYlKGcHrfqkvP2Gb8AlBbJDQy7NDonXh6YMcAe17iIVaOfH7lDgJyF95xPv3nPHdIezbqobHBck34yct6I6a_xpKcV5kmJfXLHeb9LenqZTbdMMQ95vlUL8R914AmE2TbwGqjl4XkIoADHDez7PVM2MwyIDSfEaQ6o7J05ES7wIgI9gGspQ5w-2Xem4GOare25FBo-LrgVADDiAhKUhSLNT6XISCMAHZ3L2J86cnRhU1fekr-DJYFyfDcgAZeQPSPETHGokBWYtC1K-2qIouODKBKBcooABYEh6YTkC7bdax5KqFFbvJmSfDEjyN3tZ4w

Example using Python

The variables in this example must be replaced with the following information:

  • <identity_domain_url>: The URL of your OCI identity domain.
  • <identity_domain_confidential_application_clientID>: The client ID of the confidential application in your OCI identity domain.
  • <base64_encoded_clientID:client_secret>: The Base64 encoded client ID and client secret provided by your identity provider.
    import requests, base64, uuid, sys, hashlib 
          from datetime import datetime, timezone ##timezone only available in python 3.2+ 
          import jwt ##jwt requires "pip install pyjwt" 
          from OpenSSL.crypto import load_pkcs12, dump_privatekey, dump_certificate, FILETYPE_PEM, FILETYPE_ASN1 
           
          #script can be executed as follows: 
          #python3 getOAuthToken_example.py <username> <expiry> 
          #Ex: python3 getOAuthToken_example.py JonesE 86400 
          #    where 'JonesE' is the username and 86400 is the expiry in seconds 
           
          #This python script example includes both the JWT user assertion generation and 
          #OAuth access token generation using base64-encoded client ID and secret 
           
          #MISC VARIABLE CREATION 
          userName = sys.argv[1] 
          expiryDuration = int(sys.argv[2]) 
          tokenIssued = int(datetime.now(tz=timezone.utc).timestamp()) 
          tokenExpiry = tokenIssued + expiryDuration 
          tokenJti = str(uuid.uuid4()) #generates random UUID 
          tokenEndpoint = '/oauth2/v1/token' 
          idp_url = 'https://<identity_domain_url>' + tokenEndpoint 
          clientId =  '<identity_domain_confidential_application_clientID>' 
          clientSecret = '<base64_encoded_clientID:client_secret>' 
          audience = 'https://identity.oraclecloud.com/' 
          signing_alg = 'RS256' 
           
          #SCOPE VARIABLE CREATION 
          #scope = 'urn:opc:idm:__myscopes__'  #allows an expiry range between 60s to 3600s 
          scope = 'urn:opc:idm:__myscopes__ urn:opc:resource:expiry=' + str(expiryDuration) #allows an expiry range between 60s and 31556952s 
          #LOAD PKCS12 AND READ PRIVATE KEY IN PEM FORMAT 
          p12_file = open('/home/oracle/jwt.p12', 'rb').read()  #path is relative to location of P12 file 
          p12_pwd_bytes = "password1".encode('utf8')  #password is relative to p12 file 
          p12 = load_pkcs12(p12_file, p12_pwd_bytes) 
          private_key = dump_privatekey(FILETYPE_PEM, p12.get_privatekey()) 
           
          #GENERATE KID FOR JWT USER ASSERTION 
          #Equal to the public certificate alias uploaded to your identity provider's confidential application 
          certAlias = 'jwtkey'  #alias is relative to the alias of private key 
           
          #GENERATE X5T FOR JWT USER ASSERTION 
          #Equal to the base64, url-encoded X.509 certificate sha1 thumbprint 
          cert = p12.get_certificate() 
          cert_der = dump_certificate(FILETYPE_ASN1, cert) 
          sha1_hash = hashlib.sha1(cert_der).digest() 
          x5t = base64.urlsafe_b64encode(sha1_hash).decode('utf8').rstrip('=') 
           
          #BASE64 ENCODE CLIENTID:CLIENTSECRET 
          clientIdSecret = clientId + ':' + clientSecret 
          clientIdSecret_bytes = clientIdSecret.encode("ascii") 
          clientIdSecret_base64_bytes = base64.b64encode(clientIdSecret_bytes) 
          clientIdSecret_base64_string = clientIdSecret_base64_bytes.decode("ascii") 
           
          #JWT CREATION 
          #Either the x5t or kid claim must be present in the JWT assertion header (no need for both) 
          #but this example will include both for demonstration purpose 
          header = { 
              "alg":signing_alg, 
              "typ":"JWT", 
              "kid":certAlias, 
              "x5t":x5t 
          } 
          user_payload = { 
              "sub":userName, 
              "iss":clientId, 
              "aud":audience, 
              "iat":tokenIssued, 
              "exp":tokenExpiry, 
              "jti":tokenJti 
          } 
          encoded_user_assertion = jwt.encode( 
              payload=user_payload, 
              headers=header, 
              key=private_key, 
              algorithm=signing_alg) 
           
          #OAUTH REQUEST HEADER AND PAYLOAD CREATION USING CLIENT ID AND CLIENT SECRET 
          headers = { 
            'Content-Type':'application/x-www-form-urlencoded', 
            'Authorization':'Basic ' + clientIdSecret_base64_string 
          } 
          payload = { 
          'grant_type':'urn:ietf:params:oauth:grant-type:jwt-bearer', 
          'scope':scope, 
          'assertion':encoded_user_assertion 
          } 
           
          #OAUTH TOKEN REQUEST EXECUTION 
          response = requests.request("POST", idp_url, headers=headers, data=payload) 
           
          #SCRIPT OUTPUT 
          print() 
          print() 
          print() 
          print('***************************************************************') 
          print('JWT OAUTH TOKEN GENERATION TEST') 
          print('***************************************************************') 
          print() 
          print('******************************') 
          print('ENVIRONMENT') 
          print('******************************') 
          print('OAUTH TOKEN GENERATION FOR USER:') 
          print(userName) 
          print() 
          print('******************************') 
          print('VARIABLES') 
          print('******************************') 
          print('TOKEN EXPIRY DURATION:') 
          print(str(expiryDuration) + ' seconds') 
          print() 
          print('TOKEN JTI:') 
          print(tokenJti) 
          print() 
          print('ENCODED CLIENTID:SECRET:') 
          print(clientIdSecret_base64_string) 
          print() 
          print('IDCS OAUTH TOKEN ENDPOINT:') 
          print(idcs_url) 
          print() 
          print('P12 PRIVATE KEY:') 
          print(private_key) 
          print() 
          print('ENCODED JWT USER ASSERTION:') 
          print(encoded_user_assertion) 
          print() 
          print('******************************') 
          print('IDENTITY PROVIDER OAUTH TOKEN REQUEST HEADERS AND PAYLOAD') 
          print('******************************') 
          print('REQUEST HEADERS:') 
          print(headers) 
          print() 
          print('REQUEST PAYLOAD:') 
          print(payload) 
          print() 
          print('******************************') 
          print('OAUTH TOKEN RESPONSE') 
          print('******************************') 
          print(response.json()) 
          print()
    
    

Example using Java Code

For an example of using Java Code to create a JWT token for an assertion grant type, see: https://www.ateam-oracle.com/post/creating-a-jwt-token-for-an-assertion-grant-type-flow