Repository: Azure/kubelogin

Issue:
Add AzurePipelinesCredential to authentication options
Hi!

Please, extend login options with [AzurePipelinesCredential](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/azure_pipelines_credential.go). 

This should provide easier way to configure kubelogin authentication, when running as exec plugin in tools such as Terraform.

Hints from issue comments:
@weinong: i will take a look in about two weeks. busy in some other things.@weinong: Implementation Plan: AzurePipelinesCredential Support in kubelogin

Summary
Add a new login method "azurepipelines" leveraging azidentity.NewAzurePipelinesCredential to enable non-interactive authentication inside Azure DevOps pipelines (e.g., Terraform exec plugin scenarios). Pattern will mirror existing credential implementations (e.g., AzureDeveloperCLICredential) and integrate with convert-kubeconfig.

1. Version Assessment
- Current go.mod: azidentity v1.8.0. Verify whether NewAzurePipelinesCredential exists in this version.
- If absent, bump azidentity (and azcore if required) to the minimum version that introduces AzurePipelinesCredential.
- Re-run tests after upgrade; watch for transitive changes (cache module, azcore interface changes).

2. New Login Method Constant
Files:
- internal/token/options.go: add `AzurePipelinesLogin = "azurepipelines"`
- Append to `supportedLogin` slice.
- pkg/token/options.go: export alias if pattern followed elsewhere (similar to WorkloadIdentityLogin).

3. Options / Flags / Env
Add fields to internal/token/options.go:
- AzurePipelinesServiceConnectionID string
Add flag in AddFlags(): `--azure-pipelines-service-connection-id` (name intentionally verbose for clarity; short form not needed).
Environment variables mapping (internal/env/variables.go):
- AZURE_PIPELINES_SERVICE_CONNECTION_ID
System.AccessToken capture strategy:
- Do NOT add a flag by default; read from env var `SYSTEM_ACCESSTOKEN` (Azure Pipelines predefined). Optionally allow explicit override via env AZURE_PIPELINES_SYSTEM_ACCESS_TOKEN if desirable (defer unless requested).
Validation changes (Options.Validate()):
- If LoginMethod == azurepipelines then require: TenantID (non-empty), ServerID (already required later), AzurePipelinesServiceConnectionID non-empty, and presence of SYSTEM_ACCESSTOKEN env var. Provide clear error messages:
  * missing service connection ID: `--azure-pipelines-service-connection-id is required for --login azurepipelines`
  * missing system access token: `environment variable SYSTEM_ACCESSTOKEN not set; enable "Allow scripts to access the OAuth token" in the pipeline`

4. Credential Implementation
New file: internal/token/azurepipelinescredential.go
- Follow azuredevopscredential.go pattern.
- Constructor: newAzurePipelinesCredential(o *Options) (CredentialProvider, error)
  * Read systemAccessToken := os.Getenv("SYSTEM_ACCESSTOKEN")
  * Call: azidentity.NewAzurePipelinesCredential(o.TenantID, o.AzurePipelinesServiceConnectionID, systemAccessToken, nil)
  * Wrap in struct implementing CredentialProvider with Name() = "AzurePipelinesCredential"; GetToken delegates; Authenticate returns errAuthenticateNotSupported; NeedAuthenticate() false.

5. Provider Switch Wiring
File: internal/token/provider.go
- Add case AzurePipelinesLogin: return newAzurePipelinesCredential(o)

6. convert-kubeconfig Integration
File: internal/converter/convert.go
- Extend switch over login method to add case token.AzurePipelinesLogin paralleling service principal & interactive patterns.
- Required args to add: `--tenant-id`, `--azure-pipelines-service-connection-id` (new flag)
- Always require tenant ID (error if empty).
- Append arg ordering consistent with existing methods: after `--login` & `--server-id`, insert `--tenant-id <val>` then `--azure-pipelines-service-connection-id <val>`.
- No environment arg (environment optional; treat similarly to other non-interactive flows—allow if explicitly set by user & pass-through if consistent with design; can omit initially if not needed).

7. CLI Help / Completions
- Adding flag automatically included via AddFlags() pattern.
- Update help string in AddFlags() to clarify usage: `Service connection (resource) ID used by azurepipelines login method`.
- Ensure cobra completion registration picks it up (automatic).

8. Tests
Add tests / modify existing:
- internal/token/provider_test.go: add scenario constructing provider with LoginMethod azurepipelines and required fields; assert type.
- internal/token/options_ctor_test.go or similar: ensure env/flag mapping loads AzurePipelinesServiceConnectionID.
- Validation test: missing service connection ID or missing SYSTEM_ACCESSTOKEN env => error.
- Converter test (if converter has existing tests) verifying args include new flag sequence.
- (Optional) Minimal GetToken smoke test using a mocked HTTP pipeline or record fixture (may be deferred to later if OIDC interaction complex).

9. Documentation
Add new doc page: docs/book/src/concepts/login-modes/azurepipelines.md
Contents:
- Overview, prerequisites (service connection, enabling System.AccessToken), sample pipeline YAML.
- Required flags/env variables.
- Troubleshooting table referencing typical errors (AADSTS900023, 401 OIDC, invalid service connection ID).
Update:
- docs/book/src/cli/get-token.md & convert-kubeconfig.md (supported login methods list add azurepipelines).
- README.md supported login methods bullet list.

10. Changelog
CHANGELOG.md under Unreleased:
- Added: Azure Pipelines login method (azurepipelines) using AzurePipelinesCredential.

