> ## Documentation Index
> Fetch the complete documentation index at: https://metoro.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Keycloak OIDC Setup

> Create the Keycloak realm, client, groups, group claim, client Secret, and values required for OIDC login

export const usePlannerSelections = () => {
  const [selections, setSelections] = useState(defaultSelections);
  useEffect(() => {
    setSelections(readSelections());
    const handleChange = event => {
      setSelections({
        ...defaultSelections,
        ...event.detail
      });
    };
    window.addEventListener(eventName, handleChange);
    return () => window.removeEventListener(eventName, handleChange);
  }, []);
  return [selections, setSelections];
};

export const storageKey = "metoro-onprem-installation-planner-v1";

export const readSelections = () => {
  if (typeof window === "undefined") {
    return defaultSelections;
  }
  try {
    const stored = window.localStorage.getItem(storageKey);
    if (!stored) {
      return defaultSelections;
    }
    return {
      ...defaultSelections,
      ...JSON.parse(stored)
    };
  } catch {
    return defaultSelections;
  }
};

export const oidcCallbackUrl = selections => {
  return `${deploymentUrl(selections)}/api/v1/auth/oidc/callback`;
};

export const eventName = "metoro-onprem-installation-planner-change";

export const deploymentUrl = selections => {
  return `${deploymentScheme(selections)}://${deploymentHostname(selections)}`;
};

export const deploymentScheme = selections => {
  return selections.urlScheme === "http" ? "http" : "https";
};

export const deploymentHostname = selections => {
  return cleanHostname(selections.deploymentHostname) || "CHANGE_ME_METORO_HOSTNAME";
};

export const defaultSelections = {
  topology: "dedicated",
  network: "sharedHost",
  urlScheme: "https",
  tls: "publicCa",
  authentication: "oidcGroups",
  dependencies: "bundled",
  clickhouseStorage: "hotPvcObjectStorage",
  deploymentHostname: "",
  ingesterHostname: ""
};

export const cleanHostname = value => {
  return String(value || "").trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "");
};

export const KeycloakValuesSnippet = () => {
  const [selections] = usePlannerSelections();
  const values = ["auth:", "  oidc:", "    enabled: true", "    displayName: Keycloak", '    issuerUrl: "https://CHANGE_ME_KEYCLOAK_HOST/realms/CHANGE_ME_REALM"', "    clientId: metoro", "    clientSecretName: metoro-oidc-client-secret", "    organizationUUID: default-on-prem", `    redirectUrl: "${oidcCallbackUrl(selections)}"`, '    scopes: "openid profile email groups"', "    groupRoleMappings:", '      - oidcGroup: "/metoro-admins"', '        metoroRole: "default-metoro-admin"', '      - oidcGroup: "/metoro-users"', '        metoroRole: "default-metoro-user"'].join("\n");
  return <pre>
      <code>{values}</code>
    </pre>;
};

export const KeycloakRedirectFields = () => {
  const [selections] = usePlannerSelections();
  const url = deploymentUrl(selections);
  const fields = [`Valid redirect URIs:       ${oidcCallbackUrl(selections)}`, `Web origins:               ${url}`, `Root URL:                  ${url}`, `Home URL:                  ${url}`, `Valid post logout redirect URIs: ${url}/*`].join("\n");
  return <pre>
      <code>{fields}</code>
    </pre>;
};

export const KeycloakOidcInputs = () => {
  const [selections] = usePlannerSelections();
  const url = deploymentUrl(selections);
  const inputs = [`Metoro deployment URL: ${url}`, "Keycloak issuer URL:   https://CHANGE_ME_KEYCLOAK_HOST/realms/CHANGE_ME_REALM", `OIDC callback URL:     ${oidcCallbackUrl(selections)}`, "Client ID:             metoro", "Admin group:           /metoro-admins", "User group:            /metoro-users"].join("\n");
  return <pre>
      <code>{inputs}</code>
    </pre>;
};

