Skip to content

Developers

Add Harbor sign-in to your app

Use the OpenID Connect (OIDC) client your app already has. Register its callback in Harbor, copy the issuer and client ID, and use Authorization Code with PKCE. Choose Web backend for a server app (client_secret_basic with a server-held secret), or Browser/mobile for a public app without a secret. Both require S256 PKCE.

Set up your app

  1. 1. Register your callback. After business signup, open App setup and register your own app for Harbor sign-in. Enter its exact callback URL. The form suggests http://127.0.0.1:3000/callback for local testing; replace it if your app uses another route.
  2. 2. Copy your settings. Your app page shows the issuer, client ID, and registered callback. Put those values in your app’s OIDC configuration. You do not need an API key or client secret for user sign-in.
  3. 3. Test sign-in. Send the browser to Harbor from your app’s sign-in route. Harbor returns it to your callback. Validate the response, identify the user by the returned sub, and resolve its existing local account before creating your app session.

If your app already supports OIDC

Add Harbor as an identity provider with these settings. Your OIDC library can discover Harbor’s authorize, token, and signing-key endpoints from the issuer.

SettingUse
IssuerCopy HARBOR_ISSUER from your app page. Enable OIDC discovery.
Client IDCopy HARBOR_CLIENT_ID from your app page.
Redirect URIUse the exact callback registered in Harbor.
Flow and scopeAuthorization Code, PKCE with S256, and openid. Enable email in Allowed permissions and request openid email when needed.
Client authenticationclient_secret_basic for Web backend; store the secret on your server. none for Browser/mobile; leave the secret empty.

Have separate local and production callbacks? Add both to the same Harbor application, then select the callback that matches your environment.

Adding OIDC to a Node app

These functions fit into your own sign-in and callback routes. They use openid-client v6 and Redis for a five-minute, one-time login transaction. Your app supplies the trusted, opaque browser session ID and creates its own customer session after resolving the identity.

Show the Node integration example
npm install openid-client@^6.8.7 redis

Set HARBOR_ISSUER, HARBOR_CLIENT_ID, HARBOR_CLIENT_TYPE,, HARBOR_SCOPES, and HARBOR_REDIRECT_URI from your app page before using this code. Configure REDIS_URL for your real session infrastructure. For Web backend, also set HARBOR_CLIENT_SECRET on the server to the secret shown once during creation. For Browser/mobile, leave it unset.

import * as oidc from 'openid-client';
import { createClient } from 'redis';
const transactions = createClient({ url: process.env.REDIS_URL });
transactions.on('error', () => console.error('Login transaction storage unavailable.'));
await transactions.connect();
const loginKey = session => 'harbor:login:' + session.id; // Trusted, opaque server session ID.

const redirectUri = process.env.HARBOR_REDIRECT_URI;
const clientType = process.env.HARBOR_CLIENT_TYPE;
if (!['public', 'confidential'].includes(clientType)) throw new Error('Set HARBOR_CLIENT_TYPE.');
if (clientType === 'confidential' && !process.env.HARBOR_CLIENT_SECRET) throw new Error('Set the server-held client secret.');
const harbor = await oidc.discovery(
  new URL(process.env.HARBOR_ISSUER),
  process.env.HARBOR_CLIENT_ID,
  undefined, clientType === 'confidential'
    ? oidc.ClientSecretBasic(process.env.HARBOR_CLIENT_SECRET)
    : oidc.None(),
  { execute: [oidc.enableNonRepudiationChecks] },
);

// Call from your existing sign-in route. Save the session before redirecting.
export async function startHarborSignIn(session) {
  const verifier = oidc.randomPKCECodeVerifier();
  const challenge = await oidc.calculatePKCECodeChallenge(verifier);
  const state = oidc.randomState();
  const nonce = oidc.randomNonce();
  await transactions.set(loginKey(session), JSON.stringify({ verifier, state, nonce }), { EX: 300 });
  return oidc.buildAuthorizationUrl(harbor, {
    redirect_uri: redirectUri, scope: process.env.HARBOR_SCOPES || 'openid',
    code_challenge: challenge, code_challenge_method: 'S256',
    state, nonce,
  }).href;
}

// Call from your registered callback route with its full URL.
export async function finishHarborSignIn(session, callbackUrl) {
  const raw = await transactions.getDel(loginKey(session)); // Atomic, one-time consume.
  const pending = raw ? JSON.parse(raw) : undefined;
  if (!pending) throw new Error('Sign-in expired. Start again.');
  const tokens = await oidc.authorizationCodeGrant(
    harbor, new URL(callbackUrl), {
      pkceCodeVerifier: pending.verifier,
      expectedState: pending.state,
      expectedNonce: pending.nonce,
      idTokenExpected: true,
    },
  );
  const subject = tokens.claims()?.sub;
  if (!subject) throw new Error('Harbor did not return a subject.');
  const userInfo = await oidc.fetchUserInfo(harbor, tokens.access_token, subject);
  return { issuer: tokens.claims().iss, subject,
    email: userInfo.email_verified === true ? userInfo.email : undefined };
  // Find the linked local customer by (issuer, subject).
  // If unfamiliar, offer Connect existing account / Create account; never auto-link by email.
}

Keep the PKCE verifier, state, and nonce in a short-lived server-side login transaction tied to the browser. Save it before redirecting and clear it when the callback is handled. Use your own session store for the signed-in user; do not put tokens or the verifier in browser storage.

Email and existing customer accounts

Enable email in your application’s Allowed permissions, then request openid email. Harbor asks the user to approve sharing their verified real email address. Read it from UserInfo with the access token and check that UserInfo’s subject matches the validated ID token. Email is omitted when no verified address exists. Name, picture, phone and address scopes are not available yet.

  1. Look up the exact issuer and subject in your own external-identities table. A linked identity signs in the same existing customer.
  2. For an unfamiliar identity, show Connect your existing account and Create a new account. Keep a short-lived, one-time continuation bound to this browser; do not silently create a duplicate account.
  3. Connecting requires your site’s normal sign-in or recovery, including MFA, and an explicit confirmation. A matching verified email may help users choose an account; it never proves ownership of that local account.
  4. Store the issuer and subject against the existing customer ID with a unique database constraint. Keep their orders, subscriptions and contact email. Never reassign an identity already linked to another customer.

Start sign-in and account linking with CSRF-protected POSTs. Consume the login transaction atomically, validate the callback with your OIDC client, and rotate your local session after login. Harbor disconnect stops future Harbor token use; your app controls its own sessions.

What your users see

Business signup sets up your Harbor account and app registration. It is separate from the sign-in flow your users see.

A user starts in your app, goes to Harbor to create a personal account or sign in to an existing one, then returns to your callback. Your app decides what that signed-in user can do. Harbor’s sub is specific to your app; use it as the Harbor account key rather than assuming an email address identifies the user.

Try the end-user demo

The demo shows the user journey. Your integration runs in your own app.

Before you go live

  • Add your production HTTPS callback to the same Harbor application. Callback URLs must match exactly; wildcards are not accepted.
  • Test sign-in with a new Harbor user and a returning user.
  • Use a shared, durable session store if your app runs on more than one server.

If sign-in fails

CheckWhat to do
Callback mismatchCompare your app’s redirect URI to the one in Harbor, including scheme, port, path, and trailing slash.
Client secret requestedUse client_secret_basic and your server-held secret for Web backend. Select none only for Browser/mobile.
State or PKCE errorMake sure the same browser session can retrieve the verifier, state, and nonce after Harbor redirects back.

Using SAML instead? Contact us. For OIDC endpoints and supported settings, view Harbor’s discovery document.