Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

These rules will be added to an openapi.json file, which is created when a developer configures Open API via the Profound.js IDE. To configure Open API if you do not already have an openapi.json file, then see: OpenAPI Configuration

Note: it is not necessary to setup an OAuth2 app with a provider to get the Profound API server to simply validate user tokens. Further, it is not necessary to share the client_id or client_secret of an OAuth2 app with the Profound API server when only user token validation for API routes is desired.

Security Schemes in openapi.json for OAuth2

...

  • x-cacheTokenTTL – a number specifying how long in seconds to cache a user’s validated ID string before re-validating their username with the OAuth2 provider.

    • A value of 0 means that each API request will cause a request to be made to the OAuth2 provider to validate the user.

    • Default: 300 (5 minutes).

    • Set this to a reasonable value for your organization, where too low can slow down API responses and result in unnecessary network traffic, but too high can result in users remaining validated too long after their session or token has ended or expired.

Example

...

excerpts

These include working URLs for User-Validation APIs (as of August 25, 2023):

Microsoft Entra (Azure Active Directory)

Code Block
languagejson
      "OAuth2_MS_Entra_Identity": {
        "type": "oauth2",
        "description": "Microsoft Azure Active Directory (Entra)",
        "flows": {
          "authorizationCode": {
            "x-userinfoUrl": "https://graph.microsoft.com/v1.0/me",
            "x-userinfoField": "userPrincipalName",
            "x-cacheTokenTTL": 120
          }
        }
      },

Google

Security Rules

The “security” object inside of the openapi.json file determines which security schemes are used for requests, and whether one or all must be valid before a request is authorized. (See OpenAPI Configuration under “Multiple Security Schemes” for more information.)

Example Excerpt where requests matching EITHER “OAuth2_MS_Entra_Identity” OR “ApiKeyAuth” are permitted.

