<< All versions
Skill v1.0.1
currentAutomated scan100/100julianobarbosa/claude-code-skills/azure-ad-sso
+4 new
──Details
PublishedJuly 20, 2026 at 06:31 AM
Content Hashsha256:0afbdb4e62f8f1d4...
Git SHA7a1fc30bbb3d
Bump Typepatch
──Files
Files (1 file, 11.6 KB)
SKILL.md11.6 KBactive
SKILL.md · 360 lines · 11.6 KB
version: "1.0.1" name: azure-ad-sso description: 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.
Azure AD SSO Integration Skill
Overview
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.
Quick Reference
Supported Applications
| 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 |
Authentication Flow Decision
┌─────────────────────────────────────────────────────────────────┐│ Access Control Decision │├─────────────────────────────────────────────────────────────────┤│ ││ Q: Who should access this application? ││ ││ ├─ Everyone in tenant ──► appRoleAssignmentRequired=false ││ │ ││ └─ Specific groups ────► appRoleAssignmentRequired=true ││ + Assign groups to Enterprise App ││ │└─────────────────────────────────────────────────────────────────┘
Implementation Workflow
Phase 1: Azure AD App Registration
bash
# 1. Create App RegistrationAPP_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 IDTENANT_ID=$(az account show --query tenantId -o tsv)echo "Directory (tenant) ID: $TENANT_ID"# 3. Create Client SecretSECRET=$(az ad app credential reset \--id $APP_ID \--append \--years 1 \--query password -o tsv)echo "Client Secret: $SECRET" # Save immediately!
Phase 2: Enable Group Claims
bash
# Enable security group claims in tokensaz 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 consentaz ad app permission admin-consent --id $APP_ID
Phase 3: Restrict Access by Group (CRITICAL)
bash
# Get Service Principal object IDSP_ID=$(az ad sp list --filter "appId eq '$APP_ID'" --query "[0].id" -o tsv)# Enable user assignment requirementaz ad sp update --id $SP_ID --set appRoleAssignmentRequired=true# Get the group ID to restrict accessGROUP_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\"}"
Phase 4: Store Secret in Key Vault
bash
az keyvault secret set \--vault-name "<keyvault-name>" \--name "<app>-azuread-client-secret" \--value "$SECRET"
Secret Management
SecretProviderClass Template
yaml
apiVersion: secrets-store.csi.x-k8s.io/v1kind: SecretProviderClassmetadata:name: <app>-secretsnamespace: <namespace>spec:provider: azureparameters:usePodIdentity: "false"useVMManagedIdentity: "true"userAssignedIdentityID: "<managed-identity-client-id>"keyvaultName: "<keyvault-name>"tenantId: "<azure-tenant-id>"objects: |array:- |objectName: <app>-azuread-client-secretobjectType: secretobjectAlias: AZURE_AD_CLIENT_SECRETsecretObjects:- secretName: <app>-azure-adtype: Opaquedata:- objectName: AZURE_AD_CLIENT_SECRETkey: client-secret
Pod Volume Mount
yaml
volumes:- name: secrets-storecsi:driver: secrets-store.csi.k8s.ioreadOnly: truevolumeAttributes:secretProviderClass: "<app>-secrets"volumeMounts:- name: secrets-storemountPath: "/mnt/secrets-store"readOnly: true
Application Configurations
DefectDojo
yaml
# Enable SSOextraEnv:- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_ENABLEDvalue: "True"- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEYvalue: "<client-id>"- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_IDvalue: "<tenant-id>"- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRETvalueFrom:secretKeyRef:name: defectdojokey: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET# Group sync- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GET_GROUPSvalue: "True"- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPSvalue: "True"- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GROUPS_FILTERvalue: "^G-Usuarios-DefectDojo-.*"# CRITICAL: For apps behind reverse proxy- name: DD_SECURE_PROXY_SSL_HEADERvalue: "True"
Grafana
yaml
grafana.ini:auth.azuread:enabled: truename: Azure ADallow_sign_up: trueclient_id: "<client-id>"client_secret: "${GF_AUTH_AZUREAD_CLIENT_SECRET}"scopes: openid email profileauth_url: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorizetoken_url: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/tokenallowed_groups: "<admin-group-id> <viewer-group-id>"role_attribute_path: contains(groups[*], '<admin-group-id>') && 'Admin' || 'Viewer'
ArgoCD (via Dex)
yaml
configs:cm:dex.config: |connectors:- type: microsoftid: microsoftname: Azure ADconfig:clientID: "<client-id>"clientSecret: $dex.azure.clientSecrettenant: "<tenant-id>"redirectURI: https://<argocd-domain>/api/dex/callbackgroups:- <admin-group-id>rbac:policy.csv: |g, <admin-group-id>, role:admin
Harbor
yaml
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
Troubleshooting
Error Reference
| 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 |
Common Issues
Malformed redirect_uri (Django apps behind proxy)
Symptom: redirect_uri=https,%20https://...
Root cause: DD_SECURE_PROXY_SSL_HEADER set incorrectly
Fix:
yaml
- name: DD_SECURE_PROXY_SSL_HEADERvalue: "True" # NOT "HTTP_X_FORWARDED_PROTO,https"
Groups not syncing
bash
# Verify group claims enabledaz ad app show --id <app-id> --query groupMembershipClaims# Check API permissionsaz ad app permission list --id <app-id># Verify group exists and user is memberaz ad group member check --group "<group-name>" --member-id "<user-object-id>"
Secret not syncing from Key Vault
bash
# Check SecretProviderClasskubectl describe secretproviderclass <name> -n <namespace># Check CSI driver podskubectl get pods -n kube-system | grep secrets-store# Check managed identity accessaz keyvault show --name <vault> --query properties.accessPolicies
Diagnostic Commands
bash
# Test OAuth redirectcurl -sS -k -D - -o /dev/null "https://<app>/login/<provider>/" 2>&1 | grep -i location# Check environment variables in podkubectl 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
Security Best Practices
- Never hardcode secrets - Always use Key Vault + CSI Driver
- Use managed identities - Avoid service principal credentials
- Restrict access by group - Enable
appRoleAssignmentRequired=true - Rotate secrets - Set calendar reminders before expiration
- Use HTTPS only - All redirect URIs must use HTTPS
- Single tenant - Never use multi-tenant for internal apps
- Audit logging - Enable Azure AD sign-in logs
Environment Reference
| Environment | Key Vault | Managed Identity | Tenant ID | |
|---|---|---|---|---|
| example-app-dev | kv-example-dev-hlg | <WORKLOAD_IDENTITY_CLIENT_ID> | <TENANT_ID> | |
| example-app-hub | kv-example-default | <WORKLOAD_IDENTITY_CLIENT_ID> | <TENANT_ID> | |
| example-app-prd | kv-example-prd | <WORKLOAD_IDENTITY_CLIENT_ID> | <TENANT_ID> |
Detailed Reference
For complete implementation examples:
- [references/azure-ad-sso-guide.md](references/azure-ad-sso-guide.md) - Full guide with manifests
- [references/app-configs.md](references/app-configs.md) - Application-specific configurations
- [references/troubleshooting.md](references/troubleshooting.md) - Extended troubleshooting guide
Gotchas
- Group claims default to objectId, not name — enable
optionalClaims: groups: SecurityGroupAND set token version 2 in the manifest, or apps that match by name see nothing. - Token scopes:
https://graph.microsoft.com/.defaultscope grants admin-consented permissions only;User.Read.Allon a delegated token still requires admin consent at first use. - OIDC redirect URIs require EXACT match — trailing slash mismatch returns AADSTS50011 with a vague error that looks like a network issue.
- Group-claims overage at 200 groups: users in >200 groups get NO group claims (overage). Switch to Graph query for membership lookup.
- App-token lifetime policies are per-app — default 1 hour. Long batch jobs hitting hour-2 silently 401 unless you wrap every call with refresh-on-401.