11. Quality Gates
- go build ./...
- go test ./... (ensure new tests added)
- Optional static checks (if repo has linters configured).

12. Future Enhancements (Not in initial PR)
- Option to specify alternative system access token env var.
- Multi-cloud support if service connection targets non-public cloud (add --environment pass-through if needed by users).
- VCR-based integration test recording OIDC exchange (requires test harness changes).

Acceptance Criteria
- Passing tests & build.
- New login method appears in CLI help and docs.
- Selecting --login azurepipelines without required inputs yields clear errors.
- With required fields + SYSTEM_ACCESSTOKEN present, credential object constructed (manually verifiable via dry-run logging or unit test).

Please comment if naming or flag semantics should be adjusted before implementation. Will proceed after a short feedback window or immediate green light.@weinong: @bonddim you can give it a try
https://github.com/weinong/kubelogin/releases/download/v0.2.10/kubelogin-linux-amd64.zip@bonddim: > [@bonddim](https://github.com/bonddim) you can give it a try https://github.com/weinong/kubelogin/releases/download/v0.2.10/kubelogin-linux-amd64.zip

Test snippet:
```yaml
  - task: AzureCLI@2
    displayName: "Get AKS Token with kubelogin"
    inputs:
      azureSubscription: $(AZURE_CONNECTION)
      scriptType: "bash"
      scriptLocation: "inlineScript"
      inlineScript: |
        kubelogin  get-token --server-id 6dae42f8-4368-4678-94ff-3960e28e3630 --login azurepipelines --tenant-id $(AZURE_TENANT_ID)
```

Got 
```
Error: --azure-pipelines-service-connection-id is required for --login azurepipelines
```
Also, it doesn't read tenant id from AZURE_TENANT_ID variable if not provided as cmd arg.
```
Error: tenant ID is required for azurepipelines login method
```
@weinong: oops. i missed it before i cut the release.
https://github.com/Azure/kubelogin/releases/tag/v0.2.11@weinong: ADO has `AZURESUBSCRIPTION_TENANT_ID` and `AZURESUBSCRIPTION_SERVICE_CONNECTION_ID` that we can use.@weinong: #712 is added to track this@weinong: https://github.com/Azure/kubelogin/releases/tag/v0.2.12

Solution patch:
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
index fba6ec5..77acea2 100644
--- a/docs/book/src/SUMMARY.md
+++ b/docs/book/src/SUMMARY.md
@@ -9,6 +9,7 @@
     - [Device Code](./concepts/login-modes/devicecode.md)
     - [Azure CLI](./concepts/login-modes/azurecli.md)
     - [Azure Developer CLI](./concepts/login-modes/azd.md)
+    - [Azure Pipelines](./concepts/login-modes/azurepipelines.md)
     - [Web Browser Interactive](./concepts/login-modes/interactive.md)
     - [Service Principal](./concepts/login-modes/sp.md)
     - [Managed Service Identity](./concepts/login-modes/msi.md)
diff --git a/docs/book/src/cli/convert-kubeconfig.md b/docs/book/src/cli/convert-kubeconfig.md
index a25f91b..938035d 100644
--- a/docs/book/src/cli/convert-kubeconfig.md
+++ b/docs/book/src/cli/convert-kubeconfig.md
@@ -14,9 +14,10 @@ Usage:
   kubelogin convert-kubeconfig [flags]
 
 Flags:
-      --authority-host string                Workload Identity authority host. It may be specified in AZURE_AUTHORITY_HOST environment variable
-      --azure-config-dir string              Azure CLI config path
-      --cache-dir string                     directory to cache authentication record (default "/home/weinongw/.kube/cache/kubelogin/")
+      --authority-host string                          Workload Identity authority host. It may be specified in AZURE_AUTHORITY_HOST environment variable
+      --azure-config-dir string                        Azure CLI config path
+      --azure-pipelines-service-connection-id string   Service connection (resource) ID used by azurepipelines login method
+      --cache-dir string                               directory to cache authentication record (default "/home/weinongw/.kube/cache/kubelogin/")
       --client-certificate string            AAD client cert in pfx. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE or AZURE_CLIENT_CERTIFICATE_PATH environment variable
       --client-certificate-password string   Password for AAD client cert. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD or AZURE_CLIENT_CERTIFICATE_PASSWORD environment variable
       --client-id string                     AAD client application ID. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_ID or AZURE_CLIENT_ID environment variable
@@ -30,7 +31,7 @@ Flags:
       --identity-resource-id string          Managed Identity resource id.
       --kubeconfig string                    Path to the kubeconfig file to use for CLI requests.
       --legacy                               set to true to get token with 'spn:' prefix in audience claim
-  -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
+  -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity, azurepipelines. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
       --login-hint string                    The login hint to pre-fill the username in the interactive login flow.
       --password string                      password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
       --pop-claims key=val,key2=val2         contains a comma-separated list of claims to attach to the pop token in the format key=val,key2=val2. At minimum, specify the ARM ID of the cluster as `u=ARM_ID`
diff --git a/docs/book/src/cli/get-token.md b/docs/book/src/cli/get-token.md
index 18d5721..febb6a2 100644
--- a/docs/book/src/cli/get-token.md
+++ b/docs/book/src/cli/get-token.md
@@ -12,8 +12,9 @@ Usage:
   kubelogin get-token [flags]
 
 Flags:
