Build an appConnection examples

Client-side browser examples

Run a JavaScript or TypeScript browser app with PKCE, a registered origin, and no client secret.

Register Browser application, authentication none, and scope profile:read. For this demo, register callback http://127.0.0.1:8080/browser.html and browser origin http://127.0.0.1:8080. The origin has no trailing slash or path. Browser applications never receive or embed a client secret.

Run JavaScript or TypeScript

Download browser.html and either the JavaScript file or the TypeScript source below. Save them in a new directory containing only the demo files. Set issuer and clientId at the top of the script to your enabled environment and browser app registration. Keep redirectUri equal to the registered callback.

Download browser.js. The HTML already loads this file. It needs no build step or JavaScript dependencies.

Serve the demo directory on the registered loopback address (Python 3):

python -m http.server 8080 --bind 127.0.0.1

Open http://127.0.0.1:8080/browser.html and choose Link your R+D account. After consent, choose Check profile access, then Disconnect. Test denial as well. Use a directory with no credentials: the local static server serves its contents. Do not use file://, another port, or localhost with this registration. For a deployed app, register its exact HTTPS callback and origin and update the script.

How the browser exchange works

The script uses Web Crypto to generate random state and a new S256 PKCE challenge. It stores only the short-lived transaction in sessionStorage before navigation. At the callback it removes that transaction, clears the code from the URL, rejects unexpected state or issuer and duplicate parameters, and then exchanges the code.

Both language versions use this form-encoded public-client request:

export async function exchangeCode(
  issuer,
  clientId,
  redirectUri,
  code,
  verifier,
) {
  // Invoke only after consuming and validating the initiating transaction.
  const response = await fetch(new URL("/oauth/token", issuer), {
    method: "POST",
    credentials: "omit",
    redirect: "error",
    body: new URLSearchParams({
      grant_type: "authorization_code",
      client_id: clientId,
      redirect_uri: redirectUri,
      code,
      code_verifier: verifier,
    }),
    signal: AbortSignal.timeout(15_000),
  });
  if (!response.ok)
    throw new Error(`Token request failed (${response.status})`);
  return response.json();
}

The token and revocation endpoints allow only registered browser origins. Leave credentials: "omit" in place; do not attach dashboard cookies, HTTP Basic, or a client secret. API calls use the returned bearer token in the Authorization header. The demo checks granted scopes before enabling its profile action.

Token lifetime and browser storage

The demo keeps its access token in memory and requests no refresh token. Reloading the page loses that token; reconnect to continue. Access expires after ten minutes, and an API 401 requires reconnecting. A browser reload is not server revocation: use Disconnect before reloading, or revoke the app in R+D's Settings → Connected apps afterward.

Do not move bearer tokens into URLs, localStorage, or the PKCE transaction. For persistent access in a web product, consider the backend pattern so your server holds tokens. Any browser-based persistence must account for script access to credentials and R+D's strict refresh rotation rules.

Keep callback pages free of third-party scripts and analytics that could capture the code. The example HTML sets a no-referrer policy. Web Crypto requires a secure context; use HTTPS outside loopback development. See Web Crypto digest and the OAuth security recommendations.

On this page