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

# User tokens

> Clusters call the Kubernetes API with each person's own SSO token, so your existing RBAC decides what they can do, as it does for kubectl.

With `auth.oidc.clusterAccess: token`, in-cluster Radar stops impersonating people. Every live Kubernetes call carries the person's own ID token, issued by your IdP to the hub's OIDC client. The API server authenticates that token and applies the RBAC you already have on the person's username and groups.

Use it when your clusters already trust your IdP for `kubectl` and you want Radar to grant exactly what `kubectl` grants. Otherwise, [Hub roles or IdP groups](/docs/cloud/self-hosted/auth#cluster-access-for-new-clusters) are simpler: they need no API server change.

| | Hub roles / IdP groups | User tokens |
| - | - | - |
| Who calls the API server | Radar's ServiceAccount, impersonating the person | The person's own ID token |
| Who decides access | Chart bindings, or your bindings on `radar:idp:<group>` | Your bindings on the person's OIDC username and groups |
| API server change | None | Must accept ID tokens issued to the hub's client |
| Personal access tokens | Work | Can't make live cluster calls: they carry no token |

## Requirements

* Radar Hub 1.12.0 or newer, and Radar 1.18.0 or newer on every cluster.
* OIDC sign-in, with `useRefreshToken: true`. Okta and Entra also need `offline_access` in `scopes`, or sessions end with the first ID token.
* `hub.publicURL` on `https` with a certificate the clusters can verify: not `web.tls.selfSigned`, and not `localCluster`. Radar only accepts tokens over a verified tunnel.
* Every API server must accept ID tokens from your issuer with the hub's client ID as the audience. The simplest way: sign the hub in with the same OIDC client `kubectl` already uses.

## 1. Set up the IdP app

**Microsoft Entra ID**

1. Azure Portal → **Microsoft Entra ID** → **App registrations**. Use the app your clusters already trust, or create one.
2. **Authentication** → add a **Web** platform with the redirect URI `https://<hub host>/api/auth/oidc/callback`.
3. **Certificates & secrets** → issue a client secret.
4. **Token configuration**:
   * **Add groups claim** → pick the group types, and tick **ID** and **Access**. Groups arrive as object IDs. Pick **Groups assigned to the application** if users belong to many groups: past 200 groups, Entra leaves the claim out of the token.

     To get group names instead of IDs (cloud-only groups), pick **Groups assigned to the application**, assign the groups under **Enterprise applications → Users and groups**, and set the groups claim to emit `cloud_displayname` (Graph or the app manifest; the portal doesn't offer it).
   * **Add optional claim** → **ID** → `email`. The username is built from this claim, and users without a mail address can't sign in to clusters.
5. **API permissions**: `openid`, `profile`, `email` and `offline_access` (Microsoft Graph, delegated).

Never add a `groups` scope: Entra rejects it with `AADSTS70011` and sign-in fails.

**Other IdPs**

Any OIDC provider your API servers can trust works. The hub's client needs:

* the redirect URI `https://<hub host>/api/auth/oidc/callback`
* the Authorization Code and Refresh Token grants
* an `email` claim and, for group-based RBAC, a groups claim in the ID token

Google Workspace emits no groups claim, so only per-user bindings work with it.

## 2. Configure the API servers

Point each API server at the same issuer and client ID. The username must come from the `email` claim: the hub sends Radar `<username prefix><email>`, so Radar's checks on cached reads match the API server only when both name people the same way.

```bash theme={null}
--oidc-issuer-url=https://login.microsoftonline.com/<tenant-id>/v2.0
--oidc-client-id=<hub client ID>
--oidc-username-claim=email
--oidc-username-prefix=oidc:
--oidc-groups-claim=groups
--oidc-groups-prefix=oidc:
```

On EKS, associate an OIDC identity provider with the same values:

```bash theme={null}
aws eks associate-identity-provider-config --cluster-name <cluster> --oidc \
  identityProviderConfigName=radar,issuerUrl=https://login.microsoftonline.com/<tenant-id>/v2.0,clientId=<hub client ID>,usernameClaim=email,usernamePrefix=oidc:,groupsClaim=groups,groupsPrefix=oidc:
```

Managed integrations that only accept tokens for their own app don't work here. AKS with Microsoft Entra integration is one: its API server expects tokens for the AKS server app, not for the hub's app.

> [!WARNING]
>
> Set a groups prefix. Without one, an IdP group named `system:masters` reaches the API server as that group. The hub logs a warning at startup when `k8sGroupsPrefix` is empty.

## 3. Configure the hub

Add these values to a `helm upgrade --reset-then-reuse-values` of the hub, on top of the OIDC values from **Settings → Organization → SSO**:

```yaml theme={null}
auth:
  oidc:
    groupsClaim: groups
    useRefreshToken: true
    scopes: ["profile", "offline_access"]   # Entra: never "groups"

    clusterAccess: token
    # The API servers' --oidc-username-prefix and --oidc-groups-prefix.
    # If the API server uses no prefix (flag unset, or "-"), leave the value empty.
    k8sUsernamePrefix: "oidc:"
    k8sGroupsPrefix: "oidc:"
```

| Value | Env var | Notes |
| - | - | - |
| `auth.oidc.clusterAccess` | `RADAR_HUB_OIDC_CLUSTER_ACCESS` | `token`. The hub refuses to start without OIDC or `useRefreshToken`, or with WorkOS configured. |
| `auth.oidc.k8sUsernamePrefix` | `RADAR_HUB_OIDC_K8S_USERNAME_PREFIX` | Must equal the API server's username prefix. |
| `auth.oidc.k8sGroupsPrefix` | `RADAR_HUB_OIDC_K8S_GROUPS_PREFIX` | Must equal the API server's groups prefix. |

Neither prefix may be `-` or start with `radar:`, `cloud:` or `system:`.

**Settings → Organization → SSO** then shows **Cluster access: User tokens**, read-only.

## 4. Bind people in Kubernetes

Radar adds no role bindings in this mode. Bind the names the API server sees, as you would for `kubectl`:

```yaml theme={null}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: platform-team-view
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: oidc:<group object ID>
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: oidc:alice@example.com
```

## 5. Check a person's identity

Sign in with the hub's client, then ask the API server who you are:

```bash theme={null}
kubectl oidc-login get-token \
  --oidc-issuer-url=https://login.microsoftonline.com/<tenant-id>/v2.0 \
  --oidc-client-id=<hub client ID> --oidc-client-secret=<client secret> \
  --oidc-extra-scope=email,profile \
  | jq -r .status.token > /tmp/id-token

kubectl auth whoami --token="$(cat /tmp/id-token)"
```

`kubectl oidc-login` is [kubelogin](https://github.com/int128/kubelogin). It listens on `http://localhost:8000`, so add that redirect URI to the app or pass a port your app already allows. On Entra, add it under the **Web** platform: kubelogin sends the client secret, which Entra refuses on a public-client redirect.

```text theme={null}
ATTRIBUTE   VALUE
Username    oidc:alice@example.com
Groups      [oidc:<group object ID> system:authenticated]
```

New Entra users and group memberships can take a few minutes to reach tokens. If `email` or `groups` is missing, wait and sign in again.

The username must be `<k8sUsernamePrefix><email>` and the groups `<k8sGroupsPrefix><group>`. If `whoami` returns `Unauthorized`, the API server doesn't trust the token: check the issuer and client ID. If the names differ, fix the prefixes on the hub or the API server so they match.

## 6. Connect clusters

* **New clusters**: the connect page installs Radar 1.18.0 or newer with `auth.kubernetesCredentials=user-token` and `cloud.defaultRbac.create=false`.
* **Existing clusters**: the hub refuses to open a cluster whose Radar still impersonates, until it's switched. **Settings → Organization → SSO → Switch clusters you already connected** has the command for each cluster:

  ```bash theme={null}
  helm upgrade radar skyhook/radar -n radar --version '>=1.18.0' \
    --reset-then-reuse-values \
    --set-string image.tag="" \
    --set cloud.defaultRbac.create=false \
    --set auth.kubernetesCredentials=user-token
  ```

In this mode the cluster's ServiceAccount loses its impersonate permission, and `rbac.podExec` and `rbac.portForward` are ignored: shells and port-forwards run on the person's own token.

## AI agents and MCP clients

Personal access tokens can't make live cluster calls in this mode. MCP clients that sign in with your IdP need the hub to exchange their access token for one the API servers trust: set `auth.oidc.mcpOAuth.tokenExchange`. See [MCP clients](/docs/cloud/self-hosted/mcp-clients#clusters-on-user-tokens).

## Troubleshooting

| Symptom | Cause |
| - | - |
| Hub doesn't start: `token needs OIDC configured, WorkOS not configured, and auth.oidc.useRefreshToken ... on` | Turn on `useRefreshToken`, and make sure OIDC is fully configured. |
| Hub doesn't start: `RADAR_HUB_OIDC_K8S_*_PREFIX` | A prefix is `-`, or starts with `radar:`, `cloud:` or `system:`. |
| A cluster won't open and asks for a switch (`user_token_unsupported`) | Its Radar still impersonates. Run the switch command above. |
| Everything is forbidden, but `kubectl` works for the same person | The hub prefixes don't match the API server's, or the binding names a different username. Compare with `kubectl auth whoami`. |
| Sessions end after about an hour | No refresh token. Add `offline_access` to `scopes` and allow the Refresh Token grant. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.