OAuth2 Authentication

OAuth2 Authentication

OAuth2 for Profound API is available beginning in version 7.0.0 of Profound.js.

Overview

OAuth2 is an authentication method allowing users to access a website without setting up credentials with that website. In lieu of credentials, trust between the website and the user is established via some third-party identity provider, such as Microsoft, Okta, Google, Amazon, Facebook, GitHub, etc. Trust is established in the form of token exchanges, which happen transparently to the user.

That trust can also be expanded to the Profound API server via OAuth2. A Profound API server can be set up to authenticate a user’s OAuth2 token, and only respond to API requests when a valid token is present. This capability allows services already using OAuth2 to take advantage of Profound API without needing to invent another trust mechanism between that service and the Profound API server.

Why Use OAuth2?

OAuth2 can improve the user experience by allowing users to access participating websites by simply clicking a few sign-on buttons rather than needing to create new accounts or remember various credentials. Security is improved as well, because authentication goes through providers using best practices; whereas, someone operating a small website might not have the resources to handle security appropriately.

Setup

To set up a Profound API server to allow OAuth2 as an authentication method, an OpenAPI configuration must be set up for the server, including:

  • security schemes - The distinct resources needed for handling OAuth2 with various providers must be defined.

  • security rules - The security object defines which security schemes are permitted or required for authorizing API requests.

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

It is not necessary to set up 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

Enter OAuth2 security schemes inside the components.securitySchemes object. There can be multiple OAuth2 schemes defined, one for each OAuth2 provider with whom the Profound API server will communicate.

An OAuth2 security scheme object should have a type property with the value "oauth2". For Profound API to validate a user, there should be two fields inside of a flows object in the security scheme object:

  • x-userinfoUrl - A URL pointing to some server that accepts an HTTP GET request and returns a JSON object.

  • x-userinfoField - A string that names a property in the returned JSON object whose value contains the user ID expected in the Profound API security store; e.g. "email", "userPrincipalName", "login", etc.

Optional field:

  • 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. 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

The examples below show the minimum properties needed for validating Profound API requests via OAuth2. If you need to set up Profound.js as an authentication server to let users sign on, see the Sample Workspace for OAuth2 Sign-On section below.

Microsoft Entra (Azure Active Directory)

"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

"OAuth2_Google": { "type": "oauth2", "flows": { "authorizationCode": { "x-userinfoUrl": "https://www.googleapis.com/oauth2/v3/userinfo", "x-userinfoField": "email" } } },

GitHub

"OAuth2_GitHub": { "type": "oauth2", "flows": { "authorizationCode": { "x-userinfoUrl": "https://api.github.com/user", "x-userinfoField": "login" } } },

The URLs above were valid for user-validation APIs as of August 30, 2023. Confirm the current endpoint with your identity provider before use.

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).

Either scheme (OR)

Example excerpt where requests matching either "OAuth2_MS_Entra_Identity" or "ApiKeyAuth" are permitted:

"security": [ { "OAuth2_MS_Entra_Identity": [] }, { "ApiKeyAuth": [] } ]

Both schemes (AND)

Example excerpt where requests must match both "OAuth2_MS_Entra_Identity" and "ApiKeyAuth", or else the API returns a Not Validated response:

"security": [ { "OAuth2_MS_Entra_Identity": [], "ApiKeyAuth": [] } ]

Complete Example openapi.json

{ "openapi": "3.0.3", "info": { "title": "APIs", "version": "1.0.0" }, "components": { "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "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": [] } ] }

The x-allowAnonymous property must be false in order for API security to function properly.

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 a 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.

For example:

{ "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 Profound API 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, 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, 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, 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.

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.

#!/usr/bin/env node async function startPJS() { const profoundjs = require("./index.js"); // Runs before the built-in OAuth2 user check for every PAPI request. // Returning undefined lets the default validation continue afterwards. 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 process 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.

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

node complete_install --installSamples

If you do not already have a config.js file, the script will prompt for various settings. When asked to install sample code, answer y for yes. For example, your terminal screen may look like the following:

$ cd /c/profoundjs $ node complete_install --installSamples Specify port number for Profound.js server (8081): Install with Git integration (y)? n Install sample code (y)? y config.js updated. [...] Copying pjssamples. Copying sample workspace into C:\profoundjs\modules\oauth2sample [...] Profound.js installation complete.

Once you have the oauth2sample workspace installed, you can follow the instructions in the README.md file to learn what changes are necessary to the openapi.json configuration and the sample setup.js.