skills/azure-ad-sso/SKILL.md
Azure AD OAuth2/OIDC SSO integration for Kubernetes applications. Use when implementing Single Sign-On, configuring Azure AD App Registrations, restricting access by groups, or integrating tools (DefectDojo, Grafana, ArgoCD, Harbor, SonarQube) with Azure AD authentication.
npx skillsauth add julianobarbosa/claude-code-skills azure-ad-ssoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides comprehensive guidance for implementing Azure AD (Entra ID) OAuth2/OIDC Single Sign-On for applications deployed on Kubernetes clusters, including access restriction by Azure AD groups.
| Application | Provider | Redirect URI Pattern | Group Sync |
|-------------|----------|---------------------|------------|
| DefectDojo | azuread-tenant-oauth2 | /complete/azuread-tenant-oauth2/ | Yes |
| Grafana | azuread | /login/azuread | Yes |
| ArgoCD | microsoft (Dex) | /api/dex/callback | Yes |
| Harbor | oidc | /c/oidc/callback | Yes |
| SonarQube | saml or oidc | /oauth2/callback/saml | Yes |
| OAuth2 Proxy | azure | /oauth2/callback | Yes |
| Keycloak | oidc | /realms/{realm}/broker/azure/endpoint | Yes |
┌─────────────────────────────────────────────────────────────────┐
│ Access Control Decision │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Q: Who should access this application? │
│ │
│ ├─ Everyone in tenant ──► appRoleAssignmentRequired=false │
│ │ │
│ └─ Specific groups ────► appRoleAssignmentRequired=true │
│ + Assign groups to Enterprise App │
│ │
└─────────────────────────────────────────────────────────────────┘
# 1. Create App Registration
APP_NAME="<application>-<environment>"
REDIRECT_URI="https://<app-domain>/complete/<provider>/"
APP_ID=$(az ad app create \
--display-name "$APP_NAME" \
--sign-in-audience "AzureADMyOrg" \
--web-redirect-uris "$REDIRECT_URI" \
--query appId -o tsv)
echo "Application (client) ID: $APP_ID"
# 2. Get Tenant ID
TENANT_ID=$(az account show --query tenantId -o tsv)
echo "Directory (tenant) ID: $TENANT_ID"
# 3. Create Client Secret
SECRET=$(az ad app credential reset \
--id $APP_ID \
--append \
--years 1 \
--query password -o tsv)
echo "Client Secret: $SECRET" # Save immediately!
# Enable security group claims in tokens
az ad app update --id $APP_ID --set groupMembershipClaims=SecurityGroup
# Add Group.Read.All permission (delegated)
az ad app permission add \
--id $APP_ID \
--api 00000003-0000-0000-c000-000000000000 \
--api-permissions 5f8c59db-677d-491f-a6b8-5f174b11ec1d=Scope
# Grant admin consent
az ad app permission admin-consent --id $APP_ID
# Get Service Principal object ID
SP_ID=$(az ad sp list --filter "appId eq '$APP_ID'" --query "[0].id" -o tsv)
# Enable user assignment requirement
az ad sp update --id $SP_ID --set appRoleAssignmentRequired=true
# Get the group ID to restrict access
GROUP_ID=$(az ad group show --group "G-Usuarios-<App>-Admin" --query id -o tsv)
# Assign group to the application (only these users can login)
az rest --method POST \
--uri "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_ID/appRoleAssignments" \
--headers "Content-Type=application/json" \
--body "{
\"principalId\": \"$GROUP_ID\",
\"principalType\": \"Group\",
\"appRoleId\": \"00000000-0000-0000-0000-000000000000\",
\"resourceId\": \"$SP_ID\"
}"
az keyvault secret set \
--vault-name "<keyvault-name>" \
--name "<app>-azuread-client-secret" \
--value "$SECRET"
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: <app>-secrets
namespace: <namespace>
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: "<managed-identity-client-id>"
keyvaultName: "<keyvault-name>"
tenantId: "<azure-tenant-id>"
objects: |
array:
- |
objectName: <app>-azuread-client-secret
objectType: secret
objectAlias: AZURE_AD_CLIENT_SECRET
secretObjects:
- secretName: <app>-azure-ad
type: Opaque
data:
- objectName: AZURE_AD_CLIENT_SECRET
key: client-secret
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "<app>-secrets"
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets-store"
readOnly: true
# Enable SSO
extraEnv:
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_ENABLED
value: "True"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY
value: "<client-id>"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID
value: "<tenant-id>"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET
valueFrom:
secretKeyRef:
name: defectdojo
key: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET
# Group sync
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GET_GROUPS
value: "True"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS
value: "True"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GROUPS_FILTER
value: "^G-Usuarios-DefectDojo-.*"
# CRITICAL: For apps behind reverse proxy
- name: DD_SECURE_PROXY_SSL_HEADER
value: "True"
grafana.ini:
auth.azuread:
enabled: true
name: Azure AD
allow_sign_up: true
client_id: "<client-id>"
client_secret: "${GF_AUTH_AZUREAD_CLIENT_SECRET}"
scopes: openid email profile
auth_url: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
token_url: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
allowed_groups: "<admin-group-id> <viewer-group-id>"
role_attribute_path: contains(groups[*], '<admin-group-id>') && 'Admin' || 'Viewer'
configs:
cm:
dex.config: |
connectors:
- type: microsoft
id: microsoft
name: Azure AD
config:
clientID: "<client-id>"
clientSecret: $dex.azure.clientSecret
tenant: "<tenant-id>"
redirectURI: https://<argocd-domain>/api/dex/callback
groups:
- <admin-group-id>
rbac:
policy.csv: |
g, <admin-group-id>, role:admin
externalURL: https://harbor.<domain>
core:
oidc:
name: "azure"
endpoint: "https://login.microsoftonline.com/<tenant-id>/v2.0"
clientId: "<client-id>"
clientSecret: "<from-secret>"
scope: "openid,profile,email"
groupsClaim: "groups"
adminGroup: "<admin-group-id>"
autoOnboard: true
| Error Code | Description | Solution |
|------------|-------------|----------|
| AADSTS50011 | Reply URL mismatch | Verify exact redirect URI including trailing slash |
| AADSTS50105 | User not assigned | Add user/group to Enterprise App assignments |
| AADSTS700016 | App not found | Check client ID and tenant ID |
| AADSTS7000218 | Secret expired | Rotate secret in Key Vault, restart pods |
| AADSTS90102 | Invalid redirect_uri | Check DD_SECURE_PROXY_SSL_HEADER=True for reverse proxy |
| AADSTS65001 | Consent not granted | Run az ad app permission admin-consent |
Symptom: redirect_uri=https,%20https://...
Root cause: DD_SECURE_PROXY_SSL_HEADER set incorrectly
Fix:
- name: DD_SECURE_PROXY_SSL_HEADER
value: "True" # NOT "HTTP_X_FORWARDED_PROTO,https"
# Verify group claims enabled
az ad app show --id <app-id> --query groupMembershipClaims
# Check API permissions
az ad app permission list --id <app-id>
# Verify group exists and user is member
az ad group member check --group "<group-name>" --member-id "<user-object-id>"
# Check SecretProviderClass
kubectl describe secretproviderclass <name> -n <namespace>
# Check CSI driver pods
kubectl get pods -n kube-system | grep secrets-store
# Check managed identity access
az keyvault show --name <vault> --query properties.accessPolicies
# Test OAuth redirect
curl -sS -k -D - -o /dev/null "https://<app>/login/<provider>/" 2>&1 | grep -i location
# Check environment variables in pod
kubectl exec -n <ns> deploy/<app> -c <container> -- env | grep -i azure
# Decode JWT token (after login, from browser dev tools)
# Use https://jwt.io to decode and verify claims
appRoleAssignmentRequired=true| Environment | Key Vault | Managed Identity | Tenant ID |
|-------------|-----------|------------------|-----------|
| cafehyna-dev | kv-cafehyna-dev-hlg | f1a14a8f-6d38-40a0-a935-3cdd91a25f47 | 3f7a3df4-f85b-4ca8-98d0-08b1034e6567 |
| cafehyna-hub | kv-cafehyna-default | f1a14a8f-6d38-40a0-a935-3cdd91a25f47 | 3f7a3df4-f85b-4ca8-98d0-08b1034e6567 |
| cafehyna-prd | kv-cafehyna-prd | f1a14a8f-6d38-40a0-a935-3cdd91a25f47 | 3f7a3df4-f85b-4ca8-98d0-08b1034e6567 |
For complete implementation examples:
optionalClaims: groups: SecurityGroup AND set token version 2 in the manifest, or apps that match by name see nothing.https://graph.microsoft.com/.default scope grants admin-consented permissions only; User.Read.All on a delegated token still requires admin consent at first use.development
End-to-end branch delivery: commit (no AI attribution) → push → open a pull request → ensure a Board work item exists (create one per task, assigned to the configured user, if none) and link it → after merge, clean up branch and worktree. Auto-detects the platform from the remote — Azure Repos + Boards (azure-devops-node-api SDK; OAuth Bearer push fallback via `az`) or GitHub (Octokit; `gh` for auth). Scripts are TypeScript, run via `bun`. Use whenever asked to "ship", "ship it", "ship this branch", "open a PR", "push and open a PR", "raise a PR", "deliver this", "send this for review", or "create a PR and link the work item" — and when a direct push to main is blocked and the change needs to go through a PR instead.
testing
Brief description of what this skill does. Include specific triggers - when should Claude use this skill? Example triggers, file types, or keywords that indicate this skill applies.
tools
Manage and troubleshoot PATH configuration in zsh. Use when adding tools to PATH (bun, nvm, Python venv, cargo, go), diagnosing "command not found" errors, validating PATH entries, or organizing shell configuration in .zshrc and .zshrc.local files.
tools
Zabbix monitoring system automation via API and Python. Use when: (1) Managing hosts, templates, items, triggers, or host groups, (2) Automating monitoring configuration, (3) Sending data via Zabbix trapper/sender, (4) Querying historical data or events, (5) Bulk operations on Zabbix objects, (6) Maintenance window management, (7) User/permission management