# Securely login to Argocd via web UI with SSO Azure Active Directory

## Introduction

In modern DevOps environments, secure and centralized access control is critical — especially when managing production-grade Kubernetes clusters with GitOps tools like Argo CD. Rather than relying on local user accounts or static passwords, integrating **Single Sign-On (SSO)** using enterprise identity providers like **Azure Active Directory (Azure AD)** ensures that authentication is secure, scalable, and aligned with your organization’s identity governance policies.

In this article, we’ll walk through how to configure **Argo CD to authenticate users via Azure AD using OpenID Connect (OIDC)**, enabling seamless and secure access through the web UI. By the end of this guide, you'll have a setup that supports **workload identity**, **group-based role access**, and a flexible RBAC model that maps Azure AD groups to Argo CD roles — allowing you to manage access across multiple teams efficiently and securely.

Whether you're a platform engineer, SRE, or DevOps lead looking to enforce identity-based access control across your GitOps workflow, this guide will give you a production-ready foundation.

## 🔧 Prerequisites

Before configuring Argo CD with Azure Active Directory (AAD) SSO via the web UI, ensure the following are in place:

✅ **Azure Subscription**: Active subscription with **Contributor** (or higher) access to manage AKS, AAD, and networking. [Sign up](https://azure.microsoft.com/free).

🏗️ **Private AKS Cluster**: Deployed via **Terraform Cloud**, with **user-assigned managed identity** and running behind firewall with private enspoints. Accessible via `kubectl` in the jumpbox within the nework.

🛡️ **Istio Internal Ingress**: **Istio** installed with an **internal ingress gateway** to restrict public access and allow access within the network or via a jumpbox/VPN.

🛠️ **Azure CLI**: Version **2.30+**, authenticated with `az login`. Used for managing Azure AD apps and AKS.

🖥️ **kubectl**: Configured to access the AKS cluster. Test with `kubectl get nodes`.

📦 **Helm 3.x**: Required to deploy **Argo CD**, **Istio**, and **cert-manager**.

🔐 **Workload Identity**: Enabled for **secretless authentication** to Azure resources from Kubernetes workloads.

🔒 **Cert-Manager** *(optional)*: For automated TLS via Let’s Encrypt or internal CA. Cert-Manager Docs. but i will use `openssl` because `let’sencrypt` does not authenticate `ACME challenge` in internal `private dns zone`

🌐 **Domain & Private DNS zone** : And point `private dns name` to the internal `AKS ISTIO ingress IP` (e.g., [`https://argocd.terranetesprivate.com`](https://argocd.terranetesprivate.com)).

🔑 **App registration**: used to configure **AAD SSO/OIDC** securely in `argocd`.

📚 **Knowledge Required**:

* Basic understanding of **Istio Gateway**, **VirtualService**, and **AAD SSO/OIDC**
    
* Familiarity with **Kubernetes RBAC**, **TLS**, and **DNS** setup
    

⚠️ Ensure all tools are installed and your Terraform state reflects the expected AKS and networking configuration.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1747845505951/da3f8650-a9e0-4644-87b0-e326e05b05df.png align="left")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1747845569223/219efa62-5574-4fa8-a7da-c95f893fd786.png align="left")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1747846064978/967dbd79-666e-46b2-85b1-305767ff579e.png align="left")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1747845981891/30cd07a5-7071-42f2-8550-592db88252d8.png align="left")

