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.
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.
REPSET_OIDC_ISSUER_URL=https://auth.repset.io/realms/repset
REPSET_CLIENT_ID=integrator-... // get from console
REPSET_CLIENT_SECRET=... // get from console
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.
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.
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.
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
4. Store the link
Store the Repset userId and the chosen companyId against your own user record. You need both on almost all requests.
function linkRepsetAccount(userId, repsetUserId, repsetCompanyId) {
db.prepare(`UPDATE users SET repsetUserId = ?, repsetCompanyId = ? WHERE id = ?`).run(
repsetUserId,
repsetCompanyId,
userId,
);
}