Configure Keycloak as the identity provider for Metoro. The final configuration should include a confidential Metoro client, a `groups` claim that emits full group paths, administrator and user groups, and a Kubernetes Secret containing the client secret.

## Inputs

Choose these values before configuring Keycloak:

<KeycloakOidcInputs />

Use `deploymentUrl` from the hub values file. It already includes the scheme, so it can be `https://...` or `http://...`. The Keycloak issuer URL must be reachable from browsers and from hub pods. The callback URL must match the redirect URI configured on the Keycloak client exactly.

## Choose The Realm

Choose the Keycloak realm that will own Metoro access. The realm name becomes the final path segment in the issuer URL: `https://<keycloak-host>/realms/<realm>`.

Disable open user registration unless your organization deliberately allows users to create their own Keycloak accounts. Password reset can be enabled if Keycloak is responsible for local user credentials.

## Create Groups

Create two Keycloak groups:

```text theme={null}
metoro-admins
metoro-users
```

When the group mapper is configured with full group paths, Keycloak emits these as `/metoro-admins` and `/metoro-users`. Those emitted values are what the hub role mappings should use.

Add at least one operator to `metoro-admins` before the first login test.

## Create The Client

Create a Keycloak client for Metoro.

Use these client settings:

```text theme={null}
Client ID: metoro
Name: Metoro
Client Type / Protocol: OpenID Connect
Enabled: On
Client authentication: On
Standard flow: On
Implicit flow: Off
Direct access grants: Off
Service accounts roles: Off
Client authenticator: Client ID and Secret
Full scope allowed: On
```

Set the redirect and origin fields from the deployment URL:

<KeycloakRedirectFields />

If the same hub is reachable through multiple browser-facing hostnames, add each callback URL to `Valid redirect URIs` and each logout origin to `Valid post logout redirect URIs`. The redirect URL in the hub values file should still match the primary `deploymentUrl`.

After saving the client, open the client credentials tab and copy the generated client secret.

## Expose The Groups Claim

Configure Keycloak so the hub receives group membership in the token and userinfo response. The hub values request `openid profile email groups`, so the Keycloak client must be allowed to request the `groups` scope and Keycloak must emit a token claim named `groups`.

Use a shared `groups` client scope when you want the setup to match the requested `groups` OIDC scope.

1. In Keycloak, open **Client scopes**.
2. Open the existing `groups` client scope, or create one with protocol `OpenID Connect`.
3. Open **Mappers**.
4. Click **Add mapper**, then **By configuration**.
5. Select **Group Membership**.
6. Save the mapper with these settings:

```text theme={null}
Name: groups
Mapper type: Group Membership
Token claim name: groups
Full group path: On
Add to ID token: On
Add to access token: On
Add to userinfo: On
Add to lightweight access token: On, if your Keycloak version shows this option
```

Then assign the `groups` client scope to the `metoro` client:

1. Open **Clients**.
2. Select the `metoro` client.
3. Open **Client scopes**.
4. Click **Add client scope**.
5. Select `groups`.
6. Add it as a **Default** client scope.

A typical client should have these default scopes:

```text theme={null}
web-origins
roles
profile
groups
basic
email
```

The hub values request `openid profile email groups`; Keycloak should then include a `groups` claim containing full group paths such as `/metoro-admins`.

If you only want this mapper on the `metoro` client, add it through the client's dedicated scope instead of a shared `groups` scope:

1. Open **Clients**.
2. Select the `metoro` client.
3. Open **Client scopes**.
4. Select the dedicated scope, usually named `metoro-dedicated`.
5. Open **Mappers**.
6. Click **Add mapper**, then **By configuration**.
7. Select **Group Membership** and use the same mapper settings above.

Keep the `groups` client scope assigned to the client when the hub requests the `groups` scope. The mapper can live on the dedicated scope, but the requested OIDC scope still needs to be accepted by the client.