```bash
kubectl get pod -A | grep -E 'istio|cert|wi-webhook'
kubectl get svc -A | grep -E 'istio|cert|wi-webhook'
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750973666036/8f7983d2-50b5-4feb-8c78-17dfc6f13520.png align="center")

### Configure a new Entra ID App registration

#### **Add a new Entra ID App registration**[**¶**](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#add-a-new-entra-id-app-registration)

1. Fro[m](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#add-a-new-entra-id-app-registration) the `Microsoft Entra ID` &gt; `App registrations` menu, choose `+ New registration`
    
2. Enter a `Name` for the application (e.g. `ArgoCD-Terranetes-SSO`).
    
3. Specify who can use the application (e.g. `Accounts in this organizational directory only`).
    
4. Enter Redirect URI (optional) as follows (r[e](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#add-a-new-entra-id-app-registration)placing [`http://argocd.terranetesprivate.com:8085/auth/callback`](http://argocd.terranetesprivate.com:8085/auth/callback) with your Argo URL), then choose `Add`.
    
    * **Platform:** `Web`
        
    * **Redirect URI:** [`https://argocd.terranetesprivate.com/auth/callback`](https://argocd.terranetesprivate.com/auth/callback)
        
5. When registration finishes, the Azure porta[l](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#add-a-new-entra-id-app-registration) displays the app registration's Overview pane. You see the Application (client) ID.
    

#### **Configure additional platform settings for ArgoCD CLI**[**¶**](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#configure-additional-platform-settings-for-argocd-cli)

1. In the Azure portal, in App registrations, select your [a](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#configure-additional-platform-settings-for-argocd-cli)pplication.
    
2. Under Manage, select Authentication.
    
3. Under Platform [c](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#configure-additional-platform-settings-for-argocd-cli)onfigurations, select Add a platform.
    
4. Under Configure platforms, select the "Mobile and deskt[o](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#configure-additional-platform-settings-for-argocd-cli)p applications" tile. Use the below value. You shouldn't change it.
    
    * **Redirect URI:** [`http://localhost:8085/auth/callback`](http://localhost:8085/auth/callback)
        

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750974484400/f43289a5-2160-46e5-b61b-9f62b2272dd1.png align="center")

#### **Add credentials a new Entra ID App registration**[**¶**](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#add-credentials-a-new-entra-id-app-registration)

##### **Using Workload Identity Federation (Recommended)**

1. **Label the Pods:** Add the `azure.workload.identity/use: "true"` label to the `argocd-server` pods.
    
2. **Add Annotation to Service Account:** Add `azure.workload.identity/client-id: "$CLIENT_ID"` annotation to the `argocd-server` service account using the details from application created in previous step.
    
3. From the `Certificates & secrets` menu, navigate to `Federated credentials`, then choose `+ Add credential`
    
4. Choose `Federated credential scenario` as `Kubernetes Accessing Azure resources`
    
5. Enter Cluster Issuer URL, refer to [retrieve the OIDC issuer URL](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster#retrieve-the-oidc-issuer-url) documentation
    
6. Enter namespace as the namespace where the argocd is deployed
    
7. Enter service account name as `argocd-server`
    
8. Enter a unique name
    
9. Click Add.
    

##### **I updated the Workload Identity Federation for the App registration with TERRAFORM**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750975039819/0d63cdd9-c3df-40df-921d-30e146387513.png align="center")

#### **Setup permissions for Entra ID Application**[**¶**](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#setup-permissions-for-entra-id-application)

1. From the `API permissions` menu, choose `+ Add a permission`
    
2. Find [`User.Read`](http://User.Read) permission (under `Microsoft Graph`) and grant it to the created application:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750975213784/42587ed0-0985-48c4-b55c-7ca6c83d1d52.png align="center")

From the `Token Configuration` menu, choose `+ Add groups claim`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750975302577/1ca80cd2-1371-426b-8f91-7f1c276be9a5.png align="center")

### Associate an Entra ID group to your Entra ID App registration[¶](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#associate-an-entra-id-group-to-your-entra-id-app-registration)

1. From the `Microsoft Entra ID` &gt; `Enterprise applications` menu, sea[r](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#associate-an-entra-id-group-to-your-entra-id-app-registration)ch the App that you created (e.g. `ArgoCD-Terranetes-SSO`).
    
    * An Enterprise application with the same name of the Entra ID Ap[p](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#associate-an-entra-id-group-to-your-entra-id-app-registration) registration is created when you add a new Entra ID App registration.
        
2. From the `Users and groups` menu of the app, add any users or gro[u](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/#associate-an-entra-id-group-to-your-entra-id-app-registration)ps requiring access to the service.
    
    mine is `terranetes-group`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750975550735/3cbca5b8-372c-4f7c-9fae-bb9c71461484.png align="center")

### Configure and deploy Argocd to use the new Entra ID App registration with `helm`

let’s create variables

```bash
## create variables for the tenant_id and client_id and object_id for terranetes-group
export TENANT_ID="TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"
export APP_REGISTRATION_CLIENT_ID="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
export TERRANETES_GROUP_OBJECT_ID="GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"
```

```yaml

### create argo-cd-values.yaml file with cat command
cat > argo-cd-values.yaml << EOF
## Custom resource configuration
crds:
  install: true
  keep: true
  annotations: {}
  additionalLabels: {}

global:
  podAnnotations:
    azure.workload.identity/use: "true"   
  podLabels:
    azure.workload.identity/use: "true"

controller:
  serviceAccount:
    create: true
    name: argocd-application-controller
    annotations:
      azure.workload.identity/client-id: ${APP_REGISTRATION_CLIENT_ID} # Required for workload identity
    labels:
      azure.workload.identity/use: "true" # Required if using the webhook

server:
  serviceAccount:
    create: true
    name: argocd-server
    annotations:
      azure.workload.identity/client-id: ${APP_REGISTRATION_CLIENT_ID} # Required for workload identity
    labels:
      azure.workload.identity/use: "true" # Required if using the webhook

## Argo CD Configs
configs:
  cm:
    create: true

    # Enable local admin user
    admin.enabled: true
    
    ## ArgoCD URL 
    url: https://argocd.terranetesprivate.com

    # OIDC configuration
    oidc.config: |
      name: AzureAD
      issuer: https://login.microsoftonline.com/${TENANT_ID}/v2.0
      clientID: ${APP_REGISTRATION_CLIENT_ID} # Required for workload identity
      redirectURIs:
        - https://argocd.terranetesprivate.com:8085/auth/callback
        - https://argocd.terranetesprivate.com/auth/callback

      azure:
        useWorkloadIdentity: true
      requestedIDTokenClaims:
        groups:
          essential: true
          value: "SecurityGroup"
      requestedScopes:
        - openid
        - profile
        - email

  params:
    server.insecure: true

  # Add RBAC configuration to map Azure AD groups to ArgoCD roles
  rbac:
    # Ensure this is set to true if you are defining RBAC policies here
    create: true 
    policy.default: role:readonly
    policy.csv: |
      # Platform Admin Policies
      p, role:org-admin, applications, *, */*, allow
      p, role:org-admin, clusters, get, *, allow
      p, role:org-admin, projects, *, *, allow
      p, role:org-admin, repositories, *, *, allow
      p, role:org-admin, certificates, *, *, allow
      p, role:org-admin, accounts, *, *, allow
      p, role:org-admin, gpgkeys, *, *, allow # Allow GPG key management for admins

      # Azure AD Group Mapping for terranetes-group (Object ID: GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG)
      # The OIDC connector name 'AzureAD' must match 'name' in oidc.config.
      g, AzureAD:${TERRANETES_GROUP_OBJECT_ID}, role:org-admin
    scopes: "[groups, email]" # These scopes are required for RBAC to function correctly
    userInfoGroupsField: memberOf

EOF
```

### Deploy Argocd with helm

```bash
## install the argo-cd
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

helm upgrade --install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  -f argo-cd-values.yaml \
  --debug
  
sleep 180 # wait for 3 mins
```

When you check the pods in argocd namespace, only 1 container will be running on each component.

```bash
kubectl get pods -n argocd
```

But after labelling the `argocd` namespace with `aks istio envoy proxy` injection and restart the namespace pods, each pod will be running with two containers (`sidecar` and `main container`).

```bash
kubectl label namespace argocd istio.io/rev=asm-1-24 --overwrite

## Verify the argocd namespace labels
kubectl get namespace argocd --show-labels

## restart the pods
kubectl rollout restart deployment -n argocd
kubectl delete pod argocd-application-controller-0 -n argocd
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750978442276/154e2909-1224-4d3a-a01e-571b0e69c703.png align="center")

To add the `callback url` it requires `https url` . In that case, i need to create an tls `certificate`

I want to use `cert-manager` with `let’sencrypt` to automate the *certificate issuing* and *certificate renewal.*

But let’sencrypt does not validate ACME challenge in private dns zone, so i decided to use `OPENSSL` certificate for the `POC`. But i production i will advise to use private certificate provider like `Digicert CA`.

```bash
# Create directory in your home folder instead of root
mkdir -p ~/argocd-tls

# Generate self-signed certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout ~/argocd-tls/argocd.key \
  -out ~/argocd-tls/argocd.crt \
  -days 365 \
  -subj "/CN=argocd.terranetesprivate.com/O=argocd.terranetesprivate.com"
```

Create a Kubernetes Secret for the Self-Signed Certificate in the `aks-istio-ingress` namespace

```bash
# Create the secret using the correct path to your files
kubectl create secret tls argocd-tls-secret \
  --cert=./argocd-tls/argocd.crt \
  --key=./argocd-tls/argocd.key \
  -n aks-istio-ingress

## Verify the secret
kubectl get secret argocd-tls-secret -n aks-istio-ingress
```

Configure `AKS Istio Gateway` and `Virtualservice`: in `aks-istio-ingress` namespace where Istio Ingress Gateway is installed / cert tls secret is created.

```yaml
echo "INSTALLING ISTIO INGRESS GATEWAY FOR INTERNAL TRAFFIC (private IP)"
cat <<EOF | kubectl apply -f -
# argocd-gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: argocd-internal-gateway-tls
  namespace: aks-istio-ingress                        # Namespace for Istio Ingress Gateway
spec:
  selector:
    istio: aks-istio-ingressgateway-internal  # Selects the internal gateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "argocd.terranetesprivate.com"
    tls:
      httpsRedirect: true    
  - port: 
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: argocd-tls-secret    # Name of the TLS secret created by cert-manager or kubectl and openssl
    hosts:
    - "argocd.terranetesprivate.com"
---
# argocd-virtualservice.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: argocd-virtualservice
  namespace: argocd
spec:
  hosts:
  - "argocd.terranetesprivate.com"
  gateways:
  - aks-istio-ingress/argocd-internal-gateway-tls
  http:
  - route:
    - destination:
        host: argocd-server.argocd.svc.cluster.local
        port:
          number: 80      
EOF
```

# BINGO! 🚀

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750979155691/00f08437-ad64-4a10-85d2-0ea33fcc08a3.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751013768603/65ed6007-7b2a-4ab6-be8d-d0fc51434ab7.gif align="center")

## **✅ Conclusion: Elevating Security and Efficiency with Azure AD SSO for Argo CD**

Integrating Azure Active Directory (AAD) Single Sign-On (SSO) with Argo CD is not just a technical enhancement—it's a strategic upgrade for any organization embracing GitOps at scale. This integration bridges the gap between secure identity management and seamless developer experience, ensuring that access to your deployment pipelines is both tightly controlled and frictionless.

By aligning Argo CD authentication with your enterprise identity provider, you gain the ability to enforce consistent access policies, reduce operational overhead, and meet compliance requirements—all while empowering your teams to move faster and more securely.

---

### \*\*🔍 Why Azure AD SSO Is a Necessity—\*\*Not Optional

**🔐 Strengthened Security Posture**  
SSO centralizes authentication through Azure AD, enabling advanced security features like Multi-Factor Authentication (MFA), Conditional Access, and Identity Protection. This drastically reduces the risk of credential sprawl and unauthorized access, especially in environments where Argo CD manages critical deployment workflows.

**🚀 Seamless Developer Experience**  
With SSO, developers and operators can log in using their existing corporate credentials—no need to manage or remember separate usernames and passwords. This reduces login friction, minimizes password fatigue, and allows teams to focus on delivering value rather than managing access.

**🧩 Simplified Identity Lifecycle Management**  
As teams grow and change, managing user access manually becomes error-prone and inefficient. Azure AD integration ensures that onboarding, offboarding, and role transitions are handled centrally. When a user leaves the organization or changes roles, their access to Argo CD is automatically updated or revoked—no manual cleanup required.

**📊 Centralized Auditing and Compliance**  
All authentication events are logged in Azure AD, providing a single source of truth for access control. This is essential for meeting regulatory requirements, conducting security audits, and maintaining visibility into who accessed what, when, and from where.

**🔧 Foundation for Granular Access Control**  
This SSO setup lays the groundwork for implementing `fine-grained access control` in Argo CD using `AppProjects`. By mapping Azure AD groups to specific roles and projects, you can enforce least-privilege access across teams and environments—ensuring that users only interact with the resources they’re authorized to manage.

Follow me on **Linkedin** [George Ezejiofor](https://www.linkedin.com/in/george-ezejiofor-89615a8a/) to stay updated on cloud-native observability insights! 😊

Happy deploying! 🚀🎉