"security": [ {
Code Block
languagejson
Code Block
      "OAuth2_Google": {
        "type": "oauth2",
      "OAuth2_MS_Entra_Identity  "flows": []{
    },     {    "authorizationCode": {
  "ApiKeyAuth":  []     }

Complete Example openapi.json

Code Block
languagejson
{   "openapix-userinfoUrl": "3.0.3",
  "info": {https://www.googleapis.com/oauth2/v3/userinfo",
            "titlex-userinfoField": "APIsemail",
      "version": "1.0.0"   },
       "components": {}
    "securitySchemes": { },

GitHub

Code Block
      "ApiKeyAuthOAuth2_GitHub": {
        "type": "apiKeyoauth2",
        "inflows": "header",{
          "nameauthorizationCode": "X-API-Key"{
      },       "OAuth2_MS_Entra_Identity": {"x-userinfoUrl": "https://api.github.com/user",
            "typex-userinfoField": "oauth2login",
        "description": "Microsoft Azure}
Active Directory (Entra)",      }
  "flows": {
          "authorizationCode": {
            "x-userinfoUrl": "https://graph.microsoft.com/v1.0/me",
     },

Security Rules

The “security” object inside of the openapi.json file determines which security schemes are used for requests, and whether one or all must be valid before a request is authorized. (See OpenAPI Configuration under “Multiple Security Schemes” for more information.)

Example Excerpt where requests matching EITHER “OAuth2_MS_Entra_Identity” OR “ApiKeyAuth” are permitted.

Code Block
languagejson
  "security": [
    {
         "x-userinfoField"OAuth2_MS_Entra_Identity": "userPrincipalName"[]
    },
    {
}      "ApiKeyAuth":   }
 []
    }

Complete Example openapi.json

Code Block
languagejson
{
  "openapi":  }
  },"3.0.3",
  "info": {
    "x-allowAnonymoustitle": false"APIs",
    "securityversion": ["1.0.0"
  },
 { "components": {
    "OAuth2_MS_Entra_IdentitysecuritySchemes": []{
    },  "ApiKeyAuth": {
 {       "ApiKeyAuthtype": []"apiKey",
     }   ]
}

Conventions

By default, Profound API expects certain conventions with OAuth2:

  • OAuth2 Bearer tokens are sent in HTTP request headers to the Profound API route.

  • The OAuth2 identity provider has an user validation API that accepts GET requests and Bearer tokens in HTTP Authorization headers.

  • The identity provider’s user validation API returns JSON responses.

  • In the JSON response, the user-ID can be found as a property in the top-most object, not in any deeper objects; e.g.

    • Code Block
      {
        "user_id": "example-user-id"
        "other_things": {
          "user-id-is-not-in-this-object": true
        }
      }
  • Expired authentication tokens are not automatically renewed via refresh tokens

    • If a token is expired, then the user will not be verified, and the Profound API route will respond with not-authorized.

    • The API caller must track whether tokens are expired and whether to renew them and how.

Optional Custom OAuth2 Handling

Override or supplement the default, built-in user validation checking by defining custom code, in a function, profound.preOAuth2APIUserCheck :

  • Description:

    • called any time a request to a PAPI route is made when there exists an openapi.json > components > securitySchemes  object of type, “oauth2”.

    • Called via await, so the function may be defined using async

  • Parameter: one object with the following property names:

    • request - (input). HTTP Request Object.

    • response - (output). HTTP Response Object. Response headers or data can be written by manipulating this object.

    • secScheme - (input). Object with information read from openapi.json for an authorizationCode entry. Used to identify which security scheme definition is being processed.

  • Return value:

    • Expected return types: any.

    • To signal to Profound API that a user is valid, then a string with the user-ID should be returned. Profound API will then attempt to match that user-ID to an entry in the security store. The default, built-in OAuth2 user validation will not run.

    • To signal to Profound API that the user is not valid or could not be validated, then false should be returned. The default, built-in OAuth2 user validation will not run.

    • A returned value of true has no meaning but will not result in the request being authorized, and the default, built-in OAuth2 user validation will not run.

    • If any other types (such as undefined) are returned, then the default, built-in OAuth2 handling code will run.

  • Throws:

    • The function is expected to throw an Error object upon encountering any errors, including if the user information from the OAuth2 provider could not be read.

    • When the function throws an error, then an error message is sent to the standard error logs, and the user is not considered validated, so the API request will not be authorized via OAuth2.

Where to define that function, inside the start.js file inside of the startPJS function.

For example, if some OAuth2 provider were to require a custom HTTP header sent along with user-validation HTTP requests, then the header could be set, and then the default, built-in OAuth2 user validation code would still run:

Code Block
#!/usr/bin/env node
async function startPJS() {
  const profoundjs = require("./index.js");

  profound.preOAuth2APIUserCheck = async function({ request, response, secScheme }) {
    response.setHeader("x-custom-header", "Needed by some provider");
  };

  await profoundjs.applyConfig();
  await profoundjs.server.listen();
  const express = profoundjs.server.express;
  const app = profoundjs.server.app;

  app.use(express.json());
}
startPJS();

Sample Workspace for OAuth2 Sign-On

When configured, Profound API routes will authenticate users even when the processes of user sign-on happens on a different server; it is not necessary to implement a user sign-on process if the Profound API server is only responding to API calls and authenticating users via OAuth2.

However, the Profound.js server can handle both API requests with OAuth2 and the process of signing on and obtaining OAuth2 tokens for users, if desired. A sample workspace is available, providing an OAuth2 sign on screen, and a simple user-interface to call an API: oauth2sample

The oauth2sample workspace is meant to demonstrate OAuth2 with Profound API. It provides a sample sign-on screen, and screen for calling an API, and back-end code to glue those parts together.

If you do not already have a /modules/oauth2sample/ workspace in your Profound.js server installation, then you can install it with the following shell command, from within the installation directory of your Profound.js instance:

node complete_install --configure

The script will prompt for various settings. When asked to install sample code, answer yes, `y`, then answer `y` when prompted about oauth2sample. For example:

Code Block
$ cd /c/profoundjs
$ node complete_install --configure

Specify port number for Profound.js server (8081):
Install with Git integration (n)?
Install sample code (y)?
Install sample PAPI workspace, papisamples (y): n
Install sample PAPI workspace, oauth2sample (y):
Install sample Low-code plugin, mathoperation (y): n

config.js updated.
[...]
Copying pjssamples.
Copying sample workspace into C:\profoundjs\modules\oauth2sample
[...]
Profound.js installation complete.

TODO: everything below can go into the workspace readme.

2. Authorization Server

An authorization server authorizes a user or an API consumer to access some resource. Common providers of authorization servers are Google, Facebook, and Microsoft.

You may use Google, Amazon, Microsoft, Facebook, GitHub, Twitter, or any other authorization server. This guide covers implementing with GitHub, Google, and Twitter. You need only pick one.

Instructions for setting up either are the same except for in Steps 2 here and 3.1 (OpenAPI Configuration).

Instructions for setting up an Authorization Server (pick one):

...

GitHub

2.1. Log in at [ https://github.com/ ].

2.2. Go to [ Settings -> Developer Settings -> OAuth Apps -> New OAuth App ].

2.3. Input the following and click [ Register Application ].

Application Name: PAPI OAuth2 Application (or whatever)

...

Authorization callback URL: The URL of your Profound.js instance including the special path, /wsapi/oauth2/portal . e.g. https://myapp.example.com/wsapi/oauth2/portal

2.4. Click [ Generate a new client secret ] and authenticate on the security prompt.

2.5. Copy the Client ID and the Client Secret and store it somewhere temporarily.

...

Google

...

2.1. Log in to a Google account and go to [ https://console.cloud.google.com/apis/credentials ].

2.2. Click [ Create Project ] if there is none. Input the following data and click [ Create ].

Project Name: PAPI OAuth2 Project (or whatever)

Location: (whatever is default)

2.3. On the Left Panel, click [ OAuth consent screen ].

2.3.1. For User Type, choose [ External ] and click [ Create ].

2.3.2. For App Information, input the following data and click [ Save and Continue ].

App name: PAPI OAuth2 App (or whatever)

User support email: (whatever is default)

Developer contact information email addresses: (same as support)

2.3.3. For Scopes and Test Users, click [ Save and Continue ] to skip.

2.3.4. For Summary, click [ Back to Dashboard ].

2.4. On the Left Panel, click [ Credentials ].

2.4.1. Go to [ + Create Credentials -> OAuth client ID ]. Input the following data and click [ Create ].

Application type: Web application

Name: PAPI OAuth2 Client 1 (or whatever)

Authorized redirect URIs: The URL of your Profound.js instance including the special path, /wsapi/oauth2/portal . e.g. https://myapp.example.com/wsapi/oauth2/portal , or http://localhost:8081/wsapi/oauth2/portal , etc.

2.5. Copy the Client ID and the Client Secret and store it somewhere temporarily.

...

Twitter

...

2.1. Log in to a Twitter account and go to [ https://developer.twitter.com/portal/dashboard ].

2.2. Click [ + Create Project ] to use Twitter v2 endpoints. Input the following and click [ App settings ].

Project Name: PAPI OAuth2 Project (or whatever)

Use case: Build customized solutions in-house

Project description: Profound API OAuth 2.0 with Twitter as Authorization Server

App Name: PAPI OAuth2 App (or whatever)

2.3. Under "User authentication settings", click [ Set up ].

2.3.1. For Type of App (OAuth 2.0), choose [ Native App / Public client ].

2.3.2. For App Info, input the following data and click [ Save ].

Redirect URL: The URL of your Profound.js instance including a special path, /wsapi/oauth2/portal . e.g. https://myapp.example.com/wsapi/oauth2/portal , or http://localhost:8081/wsapi/oauth2/portal , etc.

Website URL: http://my-fully-qualified-domain-name/

2.4. Copy the Client ID and the Client Secret, store it somewhere temporarily, and click [ Done ].

3. Profound.js Configuration

Your Profound.js instance will provide resources for users or API consumers and will handle the authentication. Your Profound.js server should already be setup and running.

Navigate a web browser to your Profound.js instance's IDE page; e.g. http://your-machine-name:8081/ide

(In OAuth2 terminology, the Profound.js server is considered the Resource Server. https://www.oauth.com/oauth2-servers/the-resource-server/)

3.0 Store Client Credentials

In your Profound.js installation directory, you will create a file to store the GitHub, Google, or Twitter credentials that you created.

3.0.1. Go to the PJS working directory and create a client credentials file.

For example, in your Profound.js IDE, click New > Other > Text File.

...

Add a space, and save the file under your installation directory. For example:

...

3.0.2. Edit the client credentials file to contain JSON data, like below, substituting the credentials obtained form GitHub, Google, or Twitter as the property values.

...

3.0.3. Encrypt the client credentials file with the following command, in an interactive shell, such as the IDE's Terminal , found by clicking a link at the bottom of the IDE.

Run this command:

Code Block
languagebash
node encrypt_client_file client_creds.txt

For example:

...

The encrypt program changed the contents of client_creds.txt and should output something like this on success:

...

Open or re-open the client_creds.txt file in the IDe, and you should see the same base64 string that was output in the terminal. That text will be used in the OpenAPI configuration file, discussed in one of the configuration types in section 3.1 "OpenAPI Configuration".

3.1. OpenAPI Configuration

Depending on the Authorization Server you chose, follow instructions on one of these pages, then return to 3.2 on this page.

The configuration in these pages assume that your Open API configuration has no existing values.

If your Open API configuration already has entries, then see this page on how to combine them: PAPI OAuth2 OpenAPI Configuration.

3.2. User Management

3.2.1. From your Profound.js instance's IDE:

3.2.2. Go to [ Home Ribbon -> API Options -> User Authentication -> API Users -> Add New User ].

3.2.3. Input the user or login name of an end-user authorized to use PAPI OAuth2 and choose [ New -> oauth2 -> OK ].

Note: the username added under this dialog must match the user's GitHub, Google, Twitter, etc. username.

3.3. Create a Sample API

3.3.1. From your Profound.js instance's IDE:

3.3.2. Either use an existing workspace, or a create a new one:

Go to [ Home Ribbon -> New -> Workspace ] and input the following.

Workspace: myworkspacename

Description: My workspace for web services

3.3.3.

Either upload the attached file, mywebservices.api.json, to the workspace:

 

View file
namemywebservices.api.json

And skip to "4. Client Application"

Or follow below:

Go to [ Home Ribbon -> New -> API File ].

On the Right-side Panel, under the [ General Info ] tab, input the following data to set up a PAPI endpoint.

Name: postTestService

Summary: POST test service

HTTP Method: post

HTTP Path: /postTestService

3.3.4. Go to [ API Logic -> Switch routine to Node.js code ] and input the following code.

Including a time element and random numbers ensures that observations are happening in real-time and not cached.

API Node.js Code
Code Block
languagejs
// Set API output
output["message"] = "Congratulations! You made it to your PAPI endpoint!";
output["method"] = "post";
output["date_utc"] = (new Date()).toJSON();
output["random"] = Math.random();

3.3.5. Go to [ Home Ribbon -> Save As ] and input [ File Name -> mywebservices.api.json ].

4. Client Application

4.1. Web Browser

4.1.1 Run the application that you just created by navigating to a special URL in your instance, ending in /wsapi/oauth2/portal . For example,  http://my-machine-name:8081/wsapi/oauth2/portal

Click on the "Authorize" link, and you should see a page with two text-boxes if the OAuth2 Authorization server and the Profound.js server are setup correctly.

...

4.1.2 Copy the token(s) obtained using the authorized end-user account. You will use the token or tokens in the next section.

4.2.Test an HTTP POST on the API Endpoint

You can use different tools to send the HTTP POST.

  • Postman is simple to use and free but requires signing up.

  • Curl is a free, common command-line tool available in many Unix-like shell environments, but it is for users comfortable with a command-line environment.

Postman

4.2.1. Download and install the app from [ https://www.postman.com/ ].

4.2.2. Input the following under the Headers section.

Access Token:

(key) Authorization

(value) Bearer access-token-from-portal; e.g. "Bearer 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"

Refresh Token (if supplied by the OAuth2 authorization server):

(key) x-refresh-token

(value) refresh-token-from-portal

4.2.3. Go to the request URL bar of Postman and do the following.

Choose [ POST ] on the dropdown box.

Input [ http://my-machine-name:my-port-num/run/myworkspacename/wsapi/myapiURL].

Click [ Send ].

If you see a response like the following:

Code Block
{"message":"Congratulations! You made it to your PAPI endpoint!","method":"POST","date_utc":"2023-04-19T21:11:51.543Z","random":0.6741568741882311}

then, your API is setup and working with OAuth2.

Curl

From Bash or another shell, supply the token(s) from step 4.1 as headers, and specify the API endpoint's run URL. For example if the portal only provided Access Token:

Code Block
# Use the form like below:
#curl -s -X POST -d "" http://__my-machine-name__:__my-port-num__/run/__myworkspacename__/wsapi/__myApiEndPoint__ -H "Authorization: Bearer __some_token__"
# For example:
curl -s -X POST -d "" http://localhost:8081/run/myworkspace/wsapi/postTestService -H "Authorization: Bearer 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"

Note: __myApiEndPoint__ should be replaced with the "HTTP Path" setup in the api.json file setup in section 3. For example, if the path is "/postTestService", then use postTestService instead of __myApiEndPoint__

Or if the portal provided both access and refresh tokens (and showing an alternate example URL):

Code Block
curl -s -X POST -d "" -H "Authorization: Bearer ya29.a0Ael9sCNjZXr-wb2CFqFyzwBdUOomEwj796rdj55ABJwqIDFNSM1LfAKgM4fgOwnBeVc_MNme8C056YE0xzp2EWHQyoFQUz5tyOwwBTiBv1SZy10Fib6PrSA2hZqlVcDf1iCOdu-veec4Kp7ukfUAvFHC_M6raCgYKATgSARESFQF4udJhj4pCpay9VmpGSXuDT3SI7g0163" -H "x-refresh-token: 1//01QPY_m6vMRgNCgYIARAAGAESNwF-L9IrMoJ6l5HoU_SRg_8Hot-1oENrySPh_1zPbyNhwyYeaOfNrKWI2V2ZqwFai6_gYLu3_w0"  http://localhost:8081/run/myworkspacename/wsapi/postTestService

Where, the value in the "Authorization:" header is the token from step 4.1, and the schema, hostname, port, workspace name, and API endpoint match what is setup for your API.

You should see JSON response text, as you defined in the API route.

Additional Information

Pros and Cons of three different Authorization Servers

  • GitHub

    • Pros: Very easy to set up.

    • Cons: No refresh token, no refresh URL, no revoke URL.

  • Google

    • Pros: Adheres closesly to the OAuth 2.0 standards.

    • Cons: Slightly more complicated to set up than GitHub

  • Twitter

    • Pros: Adheres to OAuth 2.0 standards. Uses RFC 7636 Proof Key for Code Exchange (PKCE).

    • Cons: App creation limited only to three per day. PKCE S256 occasionally seems to break.

...

"in": "header",
        "name": "X-API-Key"
      },
      "OAuth2_MS_Entra_Identity": {
        "type": "oauth2",
        "description": "Microsoft Azure Active Directory (Entra)",
        "flows": {
          "authorizationCode": {
            "x-userinfoUrl": "https://graph.microsoft.com/v1.0/me",
            "x-userinfoField": "userPrincipalName"
          }
        }
      }
    }
  },
  "x-allowAnonymous": false,
  "security": [
    {
      "OAuth2_MS_Entra_Identity": []
    },
    {
      "ApiKeyAuth": []
    }
  ]
}

Conventions

By default, Profound API expects certain conventions with OAuth2:

  • OAuth2 Bearer tokens are sent in HTTP request headers to the Profound API route.

  • The OAuth2 identity provider has an user validation API that accepts GET requests and Bearer tokens in HTTP Authorization headers.

  • The identity provider’s user validation API returns JSON responses.

  • In the JSON response, the user-ID can be found as a property in the top-most object, not in any deeper objects; e.g.

    • Code Block
      {
        "user_id": "example-user-id"
        "other_things": {
          "user-id-is-not-in-this-object": true
        }
      }
  • Expired authentication tokens are not automatically renewed via refresh tokens

    • If a token is expired, then the user will not be verified, and the Profound API route will respond with not-authorized.

    • The API caller must track whether tokens are expired and whether to renew them and how.

Optional Custom OAuth2 Handling

Override or supplement the default, built-in user validation checking by defining custom code, in a function, profound.preOAuth2APIUserCheck :

  • Description:

    • called any time a request to a PAPI route is made when there exists an openapi.json > components > securitySchemes  object of type, “oauth2”.

    • Called via await, so the function may be defined using async

  • Parameter: one object with the following property names:

    • request - (input). HTTP Request Object.

    • response - (output). HTTP Response Object. Response headers or data can be written by manipulating this object.

    • secScheme - (input). Object with information read from openapi.json for an authorizationCode entry. Used to identify which security scheme definition is being processed.

  • Return value:

    • Expected return types: any.

    • To signal to Profound API that a user is valid, then a string with the user-ID should be returned. Profound API will then attempt to match that user-ID to an entry in the security store. The default, built-in OAuth2 user validation will not run.

    • To signal to Profound API that the user is not valid or could not be validated, then false should be returned. The default, built-in OAuth2 user validation will not run.

    • A returned value of true has no meaning but will not result in the request being authorized, and the default, built-in OAuth2 user validation will not run.

    • If any other types (such as undefined) are returned, then the default, built-in OAuth2 handling code will run.

  • Throws:

    • The function is expected to throw an Error object upon encountering any errors, including if the user information from the OAuth2 provider could not be read.

    • When the function throws an error, then an error message is sent to the standard error logs, and the user is not considered validated, so the API request will not be authorized via OAuth2.

Where to define that function, inside the start.js file inside of the startPJS function.

For example, if some OAuth2 provider were to require a custom HTTP header sent along with user-validation HTTP requests, then the header could be set, and then the default, built-in OAuth2 user validation code would still run:

Code Block
#!/usr/bin/env node
async function startPJS() {
  const profoundjs = require("./index.js");

  profound.preOAuth2APIUserCheck = async function({ request, response, secScheme }) {
    response.setHeader("x-custom-header", "Needed by some provider");
  };

  await profoundjs.applyConfig();
  await profoundjs.server.listen();
  const express = profoundjs.server.express;
  const app = profoundjs.server.app;

  app.use(express.json());
}
startPJS();

Sample Workspace for OAuth2 Sign-On

When configured, Profound API routes will authenticate users even when the processes of user sign-on happens on a different server; it is not necessary to implement a user sign-on process if the Profound API server is only responding to API calls and authenticating users via OAuth2.

However, the Profound.js server can handle both API requests with OAuth2 and the process of signing on and obtaining OAuth2 tokens for users, if desired. A sample workspace is available, providing an OAuth2 sign on screen, and a simple user-interface to call an API: oauth2sample

The oauth2sample workspace is meant to demonstrate OAuth2 with Profound API. It provides a sample sign-on screen, and screen for calling an API, and back-end code to glue those parts together.

If you do not already have a /modules/oauth2sample/ workspace in your Profound.js server installation, then you can install it with the following shell command, from within the installation directory of your Profound.js instance:

node complete_install --configure

The script will prompt for various settings. When asked to install sample code, answer yes, `y`, then answer `y` when prompted about oauth2sample. For example:

Code Block
$ cd /c/profoundjs
$ node complete_install --configure

Specify port number for Profound.js server (8081):
Install with Git integration (n)?
Install sample code (y)?
Install sample PAPI workspace, papisamples (y): n
Install sample PAPI workspace, oauth2sample (y):
Install sample Low-code plugin, mathoperation (y): n

config.js updated.
[...]
Copying pjssamples.
Copying sample workspace into C:\profoundjs\modules\oauth2sample
[...]
Profound.js installation complete.