Skip to content

Connect your identity provider

How people sign in to Winner AI through the identity provider your organisation already runs, who becomes a Manager, and how people join and leave. It is written for the operator who installs the server and the administrator of the provider, who are often the same person, and it needs no help from us.

The sign-in gate admits anyone your provider admits and proves who they are, and decides no role; the control plane decides the role on every request. A Manager sees the whole organisation, its roster and the dashboard. A Member sees only their own, and every manager page refuses them.

Another provider means no Auth0 Management credentials

A production installation signs in through any OpenID Connect provider, JumpCloud among them, with AUTH0_MGMT_CLIENT_ID and AUTH0_MGMT_CLIENT_SECRET blank. With them set, the control plane finds each person by e-mail in Auth0, so scripts/check-env.sh and the control plane refuse an https installation whose issuer is not Auth0's. An installation with Auth0 Management credentials reads roles from Auth0; with them blank the group rules below apply, on Auth0 too.

On an installation from the bundle there is no make: scripts/install-server.sh runs scripts/check-env.sh against .env before it loads or starts anything, and stops on a refusal with nothing started, so make check-env and make up below are what a checkout of the source runs instead. Commands on the other pages says how to read them, and who runs the server's commands, once the server is installed.

Before you install

This is the checklist the install asks you for. Have every answer to hand, and the install needs nothing else from us. Where the provider offers a choice, the answer is the one that gives the control plane a stable identity and a group claim it can read.

You need Why
Whether your provider's plan includes single sign-on with a custom OpenID Connect application The gate is an OpenID Connect client. A plan with no way to register one cannot sign people in
The provider's issuer address, and its region where it has one OAUTH2_PROXY_OIDC_ISSUER_URL. A provider with a region per organisation may have one issuer per region
Whether the server can reach the issuer over https from where it runs The gate fetches the provider's discovery document, tokens and keys from the server, not from a browser
An application at the provider for the gate, with its client id and client secret AUTH0_PROXY_CLIENT_ID and AUTH0_PROXY_CLIENT_SECRET hold them, whichever provider they belong to
The name of the group whose members are Managers FLEETVIEW_MANAGER_GROUP
Whether that group is bound to the application Some providers put only the groups that connect a person to the application in the token, so a manager group that is not bound never arrives
The name of the attribute or claim that carries group membership, and whether it holds a list or one string OAUTH2_PROXY_OIDC_GROUPS_CLAIM. Some providers let the administrator name it; there is no fixed name
Whether the provider sends email_verified, and whether people can choose their own address Decides OAUTH2_PROXY_ALLOW_UNVERIFIED_EMAIL and whether the manager e-mail fallback is safe
The provider's end-session address OIDC_END_SESSION_URL, where sign-out ends the provider's own session
Whether an access policy applies to the application, such as a managed-device or network condition A laptop is approved in a browser. A developer whose browser the policy refuses cannot finish approving
The public address people browse to PUBLIC_BASE_URL, which every redirect below is built from, and which a production installation sets to https
A key for the installation's one organisation AUTH0_TEST_ORG_ID: on Auth0 the Organization's id (org_...), on any other provider any name you choose for the installation, set once; see One organisation

How sign-in works

  1. A person opens the address. The gate, oauth2-proxy, sends them to your provider, and on their return checks the token and the issuer.
  2. The gate admits them if the provider did. Who may sign in at all is the provider's decision: assign the application to the people or the groups you want.
  3. The gate forwards the person's permanent id, e-mail and groups to the control plane, which trusts them because the gate is the only ingress.
  4. On the first page they open, or their first laptop approval, the control plane finds or makes the person's row. Without Auth0 Management credentials, the row is keyed on the issuer and the provider's permanent id together, never on the e-mail; another provider presenting the same id is another person, and the control plane recomputes the role from the groups on every request after. With them, which only an Auth0 installation sets, the control plane finds the person by e-mail in Auth0, and the role comes from Auth0, kept current by its role sync (Onboard on Auth0).

Set up any OpenID Connect provider

At the provider

Create an application for the gate:

  • A web application using the authorisation code flow, authenticating with a client secret.
  • The redirect address: <PUBLIC_BASE_URL>/oauth2/callback, for example https://winner.example.org/oauth2/callback.
  • The scopes openid, profile and email, and groups where the provider puts groups in the token only when asked.
  • The groups attribute included in the ID token, under a claim name you choose and write down.
  • The post-logout redirect address, where the provider has one: PUBLIC_BASE_URL.
  • The permanent identifier left at its default. A provider that lets sub be remapped to an attribute people can change breaks the row each person is keyed on.
  • The application assigned to the people who may sign in, and the manager group bound to it.

