OAuth2 Authentication

Intro

OAuth2 is a 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 are 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 be also expanded to the Profound API server via OAuth2. A Profound API server can be setup 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.

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


Why Use OAuth2?

OAuth2 can improve the user experience by allowing them 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 setup a Profound API server to allow OAuth2 as an authentication method, an OpenAPI configuration must be setup 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 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

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 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 PAPI 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, 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

Examples showing minimum properties needed for validating PAPI requests via OAuth2 are below. If you need to setup Profound.js as an authentication server to let users sign on, then see the section, “Sample Workspace for OAuth2 Sign-On”.

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" } } },

Note: the URLs above are valid for User-Validation APIs as of August 30, 2023.

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 https://profoundlogicsupport.atlassian.net/wiki/spaces/PUI/pages/164520358 under “Multiple Security Schemes” for more information.)

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

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

 

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": [] } ] }

 

Note: 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 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.

    • { "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:

#!/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 --installSamples

If you do not already have a config.js file, then 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 below:

$ 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, then you can follow instructions in the README.md file to learn what changes are necessary to the openapi.json configuration and the sample setup.js.