Modifying the PowerBuilder app

Purpose

In this section, we will modify the PowerBuilder application settings and source code to achieve the following results:

  • Gets a token from the Microsoft Entra ID server

  • Creates the user session that includes the token.

  • Refreshes the token when necessary.

Specify the token URL

The token URL is where you can get a token.

Instead of hard-coding the token URL in the application scripts, we will specify the token URL in an INI file, so that the URL can be modified without affecting the scripts.

Step 1: Create an INI file in the same location as the PBT file and name it CloudSetting.ini.

Step 2: Specify the URL for requesting the token from Microsoft Entra ID in the INI file.

[Setup]
TokenURL=https://login.microsoftonline.com/0ffb9ae0-c080-4913-aa94-ed08b5de4d40/oauth2/v2.0/token

Note

The INI file must be added as external file in the PowerServer project painter, in order to be deployed with the application. For more details, refer to the next section "Modifying and re-deploying the PowerServer project".

Get the token

To modify the PowerBuilder application source code to get a token from the authentication server:

Step 1: Declare the following global variables.

//Token expiresin
Long gl_Expiresin
//Refresh token clockskew 
Long gl_ClockSkew = 3

Step 2: Define a global function and name it f_Authorization().

Select from menu File > New; in the New dialog, select the PB Object tab and then select Function and click OK to add a global function.

This global function uses the HTTP Post method to send the user credentials to the authorization server and then gets the identity token from the HTTP Authorization header.

Add scripts to the f_Authorization() function to implement the following scenario:

  • Scenario 1: Supports Client Credentials (GrantType="client_credentials") and gets the client ID and secret from the application.

  • Scenario 2: Supports Resource Owner Password (GrantType="password") and gets the username and password from a login window.

Scripts for scenario 1:

When the application starts, the application uses the client ID and secret stored in the application to get the token from Microsoft Entra ID, and when the token expires, it automatically refreshes the token.

Note

If the ClientSecret value contains the escape character "~", you will have to replace it with "~~", like the example below.

//Integer f_Authorization() for client_credentials
//Token URL is obtained from the INI file
OAuthClient    loac_Client
TokenRequest   ltr_Request
TokenResponse  ltr_Response
String  ls_url, ls_UserName, ls_UserPass
String  ls_TokenType, ls_AccessToken
String  ls_type, ls_description, ls_uri, ls_state
Integer  li_Return, li_rtn

li_rtn = -1
ls_url = profilestring("CloudSetting.ini","setup","TokenURL","")
//TokenRequest
ltr_Request.tokenlocation = ls_url
ltr_Request.Method = "POST"
ltr_Request.clientid = "49cddad2-721d-4fbc-bd64-1cfa2b183e00"
ltr_Request.clientsecret = "2ig8hfliVu.u1kl_79RbyZuh~~.X_b~~e~~3M"
ltr_Request.granttype = "client_credentials"
ltr_Request.Scope = "49cddad2-721d-4fbc-bd64-1cfa2b183e00/.default"

loac_Client = Create OAuthClient
li_Return = loac_Client.AccessToken( ltr_Request, ltr_Response )
If li_Return = 1 and ltr_Response.GetStatusCode () = 200 Then
 ls_TokenType = ltr_Response.gettokentype( )
 ls_AccessToken = ltr_Response.GetAccessToken()
 //Application Set Authorization Header
 Getapplication().SetHttpRequesTheader("Authorization", ls_TokenType + " " + ls_AccessToken, true)
 //Set Global Variables
 gl_Expiresin = ltr_Response.getexpiresin( )
 
 li_rtn = 1
Else
 li_Return = ltr_Response.GetTokenError(ls_type, ls_description, ls_uri, ls_state)
 MessageBox( "AccessToken Falied", "Return :" + String ( li_Return ) + "~r~n" + ls_description )
End If

If IsValid ( loac_Client ) Then DesTroy ( loac_Client )

Return li_rtn

Scripts for scenario 2:

When the application starts, the client ID and secret stored in the application as well as the username and password from the login window will be sent to Microsoft Entra ID to get the token, and when the token expires, the login window displays for the user to input the username and password again.

The following scripts hard code the username and password instead of getting them from the login window. You can change the scripts to use the login window after you implement the login window and pass the username and password to the f_Authorization() function.

Note

If the ClientSecret value contains the escape character "~", you will have to replace it with "~~", like the example below.

//Integer f_Authorization() for password
//Token URL is obtained from the INI file
//UserName & Password are hardcoded in the script
OAuthClient    loac_Client
TokenRequest   ltr_Request
TokenResponse  ltr_Response
String  ls_url, ls_UserName, ls_UserPass
String  ls_TokenType, ls_AccessToken
String  ls_type, ls_description, ls_uri, ls_state
Integer  li_Return, li_rtn

