---
title: "OAuth2 and OIDC for Infrastructure"
description: "Practical guide to OAuth2 and OIDC for infrastructure authentication using Keycloak, Dex, and OAuth2 Proxy with Kubernetes."
url: https://agent-zone.ai/knowledge/security/oauth2-oidc-infrastructure/
section: knowledge
date: 2026-02-21
categories: ["security"]
tags: ["oauth2","oidc","keycloak","dex","oauth2-proxy","kubernetes","sso","authentication"]
skills: ["identity-provider-configuration","kubernetes-oidc","sso-integration","oauth2-flow-selection"]
tools: ["keycloak","dex","oauth2-proxy","kubectl","kubelogin","helm"]
levels: ["intermediate"]
word_count: 1368
formats:
  json: https://agent-zone.ai/knowledge/security/oauth2-oidc-infrastructure/index.json
  html: https://agent-zone.ai/knowledge/security/oauth2-oidc-infrastructure/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=OAuth2+and+OIDC+for+Infrastructure
---


## OAuth2 vs OIDC: What Actually Matters

OAuth2 is an authorization framework. It answers the question "what is this client allowed to do?" by issuing access tokens. It does not tell you who the user is. OIDC (OpenID Connect) is a layer on top of OAuth2 that adds authentication. It answers "who is this user?" by adding an ID token -- a signed JWT containing user identity claims like email, name, and group memberships.

Most infrastructure tooling uses OIDC, not plain OAuth2. When you configure Kubernetes API server authentication, ArgoCD SSO, or Grafana login, you are using OIDC. The access token authorizes API calls. The ID token identifies the user so the system can make RBAC decisions.

```
OAuth2:  Client -> Authorization Server -> Access Token (opaque, for API access)
OIDC:    Client -> Authorization Server -> Access Token + ID Token (JWT with user claims)
```

The ID token is a JWT you can decode and inspect. It contains claims like `sub` (subject identifier), `email`, `groups`, and `preferred_username`. These claims drive authorization decisions downstream.

## OAuth2 Flows: Choosing the Right One

**Authorization Code flow** is the standard for web applications. The user is redirected to the identity provider, authenticates, and is redirected back with an authorization code. The backend exchanges that code for tokens. This keeps tokens out of the browser URL.

**Authorization Code with PKCE** extends the Authorization Code flow for public clients (SPAs, mobile apps, CLI tools). PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Use this for any client that cannot securely store a client secret.

**Client Credentials flow** is for service-to-service communication where no user is involved. The service authenticates directly with its client ID and client secret to get an access token. Use this for background jobs, CI pipelines, and automated tooling.

**Device Code flow** is for CLI tools and devices without a browser. The tool displays a URL and code, the user opens a browser on another device, enters the code, and authenticates. The CLI polls the authorization server until authentication completes.

**Never use the Implicit flow.** It was designed for SPAs before PKCE existed and returns tokens directly in the URL fragment. It is deprecated in OAuth 2.1 and insecure because tokens are exposed in browser history and server logs. Use Authorization Code with PKCE instead.

## OIDC for the Kubernetes API Server

Kubernetes does not have a built-in identity store. It delegates authentication to external providers via OIDC. Configure the API server with these flags:

```bash
kube-apiserver \
  --oidc-issuer-url=https://keycloak.example.com/realms/infrastructure \
  --oidc-client-id=kubernetes \
  --oidc-username-claim=email \
  --oidc-groups-claim=groups \
  --oidc-ca-file=/etc/kubernetes/pki/oidc-ca.pem
```

The `--oidc-username-claim` maps an ID token claim to the Kubernetes username. The `--oidc-groups-claim` maps a claim to Kubernetes groups. You then create RBAC ClusterRoleBindings that reference these groups:

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: platform-admins
subjects:
  - kind: Group
    name: platform-team
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
```

Users authenticate through the IdP, receive an ID token with `groups: ["platform-team"]`, and Kubernetes grants them the `cluster-admin` role.

## Keycloak: Full-Featured Identity Provider

Keycloak is an open-source identity and access management solution. It provides user management, SSO, user federation with LDAP and Active Directory, social login, and fine-grained authorization. Deploy it when you need a full identity provider that manages users directly or federates with existing directories.

Deploy on Kubernetes using the Bitnami Helm chart:

```bash
helm install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \
  --namespace identity --create-namespace \
  --set auth.adminUser=admin \
  --set auth.adminPassword=changeme \
  --set postgresql.enabled=true \
  --set ingress.enabled=true \
  --set ingress.hostname=keycloak.example.com
```

Key Keycloak concepts: **Realms** are isolated tenants -- create one realm per environment or team. **Clients** represent applications that authenticate against Keycloak -- each application (Kubernetes, ArgoCD, Grafana) gets its own client with specific redirect URIs. **User federation** connects Keycloak to LDAP or Active Directory so users authenticate with their existing corporate credentials.

Choose Keycloak when: you need a full identity provider, you need to manage users directly, you need LDAP/AD federation, you want a single SSO portal for all infrastructure tools.

## Dex: Lightweight OIDC Federation

Dex is a lightweight OIDC provider that acts as a gateway to upstream identity providers. It does not manage users itself. Instead, it uses connectors to authenticate against GitHub, GitLab, LDAP, SAML, and other identity sources, and presents a unified OIDC interface to downstream applications.

```yaml
# Dex configuration
issuer: https://dex.example.com
connectors:
  - type: github
    id: github
    name: GitHub
    config:
      clientID: $GITHUB_CLIENT_ID
      clientSecret: $GITHUB_CLIENT_SECRET
      orgs:
        - name: my-org
          teams:
            - platform-team
            - developers
  - type: ldap
    id: ldap
    name: Corporate LDAP
    config:
      host: ldap.internal:636
      rootCA: /etc/dex/ldap-ca.pem
      bindDN: cn=serviceaccount,dc=example,dc=com
      bindPW: $LDAP_BIND_PASSWORD
      userSearch:
        baseDN: ou=Users,dc=example,dc=com
        username: uid
        emailAttr: mail
      groupSearch:
        baseDN: ou=Groups,dc=example,dc=com
        userMatchers:
          - userAttr: DN
            groupAttr: member
        nameAttr: cn
