KubernetesLVII · AuthenticationAuthentication
OIDC — corporate identity for human users
What you'll learn
- Configure OIDC integration for the API server
- Map IdP claims to Kubernetes UserInfo (username, groups)
- Set up `kubectl` with OIDC for human users
- Diagnose common OIDC failure modes (token not refreshed, claim mapping wrong)
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
OIDC (OpenID Connect) is the right authentication method
for human users of a Kubernetes cluster. The API server
delegates authentication to a corporate IdP (Okta,
Azure AD, Google Workspace, Keycloak, etc.); the IdP
issues an ID token; kubectl presents the token as a
bearer token to the API server. The cluster never sees
the user’s password, the token is short-lived (1 hour by
default), and the IdP’s audit log records every cluster
access. This lesson covers the configuration, the
kubeconfig flow, and the failure modes.
How OIDC works
The user runs kubectl with OIDC. kubectl opens a
browser to the IdP’s login page; the user
authenticates (password + MFA); the IdP redirects
kubectl to a callback URL with an authorization code;
kubectl exchanges the code for an ID token; kubectl
presents the ID token as a bearer token to the API
server; the API server validates the token’s signature,
issuer, audience, and expiry against the IdP’s metadata.
sequenceDiagram
participant U as User (kubectl)
participant IDP as IdP (Okta, AAD)
participant AS as API server
U->>IDP: Browser login + MFA
IDP->>U: Authorization code
U->>IDP: Code → ID token
IDP->>U: ID token (JWT)
U->>AS: GET /api (Authorization: Bearer ID_JWT)
AS->>IDP: Fetch JWKS for signature verification
AS->>AS: Verify issuer, audience, expiry, claims
AS->>U: 200 OK (RBAC applied)
The ID token is a JWT signed by the IdP with a key
published at the issuer’s .well-known/openid-configuration
URL. The API server fetches the JWKS (JSON Web Key Set)
and verifies the signature; it then reads the claims
and maps them to UserInfo.
Configuration
The minimum OIDC configuration:
# AuthenticationConfiguration (mounted into kube-apiserver)
apiVersion: apiserver.config.k8s.io/v1
kind: AuthenticationConfiguration
jwt:
- issuer:
url: https://idp.example.com
audiences:
- kubernetes
claimMappings:
username:
claim: email
prefix: ""
groups:
claim: groups
prefix: "idp:"
userValidationRules:
- expression: 'user.groups.exists(g, g.startsWith("idp:"))'
message: "user must belong to an idp: group"
The claim mappings:
username.claim: email— theemailclaim becomes the username (alice@example.com).groups.claim: groups— thegroupsclaim is split into individual groups, each prefixed withidp:.- The
userValidationRulesrequire that the user belong to anidp:group; users without groups are rejected.
The prefix is critical: it prevents a user whose IdP
groups contain system:masters from impersonating a
Kubernetes built-in group. Without the prefix, a
malicious IdP group would be honoured; with the prefix,
Kubernetes groups are namespaced under the IdP.
kubeconfig for OIDC
apiVersion: v1
kind: Config
clusters:
- name: prod
cluster:
server: https://api.prod.example.com:6443
certificate-authority: /home/alice/.kube/ca.crt
contexts:
- name: alice-prod
context:
user: alice
cluster: prod
users:
- name: alice
user:
auth-provider:
name: oidc
config:
idp-issuer-url: https://idp.example.com
client-id: kubernetes-cli
client-secret: <redacted> # optional, for confidential clients
id-token: <id_token> # populated by kubectl
refresh-token: <refresh> # for offline access
idp-certificate-authority: /home/alice/.kube/idp-ca.crt
The auth-provider block tells kubectl to use the
built-in OIDC flow: open the browser, authenticate with
the IdP, receive the ID token, cache it in
~/.kube/cache/, and refresh it as it approaches
expiry.
# First use opens the browser
kubectl --context=alice-prod get pods
# Subsequent uses use the cached ID token
kubectl --context=alice-prod get pods # uses cached token
Claim mappings
The API server maps IdP claims to UserInfo:
| IdP claim | UserInfo | Notes |
|---|---|---|
email | username: alice@example.com | username.claim |
groups | groups: [idp:dev, idp:oncall] | groups.claim |
sub | (used as fallback) | Subject identifier |
The IdP’s groups are mapped to Kubernetes groups with
the configured prefix. A user in the IdP’s
prod-admins group is in idp:prod-admins in
Kubernetes. RBAC bindings must reference the prefixed
group:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prod-admins
subjects:
- kind: Group
name: idp:prod-admins
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
Token refresh and offline access
The OIDC flow has two variants:
- Authorization Code with PKCE (default) —
kubectlopens the browser, exchanges the code for a token, caches the token and the refresh token, and uses the refresh token to obtain a new ID token before the old one expires. The user is prompted again when the refresh token expires (typically every 24 hours). - Device Code Flow — for headless machines,
kubectlprints a URL and a code; the user opens the URL on any device, enters the code, and authenticates. The token is returned tokubectlvia polling.
CI/CD pipelines that need offline access use the device code flow with a refresh token persisted to disk. The refresh token has a long lifetime (days to weeks) but is revocable via the IdP.
Common failure modes
- Wrong claim mapping. The API server expects
emailbut the IdP sendspreferred_username. The username is empty or wrong. The fix is to match the claim name to the IdP’s actual claim. - Audience mismatch. The IdP issues a token with
aud=kubernetes, but the API server is configured foraud=k8s. The token is rejected. The fix is to match the audience. - Clock skew. The API server’s clock is 5 minutes
behind the IdP’s clock. The token is not yet valid
(
nbf). The fix is NTP. - Missing refresh token. A user with no refresh token (e.g., a confidential client with no offline access) is prompted every hour. The fix is to grant offline access in the IdP.
- JWKS endpoint unreachable. The API server cannot fetch the IdP’s signing keys. The token cannot be verified. The fix is to allow the API server to reach the IdP.
Production failure modes
- OIDC configured without RBAC bound to IdP groups. A user can authenticate but cannot do anything; the team has shipped OIDC but not the RBAC. The fix is to bind the IdP groups to appropriate Roles.
- Single IdP, no fallback. The IdP is unreachable; the entire cluster is unreachable. The fix is to keep a break-glass admin (OIDC + client cert).
- No claim validation. A user without any group
is authenticated and granted whatever RBAC the
system:authenticatedgroup has. The fix isuserValidationRulesthat require a specific group. - Refresh token stored in plaintext. A
.kube/cache/directory that is world-readable exposes refresh tokens. The fix ischmod 700.
Cross-course references
- The Linux course covers JWT and JWKS — the underlying primitives that OIDC uses.
- The OPNsense course covers the network-level access controls that complement IdP authentication.
Quiz
Knowledge check · 4 questions
Q1. Why is the `prefix` field on the OIDC `groups` claim mapping a critical security control?
Q2. An OIDC integration that authenticates a user but does not validate that the user belongs to any IdP group will reject the user with `unauthorized` because Kubernetes requires at least one group.
Q3. Your team configured OIDC with `--oidc-username-claim=email`, but the IdP sends `preferred_username`. Users authenticate but the username is empty. `kubectl auth whoami` returns `<unknown>`. What went wrong, and how do you fix it?
The cluster is configured with `claimMappings.username.claim: email`. The IdP issues tokens with both `email` and `preferred_username`. The audit log shows `user.username: ""` for every request. RBAC bindings reference `alice@example.com`.
Q4. Describe the OIDC authentication flow from `kubectl` to the API server in five steps.
Passing score: 75%. Answers are checked in this browser.
Production discipline
OIDC is the right authentication primitive for human
users of a Kubernetes cluster. Every production cluster
should be on OIDC; client certs and --token-auth-file
are reserved for nodes and bootstrap. The configuration
must use a prefix on the group claim, must validate
that users belong to a known group, must be backed by
RBAC bindings on the prefixed group names, and must
have a break-glass admin path (OIDC + client cert) for
the IdP-is-down scenario. The audit log is the proof of
OIDC’s value: every cluster access is traced to an IdP
record, which is the operational benefit the static
token file can never provide.