...
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 | ||
---|---|---|
| ||
"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
}
}
}, |
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 | ||
---|---|---|
| ||
Code Block | ||
"OAuth2_Google": { "type": "oauth2", "OAuth2_MS_Entra_Identity "flows": []{ }, { "authorizationCode": { "ApiKeyAuth": [] } |
Complete Example openapi.json
Code Block | ||
---|---|---|
| ||
{ "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 | ||
---|---|---|
| ||
"security": [ { "x-userinfoField"OAuth2_MS_Entra_Identity": "userPrincipalName"[] }, { } "ApiKeyAuth": } [] } |
Complete Example openapi.json
Code Block | ||
---|---|---|
| ||
{ "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.
...
...
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.
...
...
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
|
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 | ||
---|---|---|
| ||
// 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. |