# SSO connection settings

> The organization's SAML or OIDC connection with its domains and the role new members get.

- Block: [sso](https://bettersupabase.com/blocks/sso.md)
- Kind: screen
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/sso-settings

## Install the block

```bash
npx better-supabase add sso
```

## Web (Next.js)

Add the shadcn components:

```bash
npx shadcn add alert badge button card empty skeleton
```

From `apps/examples/nextjs/src/features/sso/components/sso-settings.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/sso/components/sso-settings.tsx):

```tsx
/**
 * The organization's email domains (claimed with a TXT record, then
 * verified) and its SAML connection. A verified domain sets the role new
 * users at it join with and whether they must sign in with SSO.
 */
export async function SsoSettings() {
  if (!can(await getSession(), "sso.manage")) {
    forbidden();
  }

  const [t, format, result] = await Promise.all([
    getExtracted("sso"),
    getFormatter(),
    loadSettings(),
  ]);

  if (result.kind === "error") {
    return (
      <Alert variant="destructive" data-recipe="sso-settings">
        <TriangleAlertIcon />
        <AlertTitle>{t("SSO settings could not be loaded")}</AlertTitle>
        <AlertDescription>{t("Try again in a moment.")}</AlertDescription>
        <AlertAction>
          <form action={refreshSso}>
            <Button size="sm" variant="outline" type="submit">
              {t("Retry")}
            </Button>
          </form>
        </AlertAction>
      </Alert>
    );
  }

  if (result.data === null) {
    return (
      <p className="text-sm text-muted-foreground" data-recipe="sso-settings">
        {t("Pick an organization to manage its single sign-on.")}
      </p>
    );
  }

  const { domains, connections } = result.data;

  const verifiedDomains = domains
    .filter((domain) => domain.verified)
    .map((domain) => domain.domain);

  return (
    <div className="flex flex-col gap-6" data-recipe="sso-settings">
      <Card id="sso-domains">
        <CardHeader>
          <CardTitle>{t("Domains")}</CardTitle>
          <CardDescription>
            {t(
              "Claim the email domains your company owns. Publish the TXT record at your DNS host, then verify the domain.",
            )}
          </CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col gap-4">
          {domains.length === 0 ? (
            <Empty>
              <EmptyHeader>
                <EmptyMedia variant="icon">
                  <GlobeIcon />
                </EmptyMedia>
                <EmptyTitle>{t("No domains yet")}</EmptyTitle>
                <EmptyDescription>
                  {t("Add your company's email domain to start.")}
                </EmptyDescription>
              </EmptyHeader>
              <EmptyContent>
                <AddDomainForm />
              </EmptyContent>
            </Empty>
          ) : (
            <>
              <ul className="flex flex-col divide-y rounded-lg border">
                {domains.map((domain) => (
                  <li key={domain.id} className="flex flex-col gap-3 p-3">
                    <div className="flex flex-wrap items-center justify-between gap-2">
                      <div className="flex items-center gap-2">
                        <span className="text-sm font-medium">
                          {domain.domain}
                        </span>
                        {domain.verified ? (
                          <Badge variant="success-light">{t("Verified")}</Badge>
                        ) : (
                          <Badge variant="warning-light">
                            {t("Not verified")}
                          </Badge>
                        )}
                      </div>
                      <DomainActions domain={domain} />
                    </div>
                    {domain.verified ? null : (
                      <dl className="grid gap-1 text-xs text-muted-foreground sm:grid-cols-[auto_1fr] sm:gap-x-3">
                        <dt>{t("TXT name")}</dt>
                        <dd className="font-mono break-all text-foreground">
                          {domain.recordName}
                        </dd>
                        <dt>{t("TXT value")}</dt>
                        <dd className="font-mono break-all text-foreground">
                          {domain.recordValue}
                        </dd>
                      </dl>
                    )}
                  </li>
                ))}
              </ul>
              <AddDomainForm />
            </>
          )}
        </CardContent>
      </Card>
      <Card>
        <CardHeader>
          <CardTitle>{t("SAML connection")}</CardTitle>
          <CardDescription>
            {t(
              "Members at your verified domains sign in through your identity provider, such as Okta or Microsoft Entra ID.",
            )}
          </CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col gap-4">
          {connections.length === 0 ? (
            <Empty>
              <EmptyHeader>
                <EmptyMedia variant="icon">
                  <KeyRoundIcon />
                </EmptyMedia>
                <EmptyTitle>{t("No SAML connection")}</EmptyTitle>
                <EmptyDescription>
                  {verifiedDomains.length === 0
                    ? t(
                        "Verify a domain first, then connect your identity provider.",
                      )
                    : t(
                        "Connect your identity provider with its metadata URL.",
                      )}
                </EmptyDescription>
              </EmptyHeader>
              <EmptyContent>
                {verifiedDomains.length === 0 ? (
                  <a
                    href="#sso-domains"
                    className={buttonVariants({ variant: "outline" })}
                  >
                    {t("Go to domains")}
                  </a>
                ) : (
                  <AddSamlForm verifiedDomains={verifiedDomains} />
                )}
              </EmptyContent>
            </Empty>
          ) : (
            <ul className="flex flex-col divide-y rounded-lg border">
              {connections.map((connection) => (
                <li
                  key={connection.id}
                  className="flex flex-wrap items-center justify-between gap-2 p-3"
                >
                  <div className="flex min-w-0 flex-col gap-1">
                    <div className="flex flex-wrap items-center gap-2">
                      <span className="text-sm font-medium">
                        {t("SAML 2.0")}
                      </span>
                      {connection.domains.map((domain) => (
                        <Badge key={domain} variant="outline">
                          {domain}
                        </Badge>
                      ))}
                    </div>
                    <span className="truncate text-xs text-muted-foreground">
                      {connection.metadataUrl ?? t("Metadata uploaded as XML")}
                      {" · "}
                      {t("Added {date}", {
                        date: format.dateTime(new Date(connection.createdAt), {
                          dateStyle: "medium",
                        }),
                      })}
                    </span>
                  </div>
                  <RemoveConnectionButton id={connection.id} />
                </li>
              ))}
            </ul>
          )}
        </CardContent>
      </Card>
    </div>
  );
}
```

## Prompt

Paste this into a coding agent to build the recipe in your app.

### Web

```text
Build "SSO connection settings" in a React web app that uses shadcn/ui components. The organization's SAML or OIDC connection with its domains and the role new members get.

It uses the better-supabase `sso` block (SSO and SCIM). It loads its data on the server through the block's client.

Steps:

1. If the app doesn't have the block yet, add the SQL with `npx better-supabase add sso` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add alert badge button card empty skeleton`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the screen. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/sso/components/sso-settings.tsx`). Adapt it to the app's existing components, styling and data loading instead of copying it as is.
5. Check the result against the recipe and the block's docs: it typechecks, it reads the same block data, and it handles the loading and empty states the snippet handles.

Read the full code first:

- Recipe: https://bettersupabase.com/recipes/sso-settings.md
- Block docs: https://bettersupabase.com/docs/blocks/sso
```