Keep the issuer once people have signed in. Without Auth0 Management credentials, the case on any provider but Auth0, a person is the issuer and the permanent id together, so a new issuer, another provider or another region of the same one, meets everyone as a new person with no laptops, in the role their first request's groups give them (or the manager e-mail list, where that decides), while their old rows keep their history. Nobody is matched to an old row by e-mail.

In .env

These settings sit in the installation's .env. The gate's issuer, groups claim and whitelist, left blank, mean Auth0's values; the scopes mean openid profile email; the end-session address and the two manager settings mean none.

Setting Holds
AUTH0_PROXY_CLIENT_ID, AUTH0_PROXY_CLIENT_SECRET The gate's application at your provider. They keep their Auth0 names
OAUTH2_PROXY_OIDC_ISSUER_URL Your provider's issuer
OAUTH2_PROXY_SCOPE The scopes the gate asks for. openid profile email, plus groups where the provider needs it
OAUTH2_PROXY_OIDC_GROUPS_CLAIM The claim the gate reads groups from
OIDC_END_SESSION_URL The provider's end-session address
OAUTH2_PROXY_WHITELIST_DOMAINS The hosts the gate may send a browser to after sign-out: the end-session address's host, comma-separated. A leading . or *. admits subdomains
OAUTH2_PROXY_ALLOW_UNVERIFIED_EMAIL false where the provider's email_verified reflects a real check and people cannot choose their address. JumpCloud's is a constant, below
FLEETVIEW_MANAGER_GROUP The group whose members are Managers
FLEETVIEW_MANAGER_EMAILS Comma-separated fallback addresses, below
AUTH0_MGMT_CLIENT_ID, AUTH0_MGMT_CLIENT_SECRET Blank on any provider but Auth0: an https installation refuses them with another issuer
AUTH0_DOMAIN, AUTH0_AGENT_CLIENT_ID On a provider other than Auth0, placeholders that resolve nowhere (unused.invalid, unused): scripts/check-env.sh still requires both
# the shape of the block, with placeholders: nothing here is a value to copy
AUTH0_PROXY_CLIENT_ID=<client id>
AUTH0_PROXY_CLIENT_SECRET=<client secret>
OAUTH2_PROXY_OIDC_ISSUER_URL=https://<issuer>
OAUTH2_PROXY_SCOPE=openid profile email groups
OAUTH2_PROXY_OIDC_GROUPS_CLAIM=<the claim name>
OIDC_END_SESSION_URL=https://<end-session address>
OAUTH2_PROXY_WHITELIST_DOMAINS=<the end-session address's host>
OAUTH2_PROXY_ALLOW_UNVERIFIED_EMAIL=false
FLEETVIEW_MANAGER_GROUP=<the manager group's name>

Check .env before anything starts: on a server installed from the bundle, scripts/install-server.sh runs scripts/check-env.sh itself (Install the server, step 5); in a checkout of the source, run make check-env. It refuses an end-session address whose host the gate would not send a browser to, a whitelist entry that covers a whole public suffix (.com, *.co.uk), and on an https installation an issuer or end-session address that is not https. Each refusal names the values it compared.

The scopes are always passed to the gate: openid profile email unless you write others. A provider that puts groups in the token only when asked needs groups written out; a provider that lists no such scope may refuse the sign-in if you add it.

Who is a Manager

Without Auth0 Management credentials, the control plane reads the role from the groups the gate forwards:

  • A person whose groups include FLEETVIEW_MANAGER_GROUP is a Manager. The match is exact: a group called all managers does not make its members Managers where the group is managers.
  • The gate forwards a list claim's groups, on one header line or several, and a string claim as it is; the control plane joins any lines with commas and splits on commas. A group name with a comma in it cannot be the manager group, and spaces inside a name are kept.
  • Where FLEETVIEW_MANAGER_GROUP is set, a person whose groups arrive and do not include it is a Member, even if their address is on the e-mail list: the group alone decides.
  • The e-mail fallback decides only where sign-in brings no usable groups claim: none at all, an empty one, or one in a form the control plane cannot read; or where FLEETVIEW_MANAGER_GROUP is blank. An address in FLEETVIEW_MANAGER_EMAILS is then a Manager, and any other a Member.
  • A groups value that reads as something other than text, such as 123 or true, counts as no claim. So a manager group named only with digits never matches for a person in no other group: give it a name with a letter in it.
  • A person who has been removed stays removed. Neither the group nor the list brings them back.

