# Single sign-on (SSO)

Single sign-on lets your team sign in through an identity provider (IdP) with
one set of credentials instead of a LedgerBee password. LedgerBee supports
**OpenID Connect (OIDC)**: any provider that publishes a standard discovery
document works, including Microsoft Entra ID and Okta.

Configure SSO under **Settings → Workflows & access → Single sign-on**. You must
be an admin of the company.

## Setup

Three steps, in order. Each one unlocks the next, so the page only ever shows
you the step that is actually next.

1. **Verify an email domain.** Publish a `TXT` record to prove you control the
   domain your team's email addresses use. This is what lets LedgerBee route a
   sign-in to your provider.
2. **Configure the connection.** Register an OIDC application at your provider,
   then paste its issuer URL, client ID, and client secret into LedgerBee. A
   connection is set up ON a verified domain, so step 1 comes first.
3. **Save.** LedgerBee reads your provider's metadata and presents your client
   credentials to it as part of saving. The connection goes live when the
   provider accepts them.

| Provider | Guide |
| --- | --- |
| Microsoft Entra ID | [Set up SSO with Entra ID](/guides/sso/providers/entra-id) |
| Okta | [Set up SSO with Okta](/guides/sso/providers/okta) |
| Any other OIDC provider | [Set up SSO with any OIDC provider](/guides/sso/providers/other) |

## Verify domain ownership

A domain is the part of an email address after the `@`, such as `acme.dk`. A
company can verify several, each with its own connection and its own single
sign-on requirement. To retire one, remove it and add the replacement.

1. Under **Verified email domains**, enter the domain and click **Add domain**.
   LedgerBee shows a record name and a unique value.
2. Publish it at your DNS provider as a `TXT` record. The host is
   `_ledgerbee-sso` and the value looks like
   `ledgerbee-sso-verification=93390ce0eae88e0196f4b62c8ff3bb4a`.
3. Click **Check now**. The status moves from **Awaiting DNS** to **Verified**.

Enter the host as `_ledgerbee-sso`, without the domain. DNS providers append
their own zone to whatever you type in the name field, so a fully-qualified
value becomes `_ledgerbee-sso.acme.dk.acme.dk` and never resolves.

Only apex domains are accepted. `acme.dk` is valid; `mail.acme.dk` is not, which
is what keeps an address at a subdomain from matching a claim on the parent.
Public mailbox providers such as `gmail.com` are rejected.

### Keep the record published

LedgerBee re-checks every verified domain daily. If the record disappears, the
domain shows **Record missing**: everyone who already signs in keeps working,
and linking a *new* person by their email address pauses until the record is
back. Deleting the record does not lock your team out, and it does not leave
LedgerBee trusting a domain you may no longer control.

## The connection

Register the redirect URI shown on the page at your provider. Copy it with the
**Copy** button rather than typing it — the token exchange sends the same value
back to your provider, and the two must match byte-for-byte.

| Field | Value |
| --- | --- |
| **Issuer URL** | Your provider's OIDC issuer. LedgerBee fetches `/.well-known/openid-configuration` from it. Must be a public `https` URL. |
| **Client ID** | The client ID of the application you registered. |
| **Client secret** | Write-only. Stored encrypted and never shown again. |
| **Subject claim** | Which claim identifies the user. `sub` unless your provider documents otherwise. |

LedgerBee picks the client-authentication method from your provider's own
metadata — `client_secret_post` where it is offered, `client_secret_basic`
otherwise. There is nothing to configure.

## The connection check

Saving runs the check. It fetches your provider's metadata, confirms the issuer
it declares matches the one you entered, and then presents the client ID and
secret to the token endpoint to confirm your provider accepts them — all in the
same request, so what you see after saving is the outcome rather than a step
still to take.

Passing both is what takes the connection live. Nothing else in the interface
marks a connection live, and there is no field to declare one working — your
provider has to answer for the credentials.

If the check does not pass, the connection is still saved: your credentials are
not thrown away because a provider was unreachable. The status shows what
happened, and **Check again** re-runs it once the provider side is fixed.

Changing the issuer, client ID, client secret, or subject claim takes the
connection offline until it passes the check again. What the previous check
proved no longer describes what is stored.

The check proves your provider accepts LedgerBee's credentials. It does not
prove any particular person can sign in — that also depends on who your provider
assigns to the application, and on the subject claim matching. A first sign-in is
what confirms those.