-      --authority-host string                Workload Identity authority host. It may be specified in AZURE_AUTHORITY_HOST environment variable
-      --cache-dir string                     directory to cache authentication record (default "/home/weinongw/.kube/cache/kubelogin/")
+      --authority-host string                          Workload Identity authority host. It may be specified in AZURE_AUTHORITY_HOST environment variable
+      --azure-pipelines-service-connection-id string   Service connection (resource) ID used by azurepipelines login method
+      --cache-dir string                               directory to cache authentication record (default "/home/weinongw/.kube/cache/kubelogin/")
       --client-certificate string            AAD client cert in pfx. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE or AZURE_CLIENT_CERTIFICATE_PATH environment variable
       --client-certificate-password string   Password for AAD client cert. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD or AZURE_CLIENT_CERTIFICATE_PASSWORD environment variable
       --client-id string                     AAD client application ID. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_ID or AZURE_CLIENT_ID environment variable
@@ -25,7 +26,7 @@ Flags:
   -h, --help                                 help for get-token
       --identity-resource-id string          Managed Identity resource id.
       --legacy                               set to true to get token with 'spn:' prefix in audience claim
-  -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
+  -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity, azurepipelines. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
       --login-hint string                    The login hint to pre-fill the username in the interactive login flow.
       --password string                      password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
       --pop-claims key=val,key2=val2         contains a comma-separated list of claims to attach to the pop token in the format key=val,key2=val2. At minimum, specify the ARM ID of the cluster as `u=ARM_ID`