li_rtn = -1
ls_url = profilestring("CloudSetting.ini","setup","TokenURL","")

//TokenRequest
ltr_Request.tokenlocation = ls_url
ltr_Request.Method = "POST"
ltr_Request.clientid = "49cddad2-721d-4fbc-bd64-1cfa2b183e00"
ltr_Request.clientsecret = "2ig8hfliVu.u1kl_79RbyZuh~~.X_b~~e~~3M"
ltr_Request.scope = "49cddad2-721d-4fbc-bd64-1cfa2b183e00/.default"
ltr_Request.granttype = "password"

//login window can be implemented to pass username & password 
//Open(w_login) 
//Return:UserName & Password

ls_UserName = "appeon2@powerservertest.onmicrosoft.com"
ls_UserPass = "Test2008aaBB"

If IsNull ( ls_UserName ) Or Len ( ls_UserName ) = 0 Then
 MessageBox( "Tips", "UserName is empty!" )
 Return li_rtn
End If
If IsNull ( ls_UserPass ) Or Len ( ls_UserPass ) = 0 Then
 MessageBox( "Tips", "Password is empty!" )
 Return li_rtn
End If

ltr_Request.UserName = ls_UserName
ltr_Request.Password = ls_UserPass

loac_Client = Create OAuthClient
li_Return = loac_Client.AccessToken( ltr_Request, ltr_Response )
If li_Return = 1 and ltr_Response.GetStatusCode () = 200 Then
 ls_TokenType = ltr_Response.gettokentype( )
 ls_AccessToken = ltr_Response.GetAccessToken()
 //Application Set Authorization Header
 Getapplication().SetHttpRequesTheader("Authorization", ls_TokenType + " " +ls_AccessToken, true)
 //Set Global Variables
 gl_Expiresin = ltr_Response.getexpiresin( )
 
 li_rtn = 1
Else
 li_Return = ltr_Response.GetTokenError(ls_type, ls_description, ls_uri, ls_state)
 MessageBox( "AccessToken Falied", "Return :" + String ( li_Return ) + "~r~n" + ls_description )
End If

If IsValid ( loac_Client ) Then DesTroy ( loac_Client )

Return li_rtn

Step 3: Insert a timing object (timing_1) to the application and add the following scripts to the Timer event of timing_1.

1) Open the application object and then select from menu Insert > Object > Timing to add a timing object to the application.


2) Add the following scripts to the Timer event of timing_1.

//Authorization
f_Authorization()

When displayed in the source editor, the Timer event looks like this:

event timer;//Authorization
f_Authorization()
end event


Step 4: Add the following scripts to the SystemError event.

The scripts will trigger the SystemError event when the session or license encounters an error; and if the token is invalid or expires, the scripts will call the f_Authorization function to get the token again.

Choose Case error.Number
 Case 220  to 229 //Session Error
  MessageBox ("Session Error", "Number:" + String(error.Number) + "~r~nText:" + error.Text )
 Case 230  to 239 //License Error
  MessageBox ("License Error", "Number:" + String(error.Number) + "~r~nText:" + error.Text )
 Case 240  to 249 //Token Error
  MessageBox ("Token Error", "Number:" + String(error.Number) + "~r~nText:" + error.Text )
  //Authorization
  f_Authorization()
 Case Else
  MessageBox ("SystemError", "Number:" + String(error.Number) + "~r~nText:" + error.Text )
End Choose


Start the session manually

By default, the user session is automatically created when the application starts; and the session includes no token. For the session to include the token, the session must be started manually by code instead of automatically.

To start the session manually by code,

Step 1: Select the "Dynamic session parameters" option in the PowerBuilder IDE. (Steps: Open the application object painter, click Additional Properties in the application's Properties dialog; in the Application dialog, select the PowerServer tab and then select the Dynamic session parameters option and click Apply.)


Step 2: Call the BeginSession function in the application Open event.

Place the following scripts before the database connection is established. The scripts create the user session that includes the token information.

Note

1) The BeginSession function works only when the "Dynamic session parameters" option is selected.

2) Do not call BeginSession more than one time, otherwise, it will return -1 (indicating session already exists).

//Authorization
If f_Authorization() <> 1 Then
 Return
End If

//StartSession
long ll_return
Try
 ll_return = Beginsession()
 If ll_return <> 0 Then
  Messagebox("Beginsession Failed:" + String(ll_return), GetHttpResponseStatusText())
 End if
Catch (Throwable ex)
 MessageBox("Throwable", ex.GetMessage())
 Return
End Try

//Refresh Token for timing
If gl_Expiresin > 0 And (gl_Expiresin - gl_ClockSkew) > 0 Then
 //Timer = Expiresin - ClockSkew 
 //7200 - 3
 timing_1.Start(gl_Expiresin - gl_ClockSkew)
End If

// Connect db