User authentication

Before your backend can act on behalf of a specific Repset user, it needs that user’s user_id and company_id, and their consent to be linked. You get all three with the OpenID Connect (OIDC) Authorization Code Flow.

This flow only runs once per user, the first time they connect their account. It’s separate from the authentication your backend uses for every API call. A common pattern is a “Connect with Repset” button in your UI that starts this flow.

Example code

The snippets below (Node.js + openid-client) are illustrative, not production-ready. Adapt session handling, error handling, and which OIDC library to use to your stack.

Setup

Discover Repset’s OIDC configuration once at startup. All this must happen server-side since it uses your client_secret.

credentials.env
REPSET_OIDC_ISSUER_URL=https://auth.repset.io/realms/repset
REPSET_CLIENT_ID=integrator-... // get from console
REPSET_CLIENT_SECRET=... // get from console
oidc-setup.js
import * as client from "openid-client";

const authConfig = await client.discovery(
  new URL(process.env.REPSET_OIDC_ISSUER_URL),
  process.env.REPSET_CLIENT_ID,
  process.env.REPSET_CLIENT_SECRET,
);

1. Start the flow

When a logged-in user wants to connect their Repset account, generate a PKCE pair and a state, stash them on the session, and redirect to Repset’s authorization endpoint.

start-link.js
router.post("/init-link", requireLogin, async (req, res) => {
  const code_verifier = client.randomPKCECodeVerifier();
  const code_challenge = await client.calculatePKCECodeChallenge(code_verifier);
  const state = client.randomState();

  // Needed again in the callback, tied to this user's session
  req.session.repsetCodeVerifier = code_verifier;
  req.session.repsetState = state;

  const redirectUrl = await client.buildAuthorizationUrl(authConfig, {
    redirect_uri: `${process.env.BACKEND_URL}/api/repset/accept-link`,
    scope: "openid profile email", // the only scopes you need to request
    code_challenge,
    code_challenge_method: "S256",
    state,
  });

  res.json({ redirectUrl });
  // open the redirectUrl in your frontend application.
  // this redirects to the Repset login page.
});

2. Handle the callback

Repset redirects back to redirect_uri with a code and state. Exchange the code for tokens and read the sub claim off the ID token: that’s the Repset user’s id.

accept-link.js
router.get("/accept-link", requireLogin, async (req, res) => {
  const { repsetCodeVerifier, repsetState } = req.session;
  if (!repsetCodeVerifier || !state) return res.status(400).send("Missing PKCE verifier or state");

  const currentUrl = new URL(`${req.protocol}://${req.get("host")}${req.originalUrl}`);
  const tokens = await client.authorizationCodeGrant(authConfig, currentUrl, {
    pkceCodeVerifier: repsetCodeVerifier,
    expectedState: repsetState,
  });

  delete req.session.repsetCodeVerifier;
  delete req.session.repsetState;

  const repsetUserId = tokens.claims().sub;
  // continued in the next step...
});

3. Let the user pick a company

Fetch the user’s companies from the integration-api, and ask which company they want to link.

resolve-company.js
const res = await fetch(`https://integration-api.repset.io/api/v1/users/${repsetUserId}`, {
  headers: { Authorization: `Basic ${clientBasicAuth}` },
});
const { companies } = await res.json();

if (companies.length === 1) {
  return linkRepsetAccount(req.session.userId, repsetUserId, companies[0].id);
}

// More than one company: show `companies` to the user and let them choose

Store the Repset userId and the chosen companyId against your own user record. You need both on almost all requests.

link-repset-account.js
function linkRepsetAccount(userId, repsetUserId, repsetCompanyId) {
  db.prepare(`UPDATE users SET repsetUserId = ?, repsetCompanyId = ? WHERE id = ?`).run(
    repsetUserId,
    repsetCompanyId,
    userId,
  );
}