Implement OAuth in an Installed Application
An Installed Application is a user-facing application that is installed by the user onto their device. Typical examples are Windows, macOS, iOS app and Android apps. A single-page app (SPA) running entirely in a web browser is another example.
The difference between an Installed Application and a Web Server Application is that code runs on the user's device, not on a server. This is an important difference from a security perspective, because the code cannot maintain the confidentiality of the client credentials used for OAuth.
An Installed Application is a public client that uses the Authorization Code grant type with Proof Key for Code Exchange (PKCE) to request an access token, as described below.
PKCE is required in this case to enhance the security of the flow, since a public client cannot keep its client credentials confidential. It involves the client generating a random secret (called a code verifier) and creating a derived value (called a code challenge) from it. The code challenge is sent to the Lobby during the initial authorization request, while the code verifier is kept secret. When the Authorization Code is returned, the client provides the code verifier to prove its identity and exchanges the Authorization Code for an access token. For more details, see RFC 7636.
Step 1: Obtain Client Credentials
Register your Installed Applications by following the instructions in Register an OAuth Integration.
Redirect URI Recommendations
RFC 8252 states that Installed Applications should use one of the following types of redirect URI:
- A Private-use URI scheme redirect uses a custom URI scheme that has been registered on the device for your application, taking the form:
scheme:/path
Here's an example:
com.example.app:/oauth2/callback
- A Loopback redirect URI uses the "http" scheme with the loopback IP literal and whatever port the application is listening on, taking the form:
http://127.0.0.1:{port}/{path}
Here's an example:
http://127.0.0.1:54001/oauth2/callback
If your Installed Application loops through a number of ports to find an available port for binding the callback, you may register a single loopback redirect URI with a wildcard (*) port identifier, rather than registering multiple redirect URIs.
Your Installed Application will be registered as a public client and you will receive client credentials in the form of a Client ID only.
The Client ID must be kept confidential and must not be shared with anyone outside of your organization.
Step 2: Generate Code Verifier and Code Challenge
Generate a random code verifier, which is a high-entropy cryptographic random string of between 43-128 characters.
Create a code challenge by applying a cryptographic hash function like SHA-256 to the code verifier. Encode the result as URL-safe Base64.
For testing purposes, you can use the helper utility at https://example-app.com/pkce to generate the required values.
Here is an example using JavaScript:
const crypto = require('crypto'); // Generate a random code verifier const codeVerifier = base64URLEncode(crypto.randomBytes(48)); // Create a code challenge using SHA-256 const codeChallenge = base64URLEncode(crypto.createHash('sha256').update(codeVerifier).digest()); function base64URLEncode(buffer) { return buffer.toString('base64') .replace(/=/g, '') .replace(/\+/g, '-') .replace(/\//g, '_'); }
Step 3: Redirect the User to the Authorization Endpoint
When a user of your Installed Application wants to access their data, redirect them via the native web browser (not an embedded browser) to the authorization endpoint of the Oracle Construction and Engineering Lobby as follows:
https://<LOBBY>/auth/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&code_challenge=<CODE_CHALLENGE>&code_challenge_method=<CODE_CHALLENGE_METHOD>
Here is an example:
https://constructionandengineering.oraclecloud.com/auth/authorize?response_type=code&client_id=MyClientId&redirect_uri=https://myapp.com/oauth/callback&code_challenge=mS8J4mBfBb-6Qh8VdcP4XyDG0g0-URpKVTzIltVt5-2k&code_challenge_method=S256
Step 4: User Authentication and Consent
The Lobby responds to this request by prompting the user to sign in with their Oracle Primavera Cloud account credentials, if they are not already logged in.
The user authorizes your application to access their Primavera Cloud data by the act of authenticating their account.
Step 5: Receive the Authorization Code
The Lobby returns an Authorization Code to the redirect URI specified for your application, as follows:
<REDIRECT_URI>?code=<AUTHORIZATION_CODE>
Here is an example:
https://myapp.com/oauth/callback?code=TXlBdXRob3JpemF0aW9uQ29kZQ
Step 6: Exchange the Authorization Code for an Access Token
Your application exchanges the Authorization Code for an Access Token and Refresh Token by making a POST request to the token endpoint of the Lobby specifying the client ID in the request body, as follows:
POST https://<LOBBY>/auth/token Headers: Content-Type: application/x-www-form-urlencoded Body: client_id=<CLIENT_ID> grant_type=authorization_code code=<AUTHORIZATION_CODE> code_verifier=<CODE_VERIFIER> redirect_uri=<REDIRECT_URI>
Here is an example curl request:
curl --request POST 'https://constructionandengineering.oraclecloud.com/auth/token' \ --header 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8' \ --data 'client_id=MyClientId' \ --data 'grant_type=authorization_code' \ --data 'code=TXlBdXRob3JpemF0aW9uQ29kZQ' \ --data 'code_verifier=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM' \ --data 'redirect_uri=https://myapp.com/oauth/callback'
The response is in JSON format and contains the access token, expiration time of the access token and refresh token.
Here is an example of a response:
{ "access-token": "eyJ4NXQjUzI1NiI...KtK5elB38rcAbgFtVP9A", "token-type": "Bearer", "expires-in": 7200, "refresh_token": "TXlSZWZyZXNoVG9rZW4=" }
Step 7: Use the Access Token
Use the Access Token to make authorized requests to APIs on behalf of the authenticated user by including it in the Authorization header of your HTTP requests, as follows:
Authorization: Bearer <ACCESS_TOKEN>
Here is an example curl request:
curl 'https://primavera-eu1.oraclecloud.com/api/restapi/project/{projectId}' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer eyJ4NXQjUzI1NiI...KtK5elB38rcAbgFtVP9A'
Step 8: Refresh the Access Token
When the Access Token expires, request a new access token without requiring the user to authenticate by making a POST request to the token endpoint of the Lobby, as follows:
POST https://<LOBBY>/auth/token Headers: Content-Type: application/x-www-form-urlencoded Authorization: Basic <BASE_64_ENCODED_CLIENT_CREDENTIALS> Body: grant_type=refresh_token refresh_token=<REFRESH_TOKEN>
Here is an example curl request:
curl --request POST 'https://constructionandengineering.oraclecloud.com/auth/token' \ --header 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8' \ --header 'Authorization: Basic TXlDbGllbnRJRDpNeUNsaWVudFNlY3JldA==' \ --data 'grant_type=refresh_token' \ --data 'refresh_token=TXlSZWZyZXNoVG9rZW4='
The response is the same as the original token response, including a new access token, access token expiry and new refresh token.
(Optional) State Parameter
The state parameter is an optional security measure to prevent Cross-Site Request Forgery (CSRF) attacks. It's a random value generated by the client and used to verify the integrity of the authorization process. The Lobby includes this value when redirecting back to the client with the Authorization Code.
If you choose to make use of the state parameter, follow the steps described below.
Generate a random, unique value in your client application.
Include this value in the authorization request sent to the Lobby as follows:
https://<LOBBY>/auth/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&state=<STATE>
Here is an example:
https://constructionandengineering.oraclecloud.com/auth/authorize?response_type=code&client_id=MyClientId&redirect_uri=https://myapp.com/oauth/callback&state=TXlTdGF0ZQ
When your application receives the OAuth response, check that the state parameter in the response matches the previously sent. It will be returned as follows:
<REDIRECT_URI>?code=<AUTHORIZATION_CODE>&state=<STATE>
Here is an example:
https://myapp.com/oauth/callback?code=TXlBdXRob3JpemF0aW9uQ29kZQ&state=TXlTdGF0ZQ
If they match, proceed with the authorization process. If they don't, consider it a potential security breach, reject the response, invalidate the state parameter and generate a new one for the next authorization request.