The e-mail list trusts any address the gate admits. scripts/check-env.sh therefore refuses a FLEETVIEW_MANAGER_EMAILS unless OAUTH2_PROXY_ALLOW_UNVERIFIED_EMAIL is false. false refuses a sign-in whose email_verified is false; a provider that sends no email_verified is admitted either way, so the list is safe only on a provider where nobody can choose another person's address.

A change to FLEETVIEW_MANAGER_EMAILS or FLEETVIEW_MANAGER_GROUP takes effect at the next request once the backend has been recreated with the new .env: in <install>, run ./scripts/check-env.sh, then docker compose up -d. A change at the provider does not reach the control plane at all until the person signs in again: the groups are those of their last sign-in, and the gate's session lasts FLEETVIEW_GATE_SESSION_SECONDS, an hour by default, before the gate sends the person back to the provider. A person taken out of the manager group stays a Manager until then.

Check it

  1. Sign in as someone in the manager group. Home, Admin and the Dashboard open, and Settings shows the manager group, the e-mail list and the groups claim under Features and host.
  2. Sign in as someone outside it, in a private window. Their Home opens on their own sessions ("Your sessions."), and every manager page says the Manager role is needed.
  3. Open Admin as the Manager. Both people are on the roster.
You see Likely cause
The sign-in page of the provider says the redirect address is not registered The address at the provider is not <PUBLIC_BASE_URL>/oauth2/callback exactly
A Manager is told the Manager role is needed The group never arrived: it is not bound to the application, the claim name differs from OAUTH2_PROXY_OIDC_GROUPS_CLAIM, the scope that brings it is missing, or the group's name differs from FLEETVIEW_MANAGER_GROUP by a character
Everyone is a Manager The manager group is a broad one every person is in
Everyone is a Member The e-mail list is deciding, because FLEETVIEW_MANAGER_GROUP is blank or the sessions carry no usable groups claim, and the person is not on it
Sign-out leaves the person signed in The provider keeps a session of its own that the end-session address does not end (on JumpCloud, the User Portal's: JumpCloud), or the provider wants an id_token_hint, which the control plane never sends (Sign-out). Less often, .env was changed without ./scripts/check-env.sh and the end-session address's host is not in OAUTH2_PROXY_WHITELIST_DOMAINS, so the gate dropped the redirect

Sign-out

The control plane's sign-out link clears the gate's cookie and, where OIDC_END_SESSION_URL is set, sends the browser on to the provider's end-session address with the gate's client id and the public address to return to. The control plane never holds the ID token, so it sends no id_token_hint; a provider that insists on one signs the person out less completely.

One organisation

An installation serves one organisation, configured once. Every person the provider admits belongs to it, and nothing at sign-in or enrolment names an organisation.

  • Its key is AUTH0_TEST_ORG_ID in .env, which the control plane reads as FLEETVIEW_BOOTSTRAP_ORG_ID. It keeps the Auth0 name in this release, whichever provider you use.
  • On Auth0, the key is the Organization's id (org_...). On any other provider it is any name you choose for the installation, set once; the rule below applies to it.
  • The key names the organisation's database schema, once, at the first start. It needs a letter or a digit, and must make a schema of at most 56 bytes. After that the schema is stored and never derived again, so no schema is renamed, dropped or recreated.
  • Choose the key once, for the installation's life. Changing it does not move anyone: each person who signed in before stays in the organisation they were first recorded in, so a Manager among them gets a 403 at the Dashboard.
  • A change of key needs the whole stack brought up again with docker compose up -d, which recreates the web server and the dashboard's pg serve. Restarting only the backend leaves the Dashboard answering 503 until you do, because the web server admits only the organisation it was started with.
  • A manager of any other organisation the control plane once provisioned gets a 403 at the Dashboard.

A trial with the bundled Dex

For an evaluation with no provider of your own, the install can run Dex, a small stock OpenID Connect provider, beside the gate. It is for loopback only and production refuses it.

  • It holds two accounts in one file: manager@example.com, in the manager group, and member@example.com, in none. Both have the password winner-trial.
  • It is switched on by the trial compose profile, and reached at http://localhost:4180 and nothing else, since Dex knows only that address.
  • scripts/check-env.sh and the control plane both refuse an https base URL with the trial on, or the bundled Dex as issuer, so a trial cannot become a production sign-in by a mistake in either.
  • The trial needs no Auth0 values beyond placeholders for the gate's client and the one organisation, and the Auth0 Management credentials stay blank: that is what makes the control plane read roles from Dex's groups.
  • Sign-out clears the gate's cookie and nothing else, since Dex has no end-session endpoint. Whether Dex keeps its own browser session after that has not been checked.

Add these lines to .env and put trial in COMPOSE_PROFILES. On a server installed from the bundle, then run ./scripts/install-server.sh, which checks .env and installs the trial on loopback, with no edge and no public port (Install the server, "A trial from the bundle"). In a checkout of the source, run make check-env, then make up:

OAUTH2_PROXY_OIDC_ISSUER_URL=http://localhost:4180/dex
OAUTH2_PROXY_SCOPE=openid profile email groups
OAUTH2_PROXY_OIDC_GROUPS_CLAIM=groups
FLEETVIEW_MANAGER_GROUP=manager
OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true
OAUTH2_PROXY_LOGIN_URL=http://localhost:4180/dex/auth
OAUTH2_PROXY_REDEEM_URL=http://dex:5556/dex/token
OAUTH2_PROXY_OIDC_JWKS_URL=http://dex:5556/dex/keys

The last four exist because the gate cannot read Dex's discovery document at start: Dex's issuer is the gate's own address, which does not answer until the gate is up. The trial names the endpoints instead, and scripts/check-env.sh refuses all four on an https base URL.

Why production refuses it. The users and their password hashes ship in a file anyone holding it can read. The page for Dex's local accounts documents no second factor, lock-out or password reset, and nobody at your organisation administers it, so a leaver stays until the file is edited. Its ID tokens last 24 hours by default, as measured on a stock Dex. And a bundled issuer is a second identity system beside the one you already run.

How people join and leave

Joining

  1. At the provider, assign the application to the person or to a group they are in. If they should be a Manager, put them in the manager group, and make sure that group is bound to the application.
  2. They sign in. The first page they open, or their first laptop approval, makes their row, keyed on the issuer and the provider's permanent id, with the role the rule above gives them. Until then they are not on the roster, so a person who has never signed in is not shown.
  3. They install the client (Laptop client guide) and run winner login, which prints a code and an address. They open the address in any browser, confirm the code, and the terminal carries on and the laptop is enrolled. That login is the control plane's own: the person approves the laptop at /login/approve behind the gate, and the laptop is enrolled as them, in the installation's one organisation. A Member's laptop pushes sessions into the organisation's database like a Manager's, and the roster shows it.

A Member who signs in sees their own Home: the figures of their own sessions, with a link to a page of their own laptops that shows whether each is still enrolled, and one of their laptops' repos. Where FLEETVIEW_FEATURES switches them on, and while the licence or its grace allows them (Licence), they also see a Prompt Coaching page of the findings on their own prompts; a Skills page that lists the repeated workflows found in their own prompts and offers the approved Skills made from them; and a Benchmarks page of their own prompt patterns, with what a cheaper model did when it re-ran one. They read their own sessions on their laptop, in the session collector (agentsview), and the web Dashboard is managers only.

To start a new installation, have one person in the manager group sign in first, then assign the application to everyone else; the roster fills as they sign in.

Adding someone at the provider is the whole of it. Admin's Add member creates the person in Auth0, so it is for an Auth0 installation only; on any other provider the form is still shown and cannot work, and the control plane can create no one there.

Leaving

Removal at the provider stops new sign-ins. It does not stop what the person already has:

  • Their web session at the gate lasts until its sign-in ends, at most FLEETVIEW_GATE_SESSION_SECONDS, an hour by default.
  • A laptop that is already enrolled keeps sending. A laptop pass has no fixed expiry; it ends when a Manager removes the person or revokes the laptop.

So a Manager removes a leaver on Admin:

  • Remove, on the person's row, ends their laptop passes, drops each of their laptops' database logins and marks the row revoked. A laptop's next report is refused, and its next push fails. Nothing is deleted: the person and their laptops stay in the archive, so history and attribution are unchanged.
  • A removed person who signs in is told they were removed from the team. Signing in again does not bring them back, and neither does being in the manager group.
  • Restore, on a removed person's row, makes them a Member again with no laptop. They run winner login on their laptop again to enrol it, and the group or the e-mail list may make them a Manager again on their next request. Restore refuses while any of their laptops is still enrolled; Remove finishes that, and is also how a removal whose database login could not be dropped is retried.
  • Revoke, on one laptop, ends that laptop's pass and its database login only. The person's other laptops keep reporting.
  • A Manager cannot remove themselves, and two Managers removing each other at once leave one.

Remove and Restore appear only where no Auth0 Management credentials are set, because an installation that has them runs a sweep that would write a removal back to whatever Auth0 says. There, Admin says people are removed in Auth0, and the next sync revokes their laptops (Onboard on Auth0).

JumpCloud

A worked example on JumpCloud. The JumpCloud administrator makes the application with JumpCloud: before the install, which reads on its own, and hands back the values in its last table; this section is the operator's side, from those values to the first sign-in. Both were followed on a live organisation on 2026-11-03, and what that first run showed is at the end.

Decide the public address people will browse to, <base URL>, before the administrator starts: it is PUBLIC_BASE_URL, and the application is registered against it.

In .env

Put the values the administrator handed back in .env; the checklist's last table says which value fills which setting. The block below is for the United States region, the only one tried. In the European Union and India regions the issuer is https://oauth.id.eu.jumpcloud.com/ or https://oauth.id.in.jumpcloud.com/, but the User Portal's logout address there has never been tried, and the install cannot start without an end-session address: contact Winner AI support before the install.

AUTH0_PROXY_CLIENT_ID=<Client ID>
AUTH0_PROXY_CLIENT_SECRET=<Client Secret>
OAUTH2_PROXY_OIDC_ISSUER_URL=https://oauth.id.jumpcloud.com/
OAUTH2_PROXY_SCOPE=openid profile email
OAUTH2_PROXY_OIDC_GROUPS_CLAIM=<group attribute name>
FLEETVIEW_MANAGER_GROUP=<Managers group name>
OIDC_END_SESSION_URL=https://console.jumpcloud.com/userconsole/logout
OAUTH2_PROXY_WHITELIST_DOMAINS=console.jumpcloud.com

The end-session address is JumpCloud's User Portal logout, not the OIDC one in the issuer's discovery document. The session that signs a person straight back in is the User Portal's, and only its logout ends it. The OIDC address refuses a sign-out with a return address and no id_token_hint, and without them it ends the OIDC session but leaves the User Portal's. Sign-out then lands on JumpCloud's sign-in page.

The scopes carry no groups: JumpCloud's discovery document lists openid, profile, email, offline and offline_access and no groups scope, and the group claim arrives through the group attribute the administrator named on the application. Leave OAUTH2_PROXY_ALLOW_UNVERIFIED_EMAIL unset and do not use the e-mail fallback with JumpCloud: What the first run showed says why.

AUTH0_DOMAIN and AUTH0_AGENT_CLIENT_ID name no Auth0 tenant here, but scripts/check-env.sh still requires a value for each: write placeholders that resolve nowhere.

AUTH0_DOMAIN=unused.invalid
AUTH0_AGENT_CLIENT_ID=unused
AUTH0_MGMT_CLIENT_ID=
AUTH0_MGMT_CLIENT_SECRET=

The control plane reads the manager group only where AUTH0_MGMT_CLIENT_ID is blank, which scripts/check-env.sh accepts with FLEETVIEW_MANAGER_GROUP or FLEETVIEW_MANAGER_EMAILS set. An installation on JumpCloud leaves the Management keys blank: scripts/check-env.sh and the control plane refuse them there.

Check the JumpCloud sign-in

Sign in as a person in the Managers group and as one in the Members group. As in Check it above, the Manager reaches Home, Admin and the Dashboard, and the Member's Home opens on their own sessions ("Your sessions."), and every manager page says the Manager role is needed.

A laptop enrols through the control plane's login: each person runs winner login and approves their own laptop's code (How people join and leave).

What the first run showed

A first install on a live JumpCloud organisation, on 2026-11-03, answered what this page had left open:

  1. Scopes. openid profile email, with no groups scope, signs people in, and the group attribute named on the application arrives.
  2. email_verified. With the Email scope ticked, the console adds a constant attribute, email_verified with the value true, which cannot be removed. Every user arrives verified, whatever happened to their address. So the claim proves nothing on JumpCloud: the manager e-mail fallback is not safe there, and the allowance for an unverified e-mail stays on (the release default), which refuses nobody.
  3. Sign-out. It ends the JumpCloud session only through the User Portal's logout, the end-session address in the block above. After signing out, opening the address asks for the JumpCloud password again.

The checklist marks, on the step each belongs to, the console details its sources did not state, among them which client authentication type the gate needs.