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.