Before making changes to the PowerBuilder client app, let's follow the steps below to make sure 1) the PowerBuilder application can run successfully, 2) the app has been deployed as an installable cloud app successfully, and 3) the PowerServer C# solution has been successfully generated.
In this tutorial, we will take Sales Demo as an example.
Step 1: Select Windows Start | Appeon PowerBuilder 2022, and then right-click Example Sales App and select More | Run as administrator.
Step 2: When the SalesDemo workspace is loaded in the PowerBuilder IDE, click the Run button in the PowerBuilder toolbar.
Step 3: When the application main window is opened, click the Address icon in the application ribbon bar and make sure data can be successfully retrieved.
Step 4: Create and configure a PowerServer project for the Sales Demo app (detailed instructions are provided in Quick Start > Guide 1).
IMPORTANT: In the .NET Server page > Advanced tab, select Use external auth service from the Auth Template list box.
Step 5: Deploy the application as an installable cloud app. The PowerServer C# solution is generated, but the installable cloud app cannot run yet because further settings and changes are required, as explained in the subsequent sections.
The PowerServer C# solution provides templates for configuring the address of the identity provider like Okta OIDC.
-
Authentication.json contains the settings for enabling the authentication feature ("PowerServer:EnableAuthentication") and specifying the address of the authentication server ("Authentication:Authority"). The PowerServer Web APIs will validate the token against the authentication server; and if validation is successful, data will be obtained from the database.
The "PowerServer:EnableAuthentication" setting is set to true by default. Setting it to false will turn off the authentication feature.
The "Authentication:Authority" setting is set for JWT by default; you can set the Issuer URI of authorization server.
The following outlines the key steps for creating a web application to integrate with Okta and getting the access token.
For complete and detailed instructions, please refer to the guides.
Guide 1: https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/
Guide 2: https://developer.okta.com/docs/guides/validate-access-tokens/dotnet/overview/
Step 1: Create App Integration.
-
In Okta menu, select the Applications drop-down list > Applications page, then select Create App Integration button to create a new app integration.
-
Select OIDC-OpenID Connect radio button under Sign-in method, and select Web Application radio button under Application Type, then click Next.
-
Write down the App integration name which is appeontest, select the check box of Client Credentials, and write down the Sign-in redirect URIs which is http://localhost:5099/authorization-code/callback.
-
Select Allow everyone in your organization to access radio button under Controlled access (you can select the button according to your actual needs), and then click Save.
Step 2: Write down the following information.
-
Client ID: 0oa2gio6k8k1kJDGQ5d7
-
Client secret: Z6qh64ih-SGliIN-6U0r0Ycyd4MYayM_WDdYaJAW
-
Okta domain: dev-02923419.okta.com
-
Sign-in redirect URIs: http://localhost:5099/authorization-code/callback
Step 3: Add Person.
In Okta menu, select the Directory drop-down list > People page, then select Add Person to write down the basic information and click Save.
Step 4: Get the code.
-
The template of the code URI is as follows. You can fill in your information to get the code URI.
https://${yourOktaDomain}/oauth2/default/v1/authorize?client_id=0oabucvy c38HLL1ef0h7&response_type=code&scope=openid&redirect_uri=https%3A%2F% 2Fexample.com&state=state-296bc9a0-a2a2-4a57-be1a-d0e2fd9bb601
Note
The state consists of random numbers.
Get the code URI:
https://dev-02923419.okta.com/oauth2/default/v1/authorize?client_id=0oa2gio6k8k1kJDGQ5d7
&response_type=code&scope=openid&redirect_uri=http%3A%2F%2Flocalhost:5099/authorization-
code/callback&state=state-296bc9a
-
Access the code URI through a browser, sign in with your Okta username and password, and return the code after successful verification.
For example, if jump to the URI "http://localhost:5099/authorization-code/callback?code=o1IvY6bgkJ_WNHxJDdfCEOJVgOeTckt7G_xa4hdW8_o&state=state-296bc9a", the code will be "o1IvY6bgkJ_WNHxJDdfCEOJVgOeTckt7G_xa4hdW8_o".
Step 5: Get the token by Postman.
-
In Okta menu, select the Security drop-down list > API page to get the Issuer URI which is https://dev-02923419.okta.com/oauth2/default, and then click default for more information.
-
Check the Metadata URI in Settings page.
-
Access the URI by Postman or web browsers to get the token endpoint: https://dev-02923419.okta.com/oauth2/default/v1/token.
-
Get the access token by Oauth2.
-
Check the token by the JWT website (https://jwt.io).
In this section, we will modify the PowerBuilder application source code and the PowerServer project settings to achieve the following results:
-
Gets the authorization code from the application login window, then authenticates it with Okta and gets a token.
-
Uses the token to access data from the PowerServer Web API.
-
Refreshes the token when necessary.
Step 1: Declare the following global variables.
//Token expiresin Long gl_Expiresin //Refresh token clockskew Long gl_ClockSkew = 3
Step 2: Add the following scripts to Okta login window.
When the application starts, the application displays the login window. You can enter the username and password to sign in.
forward global type w_login from window end type type wb_1 from webbrowser within w_login end type end forward global type w_login from window integer width = 3643 integer height = 2332 boolean titlebar = true string title = "Okta login" boolean controlmenu = true windowtype windowtype = response! long backcolor = 67108864 string icon = "AppIcon!" boolean center = true wb_1 wb_1 end type global w_login w_login type variables string is_url end variables on w_login.create this.wb_1=create wb_1 this.Control[]={this.wb_1} end on on w_login.destroy destroy(this.wb_1) end on event open; is_url = message.StringParm wb_1.Navigate(is_url) end event type wb_1 from webbrowser within w_login integer x = 14 integer y = 28 integer width = 3598 integer height = 2192 end type event resourceredirect;String ls_locationUrl ls_locationUrl = RedirectUrl If Pos (ls_locationUrl, "code=" ) > 0 Then CloseWithReturn(Parent, ls_locationUrl) end If end event
Step 3: 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 Authorization Code (GrantType="authorization_code") and gets the authorization code from a login window.
Scripts for scenario 1:
When the application starts, the client ID and secret stored in the application as well as the authorization code from the login window will be sent to Okta to get the token, and when the token expires, the login window displays for the user to input the username and password again.
//Integer f_Authorization() for authorization_code //Code from login window OAuthClient loac_Client TokenRequest ltr_Request TokenResponse ltr_Response String ls_URL, ls_URL_Code, ls_code String ls_TokenType, ls_AccessToken String ls_type, ls_description, ls_uri, ls_state String ls_ClientID, ls_ClientSecret, ls_Scope, ls_RedirectUri Integer li_Return, li_rtn Long ll_Rand li_rtn = -1 ls_url = profilestring("CloudSetting.ini","setup","TokenURL","") //okta login window ll_Rand = Rand(32767) ls_ClientID = "0oa2gio6k8k1kJDGQ5d7" ls_ClientSecret = "Z6qh64ih-SGliIN-6U0r0Ycyd4MYayM_WDdYaJAW" ls_Scope = "openid" ls_RedirectUri = "http://localhost:5099/authorization-code/callback" ls_Url_code = "https://dev-02923419.okta.com/oauth2/default/v1/authorize?client_id=" + ls_ClientID +& "&redirect_uri=" + ls_RedirectUri + & "&scope=" + ls_Scope + & "&response_type=code&state=state-" + String(ll_Rand) OpenWithParm ( w_login,ls_Url_code ) ls_code = Message.Stringparm If Len (ls_code) < 1 Then Return li_rtn If Pos(ls_code, "code=") < 0 Then return li_rtn ls_code = Mid (ls_code, pos(ls_code,"code=") + 5) If Pos(ls_code, "&state") < 0 Then return li_rtn ls_code = Mid (ls_code, 1,pos(ls_code,"&state") - 1) //TokenRequest ltr_Request.Method = "POST" ltr_Request.tokenlocation = ls_url ltr_Request.granttype = "authorization_code" ltr_Request.clientid = ls_ClientID ltr_Request.clientsecret = ls_ClientSecret ltr_Request.ClearParams() ltr_Request.AppendParam("grant_type","authorization_code") ltr_Request.AppendParam("client_id", ls_ClientID) ltr_Request.AppendParam("client_secret", ls_ClientSecret) ltr_Request.AppendParam("scope", ls_Scope) ltr_Request.AppendParam("code", ls_code) ltr_Request.AppendParam("redirect_uri", ls_RedirectUri) ltr_Request.ClearHeaders() ltr_Request.SetHeader("Content-Type","application/x-www-form-urlencoded") 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 4: 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 5: Add the following scripts to the application Open event.
Place the scripts before the database connection is established. The scripts get the token from Okta and then start the user session (using the BeginSession function) to include the token information in the session.
If IsPowerServerApp() Then //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 End If // Connect db
Step 6: 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
Create an INI file in the same location as the PBT file and name it CloudSetting.ini.
The INI file specifies the URL for requesting the token from Okta.
[Setup] TokenURL= https://dev-02923419.okta.com/oauth2/default/v1/token
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: Enable "Begin session by code" 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 Begin session by code option, and click Apply.)
After this option is enabled, when the BeginSession function in the application Open event is called, it will create a session that includes the token information (See scripts in step 5 in "Add scripts").
Step 1: Add the INI file CloudSetting.ini to the Files preloaded in uncompressed format section under the Client App page.
Step 2: Select RESTClient Support under the Runtime files group in the Client App page > Advanced tab.
Step 3: Double check the URL of the PowerServer Web APIs in the .NET Server page.
Make sure the port number is not occupied by any other program. You can execute the command "netstat -ano | findstr portnumber" to check if the port number is occupied by any other program. For details, refer to Choosing an appropriate port number.
Step 4: Double check that Use external auth service is selected from the Auth Template list box in the .NET Server page > Advanced tab.
Step 5: Save the changes and deploy the PowerServer project (using the "Build & Deploy PowerServer Project" option) so that the above settings can take effect in the installable cloud app.
The Issuer URI of authorization server must be provided so that the PowerServer Web APIs can use it to validate the token passed from the client. And if validation is successful, it can get data from the database.
Get the Issuer URI from API setting of Okta: https://dev-02923419.okta.com/oauth2/default.
You can also modify the "Authentication:Authority" which is https://${yourOktaDomain}/oauth2/default. Get yourOktaDomain from General setting of Okta: dev-02923419.okta.com.
Open the Authentication.json file, uncomment the server address for standard JWT, and then modify the authentication template.
// Standard JWT Token authentication server address "Authentication:Authority": "https://dev-02923419.okta.com/oauth2/default"