@@ -228,3 +229,29 @@ users:
         command: kubelogin
         env: null
 ```
+
+### Azure Pipelines
+
+```yaml
+kind: Config
+preferences: {}
+users:
+  - name: demouser
+    user:
+      exec:
+        apiVersion: client.authentication.k8s.io/v1beta1
+        args:
+          - get-token
+          - --server-id
+          - <AAD server app ID>
+          - --client-id
+          - <AAD client app ID>
+          - --tenant-id
+          - <AAD tenant ID>
+          - --login
+          - azurepipelines
+          - --azure-pipelines-service-connection-id
+          - <service connection resource ID>
+        command: kubelogin
+        env: null
+```
diff --git a/docs/book/src/concepts/login-modes/azurepipelines.md b/docs/book/src/concepts/login-modes/azurepipelines.md
new file mode 100644
index 0000000..50013e0
--- /dev/null
+++ b/docs/book/src/concepts/login-modes/azurepipelines.md
@@ -0,0 +1,122 @@
+# Azure Pipelines
+
+This login mode uses Azure Pipelines service connections and the built-in `SYSTEM_ACCESSTOKEN` to authenticate with Azure AD. This is particularly useful when running kubelogin as an exec plugin within Azure DevOps pipelines, such as in Terraform deployments that need to interact with Azure Kubernetes Service clusters.
+
+The authentication leverages Azure Pipelines' managed identity integration through service connections, providing a seamless way to authenticate without additional credential management.
+
+> ### NOTE
+>
+> This login mode only works within Azure DevOps pipelines and requires proper pipeline configuration.
+
+## Prerequisites
+
+1. **Service Connection**: An Azure Resource Manager service connection configured in your Azure DevOps project
+2. **Pipeline Configuration**: The pipeline must have "Allow scripts to access the OAuth token" enabled in the agent job settings
+3. **Environment Variables**: The following environment variables must be available (automatically set by Azure Pipelines when OAuth token access is enabled):
+   - `SYSTEM_ACCESSTOKEN`: The OAuth token provided by Azure Pipelines
+   - `SYSTEM_OIDCREQUESTURI`: The OIDC request URI (automatically set by Azure Pipelines)
+
+## Required Parameters
+
+- `--tenant-id`: Azure AD tenant ID where the service connection is configured
+- `--client-id`: Application ID of the client application (typically the AKS cluster's client ID)  
+- `--server-id`: Application ID of the server/resource (typically the AKS cluster's server ID)
+- `--azure-pipelines-service-connection-id`: The resource ID of the Azure Resource Manager service connection
+
+## Usage Examples
+
+### Basic Usage in Pipeline
+
+```yaml
+# azure-pipelines.yml
+steps:
+- task: AzureCLI@2
+  displayName: 'Deploy to AKS'
+  inputs:
+    azureSubscription: 'my-service-connection'
+    scriptType: 'bash'
+    scriptLocation: 'inlineScript'
+    inlineScript: |
+      # Configure kubeconfig to use azurepipelines login
+      kubelogin convert-kubeconfig \
+        --login azurepipelines \
+        --tenant-id $(tenant-id) \
+        --client-id $(client-id) \
+        --server-id $(server-id) \
+        --azure-pipelines-service-connection-id $(service-connection-resource-id)
+      
+      # Now kubectl commands will authenticate using Azure Pipelines credentials
+      kubectl get nodes
+    addSpnToEnvironment: true  # This enables SYSTEM_ACCESSTOKEN
+```
+
+### Direct Token Retrieval
+
+```bash
+# In Azure DevOps pipeline (with "Allow scripts to access the OAuth token" enabled)
+kubelogin get-token \
+  --login azurepipelines \
+  --tenant-id <tenant-id> \
+  --client-id <client-id> \
+  --server-id <cluster-server-id> \
+  --azure-pipelines-service-connection-id <service-connection-resource-id>
+```
+
+### Terraform Integration
+
+```yaml
+# azure-pipelines.yml for Terraform deployments
+steps:
+- task: TerraformTaskV3@3
+  displayName: 'Terraform Apply'
+  inputs:
+    provider: 'azurerm'
+    command: 'apply'
+    workingDirectory: '$(System.DefaultWorkingDirectory)/terraform'
+    environmentServiceNameAzureRM: 'my-service-connection'
+    commandOptions: |
+      -auto-approve
+  env:
+    # Configure kubeconfig for kubectl provider in Terraform
+    KUBECONFIG: $(Agent.TempDirectory)/kubeconfig
+- script: |
+    # Convert kubeconfig to use azurepipelines authentication
+    kubelogin convert-kubeconfig \
+      --login azurepipelines \
+      --tenant-id $(tenant-id) \
+      --client-id $(client-id) \
+      --server-id $(server-id) \
+      --azure-pipelines-service-connection-id $(service-connection-resource-id) \
+      --kubeconfig $(Agent.TempDirectory)/kubeconfig
+  displayName: 'Configure kubectl authentication'
+  condition: always()
+```
+
+## How It Works
+
+1. **Service Connection**: Azure DevOps service connections provide managed identity or service principal authentication to Azure resources
+2. **System Access Token**: When "Allow scripts to access the OAuth token" is enabled, Azure Pipelines provides a `SYSTEM_ACCESSTOKEN` environment variable
+3. **OIDC Integration**: The `azurepipelines` login method uses Azure SDK's `AzurePipelinesCredential` to exchange the system access token for an Azure AD token
+4. **Token Caching**: Authentication tokens are cached to improve performance across multiple kubectl operations
+
+## Troubleshooting
+
+### Common Errors
+
+- **"SYSTEM_ACCESSTOKEN environment variable not set"**: Enable "Allow scripts to access the OAuth token" in your pipeline job settings
+- **"SYSTEM_OIDCREQUESTURI environment variable not set"**: This should be automatically set by Azure Pipelines; check your Azure DevOps version and configuration
+- **"tenant ID is required"**: Provide the `--tenant-id` parameter
+- **"--azure-pipelines-service-connection-id is required"**: Provide the service connection resource ID parameter
+
+### Finding Service Connection Resource ID
+
+The service connection resource ID can be found in the Azure DevOps portal:
+1. Go to Project Settings → Service connections
+2. Select your Azure Resource Manager service connection
+3. The resource ID is displayed in the connection details
+
+## References
+
+- https://learn.microsoft.com/en-us/azure/devops/pipelines/process/system-and-variable-groups
+- https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints
+- https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#AzurePipelinesCredential
\ No newline at end of file
diff --git a/pkg/internal/converter/convert.go b/pkg/internal/converter/convert.go
index 21fa53a..ece19a4 100644
--- a/pkg/internal/converter/convert.go
+++ b/pkg/internal/converter/convert.go
@@ -4,10 +4,11 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/Azure/kubelogin/pkg/internal/token"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/clientcmd/api"
 	klog "k8s.io/klog/v2"
+
+	"github.com/Azure/kubelogin/pkg/internal/token"
 )
 
 const (
@@ -18,51 +19,53 @@ const (
 	cfgEnvironment    = "environment"
 	cfgConfigMode     = "config-mode"
 
-	argClientID                   = "--client-id"
-	argServerID                   = "--server-id"
-	argTenantID                   = "--tenant-id"
-	argEnvironment                = "--environment"
-	argClientSecret               = "--client-secret"
-	argClientCert                 = "--client-certificate"
-	argClientCertPassword         = "--client-certificate-password"
-	argIsLegacy                   = "--legacy"
-	argUsername                   = "--username"
-	argPassword                   = "--password"
-	argLoginMethod                = "--login"
-	argIdentityResourceID         = "--identity-resource-id"
-	argAuthorityHost              = "--authority-host"
-	argFederatedTokenFile         = "--federated-token-file"
-	argTokenCacheDir              = "--token-cache-dir"
-	argAuthRecordCacheDir         = "--cache-dir"
-	argIsPoPTokenEnabled          = "--pop-enabled"
-	argPoPTokenClaims             = "--pop-claims"
-	argDisableEnvironmentOverride = "--disable-environment-override"
-	argRedirectURL                = "--redirect-url"
-	argLoginHint                  = "--login-hint"
-
-	flagAzureConfigDir             = "azure-config-dir"
-	flagClientID                   = "client-id"
-	flagContext                    = "context"
-	flagServerID                   = "server-id"
-	flagTenantID                   = "tenant-id"
-	flagEnvironment                = "environment"
-	flagClientSecret               = "client-secret"
-	flagClientCert                 = "client-certificate"
-	flagClientCertPassword         = "client-certificate-password"
-	flagIsLegacy                   = "legacy"
-	flagUsername                   = "username"
-	flagPassword                   = "password"
-	flagLoginMethod                = "login"
-	flagIdentityResourceID         = "identity-resource-id"
-	flagAuthorityHost              = "authority-host"
-	flagFederatedTokenFile         = "federated-token-file"
-	flagTokenCacheDir              = "token-cache-dir"
-	flagAuthRecordCacheDir         = "cache-dir"
-	flagIsPoPTokenEnabled          = "pop-enabled"
-	flagPoPTokenClaims             = "pop-claims"
-	flagDisableEnvironmentOverride = "disable-environment-override"
-	flagRedirectURL                = "redirect-url"
-	flagLoginHint                  = "login-hint"
+	argClientID                          = "--client-id"
+	argServerID                          = "--server-id"
+	argTenantID                          = "--tenant-id"
+	argEnvironment                       = "--environment"
+	argClientSecret                      = "--client-secret"
+	argClientCert                        = "--client-certificate"
+	argClientCertPassword                = "--client-certificate-password"
+	argIsLegacy                          = "--legacy"
+	argUsername                          = "--username"
+	argPassword                          = "--password"
+	argLoginMethod                       = "--login"
+	argIdentityResourceID                = "--identity-resource-id"
+	argAuthorityHost                     = "--authority-host"
+	argFederatedTokenFile                = "--federated-token-file"
+	argTokenCacheDir                     = "--token-cache-dir"
+	argAuthRecordCacheDir                = "--cache-dir"
+	argIsPoPTokenEnabled                 = "--pop-enabled"
+	argPoPTokenClaims                    = "--pop-claims"
+	argDisableEnvironmentOverride        = "--disable-environment-override"
+	argRedirectURL                       = "--redirect-url"
+	argLoginHint                         = "--login-hint"
+	argAzurePipelinesServiceConnectionID = "--azure-pipelines-service-connection-id"
+
+	flagAzureConfigDir                    = "azure-config-dir"
+	flagClientID                          = "client-id"
+	flagContext                           = "context"
+	flagServerID                          = "server-id"
+	flagTenantID                          = "tenant-id"
+	flagEnvironment                       = "environment"
+	flagClientSecret                      = "client-secret"
+	flagClientCert                        = "client-certificate"
+	flagClientCertPassword                = "client-certificate-password"
+	flagIsLegacy                          = "legacy"
+	flagUsername                          = "username"
+	flagPassword                          = "password"
+	flagLoginMethod                       = "login"
+	flagIdentityResourceID                = "identity-resource-id"
+	flagAuthorityHost                     = "authority-host"
+	flagFederatedTokenFile                = "federated-token-file"
+	flagTokenCacheDir                     = "token-cache-dir"
+	flagAuthRecordCacheDir                = "cache-dir"
+	flagIsPoPTokenEnabled                 = "pop-enabled"
+	flagPoPTokenClaims                    = "pop-claims"
+	flagDisableEnvironmentOverride        = "disable-environment-override"
+	flagRedirectURL                       = "redirect-url"
+	flagLoginHint                         = "login-hint"
+	flagAzurePipelinesServiceConnectionID = "azure-pipelines-service-connection-id"
 
 	execName        = "kubelogin"
 	getTokenCommand = "get-token"
@@ -464,6 +467,18 @@ func Convert(o Options, pathOptions *clientcmd.PathOptions) error {
 			if o.isSet(flagFederatedTokenFile) {
 				exec.Args = append(exec.Args, argFederatedTokenFile, o.TokenOptions.FederatedTokenFile)
 			}
+
+		case token.AzurePipelinesLogin:
+
+			if argTenantIDVal == "" {
+				return fmt.Errorf("%s is required", argTenantID)
+			}
+
+			exec.Args = append(exec.Args, argTenantID, argTenantIDVal)
+
+			if o.isSet(flagAzurePipelinesServiceConnectionID) {
+				exec.Args = append(exec.Args, argAzurePipelinesServiceConnectionID, o.TokenOptions.AzurePipelinesServiceConnectionID)
+			}
 		}
 
 		authInfo.Exec = exec
diff --git a/pkg/internal/env/variables.go b/pkg/internal/env/variables.go
index 59e7101..6cf3384 100644
--- a/pkg/internal/env/variables.go
+++ b/pkg/internal/env/variables.go
@@ -24,7 +24,11 @@ const (
 	AzureClientID                  = "AZURE_CLIENT_ID"
 	AzureClientSecret              = "AZURE_CLIENT_SECRET"
 	AzureFederatedTokenFile        = "AZURE_FEDERATED_TOKEN_FILE"
+	AzurePassword                  = "AZURE_PASSWORD"
 	AzureTenantID                  = "AZURE_TENANT_ID"
 	AzureUsername                  = "AZURE_USERNAME"
-	AzurePassword                  = "AZURE_PASSWORD"
+
+	// env vars used by Azure Pipelines
+	SystemAccessToken    = "SYSTEM_ACCESSTOKEN"
+	SystemOIDCRequestURI = "SYSTEM_OIDCREQUESTURI"
 )
diff --git a/pkg/internal/token/azurepipelinescredential.go b/pkg/internal/token/azurepipelinescredential.go
new file mode 100644
index 0000000..e084dc3
--- /dev/null
+++ b/pkg/internal/token/azurepipelinescredential.go
@@ -0,0 +1,77 @@
+package token
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
+	"k8s.io/klog/v2"
+
+	"github.com/Azure/kubelogin/pkg/internal/env"
+)
+
+type AzurePipelinesCredential struct {
+	cred *azidentity.AzurePipelinesCredential
+}
+
+var _ CredentialProvider = (*AzurePipelinesCredential)(nil)
+
+func newAzurePipelinesCredential(opts *Options) (CredentialProvider, error) {
+	systemAccessToken := os.Getenv(env.SystemAccessToken)
+	if systemAccessToken == "" {
+		return nil, fmt.Errorf("%s environment variable not set", env.SystemAccessToken)
+	}
+
+	var (
+		c   azidentity.Cache
+		err error
+	)
+	if opts.UsePersistentCache {
+		c, err = cache.New(nil)
+		if err != nil {
+			klog.V(5).Infof("failed to create cache: %v", err)
+		}
+	}
+
+	azOpts := &azidentity.AzurePipelinesCredentialOptions{
+		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
+		Cache:                    c,
+		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
+	}
+
+	if opts.httpClient != nil {
+		azOpts.ClientOptions.Transport = opts.httpClient
+	}
+
+	cred, err := azidentity.NewAzurePipelinesCredential(
+		opts.TenantID,
+		opts.ClientID,
+		opts.AzurePipelinesServiceConnectionID,
+		systemAccessToken,
+		azOpts,
+	)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create azure pipelines credential: %w", err)
+	}
+	return &AzurePipelinesCredential{cred: cred}, nil
+}
+
+func (c *AzurePipelinesCredential) Name() string {
+	return "AzurePipelinesCredential"
+}
+
+func (c *AzurePipelinesCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
+	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
+}
+
+func (c *AzurePipelinesCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
+	return c.cred.GetToken(ctx, opts)
+}
+
+func (c *AzurePipelinesCredential) NeedAuthenticate() bool {
+	return false
+}
diff --git a/pkg/internal/token/options.go b/pkg/internal/token/options.go
index dd8de8d..ad21073 100644
--- a/pkg/internal/token/options.go
+++ b/pkg/internal/token/options.go
@@ -10,39 +10,41 @@ import (
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
-	"github.com/Azure/kubelogin/pkg/internal/env"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"k8s.io/client-go/util/homedir"
+
+	"github.com/Azure/kubelogin/pkg/internal/env"
 )
 
 type Options struct {
-	LoginMethod                string
-	ClientID                   string
-	ClientSecret               string
-	ClientCert                 string
-	ClientCertPassword         string
-	Username                   string
-	Password                   string
-	ServerID                   string
-	TenantID                   string
-	Environment                string
-	IsLegacy                   bool
-	Timeout                    time.Duration
-	AuthRecordCacheDir         string
-	authRecordCacheFile        string
-	IdentityResourceID         string
-	FederatedTokenFile         string
-	AuthorityHost              string
-	UseAzureRMTerraformEnv     bool
-	IsPoPTokenEnabled          bool
-	PoPTokenClaims             string
-	DisableEnvironmentOverride bool
-	UsePersistentCache         bool
-	DisableInstanceDiscovery   bool
-	httpClient                 *http.Client
-	RedirectURL                string
-	LoginHint                  string
+	LoginMethod                       string
+	ClientID                          string
+	ClientSecret                      string
+	ClientCert                        string
+	ClientCertPassword                string
+	Username                          string
+	Password                          string
+	ServerID                          string
+	TenantID                          string
+	Environment                       string
+	IsLegacy                          bool
+	Timeout                           time.Duration
+	AuthRecordCacheDir                string
+	authRecordCacheFile               string
+	IdentityResourceID                string
+	FederatedTokenFile                string
+	AuthorityHost                     string
+	UseAzureRMTerraformEnv            bool
+	IsPoPTokenEnabled                 bool
+	PoPTokenClaims                    string
+	DisableEnvironmentOverride        bool
+	UsePersistentCache                bool
+	DisableInstanceDiscovery          bool
+	httpClient                        *http.Client
+	RedirectURL                       string
+	LoginHint                         string
+	AzurePipelinesServiceConnectionID string
 }
 
 const (
@@ -56,6 +58,7 @@ const (
 	AzureCLILogin          = "azurecli"
 	AzureDeveloperCLILogin = "azd"
 	WorkloadIdentityLogin  = "workloadidentity"
+	AzurePipelinesLogin    = "azurepipelines"
 )
 
 var (
@@ -64,7 +67,7 @@ var (
 )
 
 func init() {
-	supportedLogin = []string{DeviceCodeLogin, InteractiveLogin, ServicePrincipalLogin, ROPCLogin, MSILogin, AzureCLILogin, AzureDeveloperCLILogin, WorkloadIdentityLogin}
+	supportedLogin = []string{DeviceCodeLogin, InteractiveLogin, ServicePrincipalLogin, ROPCLogin, MSILogin, AzureCLILogin, AzureDeveloperCLILogin, WorkloadIdentityLogin, AzurePipelinesLogin}
 }
 
 func GetSupportedLogins() string {
@@ -107,6 +110,8 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) {
 		fmt.Sprintf("Workload Identity federated token file. It may be specified in %s environment variable", env.AzureFederatedTokenFile))
 	fs.StringVar(&o.AuthorityHost, "authority-host", o.AuthorityHost,
 		fmt.Sprintf("Workload Identity authority host. It may be specified in %s environment variable", env.AzureAuthorityHost))
+	fs.StringVar(&o.AzurePipelinesServiceConnectionID, "azure-pipelines-service-connection-id", o.AzurePipelinesServiceConnectionID,
+		"Service connection (resource) ID used by azurepipelines login method")
 	fs.StringVar(&o.AuthRecordCacheDir, "token-cache-dir", o.AuthRecordCacheDir, "directory to cache authentication record")
 	_ = fs.MarkDeprecated("token-cache-dir", "use --cache-dir instead")
 	fs.StringVar(&o.AuthRecordCacheDir, "cache-dir", o.AuthRecordCacheDir, "directory to cache authentication record")
@@ -163,6 +168,22 @@ func (o *Options) Validate() error {
 		return fmt.Errorf("timeout must be greater than 0")
 	}
 
+	// Azure Pipelines login method validation
+	if o.LoginMethod == AzurePipelinesLogin {
+		if o.TenantID == "" {
+			return fmt.Errorf("tenant ID is required for azurepipelines login method")
+		}
+		if o.AzurePipelinesServiceConnectionID == "" {
+			return fmt.Errorf("--azure-pipelines-service-connection-id is required for --login azurepipelines")
+		}
+		if os.Getenv(env.SystemAccessToken) == "" {
+			return fmt.Errorf("environment variable %s not set; enable \"Allow scripts to access the OAuth token\" in the pipeline", env.SystemAccessToken)
+		}
+		if os.Getenv(env.SystemOIDCRequestURI) == "" {
+			return fmt.Errorf("environment variable %s not set; this should be automatically set by Azure Pipelines", env.SystemOIDCRequestURI)
+		}
+	}
+
 	return nil
 }
 
diff --git a/pkg/internal/token/provider.go b/pkg/internal/token/provider.go
index ddbf14b..c04fb89 100644
--- a/pkg/internal/token/provider.go
+++ b/pkg/internal/token/provider.go
@@ -80,6 +80,9 @@ func NewAzIdentityCredential(record azidentity.AuthenticationRecord, o *Options)
 		default:
 			return newWorkloadIdentityCredential(o)
 		}
+
+	case AzurePipelinesLogin:
+		return newAzurePipelinesCredential(o)
 	}
 
 	return nil, errors.New("unsupported token provider")



Test patch:
diff --git a/pkg/internal/token/azurepipelinescredential_test.go b/pkg/internal/token/azurepipelinescredential_test.go
new file mode 100644
index 0000000..2b85cfd
--- /dev/null
+++ b/pkg/internal/token/azurepipelinescredential_test.go
@@ -0,0 +1,92 @@
+package token
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/Azure/kubelogin/pkg/internal/env"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewAzurePipelinesCredential(t *testing.T) {
+	// Clean up environment variables after test
+	defer func() {
+		os.Unsetenv(env.SystemAccessToken)
+		os.Unsetenv(env.SystemOIDCRequestURI)
+	}()
+
+	tests := []struct {
+		name                 string
+		opts                 *Options
+		systemAccessToken    string
+		systemOIDCRequestURI string
+		expectError          bool
+		expectErrorSubstring string
+	}{
+		{
+			name: "valid credentials",
+			opts: &Options{
+				TenantID:                          "test-tenant-id",
+				ClientID:                          "test-client-id",
+				AzurePipelinesServiceConnectionID: "test-service-connection-id",
+			},
+			systemAccessToken:    "test-system-access-token",
+			systemOIDCRequestURI: "https://test.oidc.request.uri",
+			expectError:          false,
+		},
+		{
+			name: "missing system access token",
+			opts: &Options{
+				TenantID:                          "test-tenant-id",
+				ClientID:                          "test-client-id",
+				AzurePipelinesServiceConnectionID: "test-service-connection-id",
+			},
+			systemAccessToken:    "",
+			systemOIDCRequestURI: "https://test.oidc.request.uri",
+			expectError:          true,
+			expectErrorSubstring: fmt.Sprintf("%s environment variable not set", env.SystemAccessToken),
+		},
+		{
+			name: "missing tenant ID",
+			opts: &Options{
+				ClientID:                          "test-client-id",
+				AzurePipelinesServiceConnectionID: "test-service-connection-id",
+			},
+			systemAccessToken:    "test-system-access-token",
+			systemOIDCRequestURI: "https://test.oidc.request.uri",
+			expectError:          true,
+			expectErrorSubstring: "failed to create azure pipelines credential",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.systemAccessToken != "" {
+				os.Setenv(env.SystemAccessToken, test.systemAccessToken)
+			} else {
+				os.Unsetenv(env.SystemAccessToken)
+			}
+
+			if test.systemOIDCRequestURI != "" {
+				os.Setenv(env.SystemOIDCRequestURI, test.systemOIDCRequestURI)
+			} else {
+				os.Unsetenv(env.SystemOIDCRequestURI)
+			}
+
+			cred, err := newAzurePipelinesCredential(test.opts)
+
+			if test.expectError {
+				require.Error(t, err)
+				assert.Contains(t, err.Error(), test.expectErrorSubstring)
+				assert.Nil(t, cred)
+			} else {
+				require.NoError(t, err)
+				assert.NotNil(t, cred)
+				assert.Equal(t, "AzurePipelinesCredential", cred.Name())
+				assert.False(t, cred.NeedAuthenticate())
+			}
+		})
+	}
+}
\ No newline at end of file
diff --git a/pkg/internal/token/options_test.go b/pkg/internal/token/options_test.go
index f7941e6..b13fc7a 100644
--- a/pkg/internal/token/options_test.go
+++ b/pkg/internal/token/options_test.go
@@ -2,6 +2,7 @@ package token
 
 import (
 	"fmt"
+	"os"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -114,6 +115,131 @@ func TestOptions(t *testing.T) {
 		}
 	})
 
+	t.Run("azurepipelines login method validation", func(t *testing.T) {
+		tests := []struct {
+			name             string
+			setupEnv         func()
+			options          func() Options
+			expectError      bool
+			errorSubstring   string
+		}{
+			{
+				name: "valid azurepipelines login",
+				setupEnv: func() {
+					t.Setenv(env.SystemAccessToken, "test-token")
+					t.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
+				},
+				options: func() Options {
+					o := defaultOptions()
+					o.LoginMethod = AzurePipelinesLogin
+					o.TenantID = "test-tenant"
+					o.AzurePipelinesServiceConnectionID = "test-service-connection"
+					return o
+				},
+				expectError: false,
+			},
+			{
+				name: "azurepipelines missing tenant ID",
+				setupEnv: func() {
+					t.Setenv(env.SystemAccessToken, "test-token")
+					t.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
+				},
+				options: func() Options {
+					o := defaultOptions()
+					o.LoginMethod = AzurePipelinesLogin
+					o.AzurePipelinesServiceConnectionID = "test-service-connection"
+					return o
+				},
+				expectError:    true,
+				errorSubstring: "tenant ID is required for azurepipelines login method",
+			},
+			{
+				name: "azurepipelines missing service connection ID",
+				setupEnv: func() {
+					t.Setenv(env.SystemAccessToken, "test-token")
+					t.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
+				},
+				options: func() Options {
+					o := defaultOptions()
+					o.LoginMethod = AzurePipelinesLogin
+					o.TenantID = "test-tenant"
+					return o
+				},
+				expectError:    true,
+				errorSubstring: "--azure-pipelines-service-connection-id is required for --login azurepipelines",
+			},
+			{
+				name: "azurepipelines missing SYSTEM_ACCESSTOKEN",
+				setupEnv: func() {
+					t.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
+					// Don't set SYSTEM_ACCESSTOKEN
+				},
+				options: func() Options {
+					o := defaultOptions()
+					o.LoginMethod = AzurePipelinesLogin
+					o.TenantID = "test-tenant"
+					o.AzurePipelinesServiceConnectionID = "test-service-connection"
+					return o
+				},
+				expectError:    true,
+				errorSubstring: fmt.Sprintf("environment variable %s not set", env.SystemAccessToken),
+			},
+			{
+				name: "azurepipelines missing SYSTEM_OIDCREQUESTURI",
+				setupEnv: func() {
+					t.Setenv(env.SystemAccessToken, "test-token")
+					// Don't set SYSTEM_OIDCREQUESTURI
+				},
+				options: func() Options {
+					o := defaultOptions()
+					o.LoginMethod = AzurePipelinesLogin
+					o.TenantID = "test-tenant"
+					o.AzurePipelinesServiceConnectionID = "test-service-connection"
+					return o
+				},
+				expectError:    true,
+				errorSubstring: fmt.Sprintf("environment variable %s not set", env.SystemOIDCRequestURI),
+			},
+		}
+
+		for _, test := range tests {
+			t.Run(test.name, func(t *testing.T) {
+				// Clean up environment variables before each test
+				originalSystemAccessToken := os.Getenv(env.SystemAccessToken)
+				originalSystemOIDCRequestURI := os.Getenv(env.SystemOIDCRequestURI)
+				defer func() {
+					if originalSystemAccessToken != "" {
+						os.Setenv(env.SystemAccessToken, originalSystemAccessToken)
+					} else {
+						os.Unsetenv(env.SystemAccessToken)
+					}
+					if originalSystemOIDCRequestURI != "" {
+						os.Setenv(env.SystemOIDCRequestURI, originalSystemOIDCRequestURI)
+					} else {
+						os.Unsetenv(env.SystemOIDCRequestURI)
+					}
+				}()
+
+				test.setupEnv()
+				o := test.options()
+				err := o.Validate()
+
+				if test.expectError {
+					if err == nil {
+						t.Fatalf("expected error but got none")
+					}
+					if !strings.Contains(err.Error(), test.errorSubstring) {
+						t.Fatalf("expected error to contain '%s', got: %s", test.errorSubstring, err.Error())
+					}
+				} else {
+					if err != nil {
+						t.Fatalf("expected no error but got: %s", err)
+					}
+				}
+			})
+		}
+	})
+
 }
 
 func defaultOptions() Options {
diff --git a/pkg/internal/token/provider_test.go b/pkg/internal/token/provider_test.go
index 25857b4..ed88910 100644
--- a/pkg/internal/token/provider_test.go
+++ b/pkg/internal/token/provider_test.go
@@ -5,12 +5,21 @@ import (
 	"testing"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/Azure/kubelogin/pkg/internal/env"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestNewAzIdentityCredential(t *testing.T) {
 	certFile := "fixtures/cert.pem"
 
+	// Set up environment variables for Azure Pipelines test
+	os.Setenv(env.SystemAccessToken, "test-system-access-token")
+	os.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
+	defer func() {
+		os.Unsetenv(env.SystemAccessToken)
+		os.Unsetenv(env.SystemOIDCRequestURI)
+	}()
+
 	tests := []struct {
 		name       string
 		options    *Options
@@ -100,6 +109,17 @@ func TestNewAzIdentityCredential(t *testing.T) {
 			wantErr:    true,
 			errMessage: "unsupported token provider",
 		},
+		{
+			name: "Azure Pipelines login",
+			options: &Options{
+				LoginMethod:                       AzurePipelinesLogin,
+				ServerID:                          "server-id",
+				TenantID:                          "tenant-id", 
+				ClientID:                          "client-id",
+				AzurePipelinesServiceConnectionID: "service-connection-id",
+			},
+			wantErr: false,
+		},
 	}
 
 	for _, tt := range tests {