Developer docs

Integrating with Clops SSO

Clops SSO is a standard OpenID Connect provider. If your framework has an OIDC client, you are ten minutes from a working login.

1. Register your product

In the SSO console, under Connected Products, add your product with the exact redirect URI your app will use. You get a client id and a client secret; the secret is displayed once and stored only as a hash, so put it in your secret manager immediately.

Redirect URIs must be absolute and use https, except on localhost for development. They are matched exactly — no wildcards, no prefixes.

2. Send the user to sign in

Use the discovery document rather than hard-coding endpoints: https://sso.clopsai.com/.well-known/openid-configuration

// Node — npm i openid-client
import * as client from 'openid-client';

const config = await client.discovery(
  new URL('https://sso.clopsai.com'), process.env.CLOPS_CLIENT_ID, process.env.CLOPS_CLIENT_SECRET);

const verifier = client.randomPKCECodeVerifier();
// store `verifier` in the user's session — it must survive the round trip

const url = client.buildAuthorizationUrl(config, {
  redirect_uri: 'https://your-app.com/auth/callback',
  scope: 'openid email profile',
  code_challenge: await client.calculatePKCECodeChallenge(verifier),
  code_challenge_method: 'S256'
});
res.redirect(url.href);

3. Handle the callback

Exchange the code for tokens. The library verifies the id_token signature against the JWKS for you, and checks the nonce and audience.

const tokens = await client.authorizationCodeGrant(config, currentUrl, {
  pkceCodeVerifier: req.session.verifier
});

const claims = tokens.claims();
// claims.sub    — stable user id; key your local user on this, not the email
// claims.email  — may change over time
// claims.tenant — which workspace the user belongs to

const user = await db.upsertUser({ ssoSub: claims.sub, email: claims.email });
req.session.userId = user.id;

Python and PHP

# pip install authlib
oauth.register(
    name="clops",
    server_metadata_url="https://sso.clopsai.com/"
        ".well-known/openid-configuration",
    client_id=CLOPS_CLIENT_ID,
    client_secret=CLOPS_CLIENT_SECRET,
    client_kwargs={
        "scope": "openid email profile",
        "code_challenge_method": "S256",
    },
)

token = await oauth.clops.authorize_access_token(request)
user = token["userinfo"]
// Laravel — any OIDC package works
'clops' => [
    'client_id'     => env('CLOPS_CLIENT_ID'),
    'client_secret' => env('CLOPS_CLIENT_SECRET'),
    'redirect'      => env('CLOPS_REDIRECT_URI'),
    'issuer'        => 'https://sso.clopsai.com',
],

$clopsUser = Socialite::driver('clops')->user();
User::updateOrCreate(
    ['clops_sub' => $clopsUser->getId()],
    ['email' => $clopsUser->getEmail()]
);

Tokens you receive

TokenLifetimeUse
id_token1 hour Who the user is. Verify against the JWKS, then create your own session.
access_token1 hour Call /sso/userinfo. Not meant for your own APIs.
refresh_token14 days, rotating Only issued when you request the offline_access scope.

Refresh tokens rotate on every use. If an old one is presented again, we treat it as a leak and revoke the entire family — so always store the newest value you were given.

Signing users out

Clear your own session, then send the browser to the end-session endpoint so the SSO session ends too. Otherwise the next sign-in click goes straight back through.

GET https://sso.clopsai.com/sso/logout
      ?id_token_hint=<the id_token>
      &post_logout_redirect_uri=https://your-app.com/goodbye

The redirect URI must be registered as a post-logout URI for your product, or the user simply lands on a Clops confirmation page.

Common mistakes

Keying users on email

Emails change. Store sub as the identity and treat the email as a mutable attribute.

Skipping PKCE

It is required, including for confidential clients. Requests without code_challenge_method=S256 are rejected.

Trusting an unverified token

Always verify the signature, issuer and audience. A library does this; hand-rolled base64 decoding does not.

Questions while integrating?

Send us the discovery URL you are pointing at and the error you see — that is usually enough to spot it.