staticClients:
  - id: kubernetes
    name: Kubernetes
    redirectURIs:
      - http://localhost:8000
    secret: kubernetes-client-secret
```

Choose Dex when: you already have identity sources (GitHub, GitLab, LDAP) and need a unified OIDC interface for Kubernetes, ArgoCD, or other tools. Dex is the default identity layer for ArgoCD.

## OAuth2 Proxy: SSO for Any Web Application

OAuth2 Proxy is a reverse proxy that authenticates users via OIDC before forwarding requests to the backend. Put it in front of any web application to add SSO without modifying the application itself.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
spec:
  template:
    spec:
      containers:
        - name: oauth2-proxy
          image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
          args:
            - --provider=oidc
            - --oidc-issuer-url=https://keycloak.example.com/realms/infrastructure
            - --client-id=dashboard
            - --client-secret=$(CLIENT_SECRET)
            - --cookie-secret=$(COOKIE_SECRET)
            - --email-domain=*
            - --upstream=http://kubernetes-dashboard.kubernetes-dashboard.svc:443
            - --pass-authorization-header=true
            - --http-address=0.0.0.0:4180
```

With Kubernetes ingress, use auth annotations to put OAuth2 Proxy in front of any service:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: protected-app
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "https://oauth2-proxy.example.com/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://oauth2-proxy.example.com/oauth2/start?rd=$scheme://$host$escaped_request_uri"
```

## Practical Integration Patterns

**Kubernetes Dashboard behind OAuth2 Proxy and Keycloak**: Deploy Keycloak as the IdP. Create a client in Keycloak for the dashboard. Deploy OAuth2 Proxy configured with that client. Point the dashboard ingress auth annotations at OAuth2 Proxy. Users authenticate via Keycloak, OAuth2 Proxy validates the token, and forwards the authenticated request to the dashboard.

**ArgoCD with Dex and GitHub Teams**: ArgoCD ships with Dex built in. Configure the Dex connector to use GitHub OAuth with org and team filters. Map GitHub teams to ArgoCD RBAC roles in the `argocd-rbac-cm` ConfigMap. Members of `my-org/platform-team` get admin access; members of `my-org/developers` get read-only.

**Grafana with OIDC**: Grafana has built-in OIDC support. Configure it in `grafana.ini` -- no proxy needed:

```ini
[auth.generic_oauth]
enabled = true
name = Keycloak
client_id = grafana
client_secret = grafana-secret
scopes = openid email profile
auth_url = https://keycloak.example.com/realms/infrastructure/protocol/openid-connect/auth
token_url = https://keycloak.example.com/realms/infrastructure/protocol/openid-connect/token
api_url = https://keycloak.example.com/realms/infrastructure/protocol/openid-connect/userinfo
role_attribute_path = contains(groups[*], 'grafana-admins') && 'Admin' || 'Viewer'
```

**kubectl with OIDC via kubelogin**: The `kubelogin` plugin enables browser-based authentication for kubectl. Install it as a kubectl plugin, then configure the kubeconfig:

```yaml
users:
  - name: oidc-user
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        command: kubectl
        args:
          - oidc-login
          - get-token
          - --oidc-issuer-url=https://keycloak.example.com/realms/infrastructure
          - --oidc-client-id=kubernetes
```

Running `kubectl get pods` opens a browser for authentication. After login, kubelogin caches the token and refreshes it automatically on expiry.

## Token Management

**Access token lifetime** should be short: 5 to 15 minutes. This limits the window of exposure if a token is leaked. The client uses the refresh token to get a new access token without requiring the user to log in again.

**Refresh token lifetime** is longer: hours to days depending on the security requirements. Refresh tokens should be rotated on use (the IdP issues a new refresh token each time one is used).

**Token storage**: never store tokens in `localStorage` -- any JavaScript on the page can read them (XSS attack vector). Use `httpOnly` cookies with the `Secure` and `SameSite` flags. OAuth2 Proxy handles this correctly by default.

## Common Gotchas

**Callback URL mismatch.** The redirect URI configured in the IdP client must exactly match the URL the application sends during the OAuth2 flow. A trailing slash, different port, or http vs https mismatch will cause a cryptic error. Always verify both sides match.

**Group claim not in token.** By default, many IdPs do not include group memberships in the ID token. In Keycloak, you must add a "groups" mapper to the client scope. In Dex, groups come from the connector configuration. If Kubernetes is not recognizing user groups, decode the JWT and verify the groups claim is present.

**Token expiry during long kubectl sessions.** Without kubelogin, a manually configured OIDC token in kubeconfig expires and kubectl fails silently or with obscure errors. Always use kubelogin for automatic token refresh. Configure it once in kubeconfig and never think about token management again.