## How a member is matched

LedgerBee resolves an assertion to a user in this order and stops at the first
match:

1. **A linked identity** — someone who has signed in through this connection
   before, matched on the subject claim your provider sends.
2. **A provisioned user** — with [SCIM provisioning](/guides/scim-provisioning),
   a user your IdP already pushed, matched on external ID or work email.
3. **A verified email domain** — the email address in the assertion, matched to
   an existing member, only when its domain is one you verified.

If none match, the sign-in is refused. An unknown person arriving from your
provider is a membership decision, not an authentication one, so bring them in
through SCIM or an invitation first.

Every match requires an active, accepted membership. A provider assertion proves
who someone is; it never grants access on its own.

## SSO enforcement

A live connection makes SSO possible. Enforcement decides who must use it, and
it is set **per verified domain**, not for the company as a whole. Every domain
in the table under **Settings → Workflows & access → Single sign-on** carries
its own requirement: open the row's action menu, choose **Configure
enforcement**, and set the **Require single sign-on** switch in the panel that
opens. The panel is also where the reason for a refusal, and the sign-in that
clears it, appear.

| State | Effect, for an address on that domain |
| --- | --- |
| **Off** | Any sign-in method works |
| **Required** | Only sign-in through that domain's connection reaches the company. Sessions signed in with a password, a passkey, or a personal Microsoft, Google or Apple account lose access. |

**A member on an address you have not registered keeps every sign-in method.**
The requirement belongs to the email domain rather than to the company, so an
external bookkeeper at `bookkeeper.dk` keeps signing in with a password while
everyone at `acme.dk` must come through your provider. Requiring a domain never
shuts out somebody that domain does not cover, which is what makes it safe to
require your own domain while people outside your directory still work in the
company.

It cuts the other way too: requiring one domain says nothing about a second one
you also verified. Require each domain you want covered.

Sign in through the connection **that domain** points at to satisfy **Required**.
A personal Microsoft sign-in does not satisfy it even from the same Entra
directory, because it authenticates against a shared multi-tenant application
that any directory can use.

Turning a requirement on ends the sessions it invalidates within minutes: the
ones held by members on that domain, including your own if your address is on
it, and no others.

The switch is unavailable while your own session would be one of them. Sign in
through the provider first — the page offers a button — and it becomes
available. Nothing blocks an administrator whose address is on a different
domain.

Members who cannot enter still see the company in their company switcher, marked
with the reason. Enforcement does not apply to advisor firms you have granted
access; those are governed by the grant under **Advisors**.

### What LedgerBee refuses

A domain can be required once both are true:

1. The connection it points at has passed the connection check, and nothing has
   failed through it since.
2. If your own address is on that domain, you signed in through it yourself.

Condition 2 applies only when you are one of the people the requirement would
cover. An administrator whose address is on another domain — an external
bookkeeper administering a client, say — can require a domain without signing in
through it, which is the point: they hold no account in that directory and never
will.

That also means requiring a domain no member of it has yet signed in through is
allowed. If the provider accepts our credentials but admits nobody — the
application is assigned to no users, or the subject claim is wrong — the people
on that address are shut out until it is fixed. The blast radius is that one
address at that one company; every other domain, and every member on one, is
unaffected. If it happens, LedgerBee support can relax every requirement at the
company — see [Recover from an SSO lockout](/guides/sso/lockout-recovery).

A requirement stays on through a provider outage. It is not relaxed when a DNS
record lapses or a discovery lookup fails, and a company locked out that way is
restored by LedgerBee support — see
[Recover from an SSO lockout](/guides/sso/lockout-recovery).

## What SSO does not do

**It does not assign roles.** LedgerBee reads no group or role claim from your
provider. Permissions, admin rights, and company membership are set in LedgerBee,
or synced with [SCIM provisioning](/guides/scim-provisioning). Enabling SSO
changes how someone signs in, not what they can do once they are in.

**It does not remove existing sign-in methods.** A member's password, passkey,
and personal Microsoft, Google or Apple sign-in keep working. Those belong to the
person across every company they work for, so one company's provider cannot reach
them — which is also what leaves you a way in if the connection breaks. Requiring
a domain stops those methods reaching THIS company for addresses on that domain;
it does not delete them, and they still reach every other company the person
belongs to.

If a step fails, see [Troubleshoot SSO](/guides/sso/troubleshooting).
