backstage / backstage/backstage

Handle Azure DevOps Global PAT retirement in Backstage

Open
#35,394 4 comments 0 reactions 0 assignees View on GitHub
area:framework needs:triage type:suggestion
Dominant language
TypeScript
Stars
34.4k
Forks
7.6k
Avg merge
8h 57m
Merged PRs (30d)
50

Description

### 📜 Issue Labels

- [x] Please familiarize yourself with the issue labels used in this project: [LABELS.md](https://github.com/backstage/backstage/blob/master/LABELS.md)

### 🔎 Search Terms

```plain
azure devops global PAT personal access token retirement deprecation credential provider integration
```

### 🗃️ Project Area

Core Framework

### 🔖 Need

Microsoft is [retiring Global Personal Access Tokens (PATs) in Azure DevOps Services](https://devblogs.microsoft.com/devops/retirement-of-global-personal-access-tokens-in-azure-devops/). Global PATs are tokens that authenticate across all organizations a user can access. They stop working on **December 1, 2026**. Organization-scoped PATs and Entra ID-based auth are fine.

This matters for Backstage because the `personalAccessToken` credential in `integrations.azure` is one of the most common ways people set up Azure DevOps integration, and many of those tokens are probably global-scoped since that was the easiest option when creating a PAT.

### Background

When creating a PAT in Azure DevOps, you pick between:
- **All accessible organizations** (Global PAT): one token, works everywhere
- **A specific organization** (Org-scoped PAT): limited to that one org

Microsoft considers global PATs a security risk because a single leaked token exposes every organization the user has access to.

### What's affected in Backstage

`@backstage/integration` provides the `DefaultAzureDevOpsCredentialsProvider`. It supports four credential kinds, but only PATs are affected:

| Credential Kind | Affected? |
|---|---|
| `PersonalAccessToken` | Yes, if the PAT is global-scoped |
| `ClientSecret` (Entra ID service principal) | No |
| `ManagedIdentity` | No |
| `ManagedIdentityClientAssertion` | No |

These packages consume Azure DevOps credentials and will break if a Global PAT is in use:

- `@backstage/backend-defaults` (the `AzureUrlReader` for catalog entity and TechDocs reads)
- `@backstage/plugin-catalog-backend-module-azure` (`AzureDevOpsEntityProvider`, Code Search discovery)
- `@backstage/plugin-scaffolder-backend-module-azure` (the `publish:azure` action)
- `@backstage/plugin-catalog-import` (frontend PR creation, gets tokens via `ScmAuthApi`)

Community plugins that talk to Azure DevOps (like `@backstage-community/plugin-azure-devops`) are likely affected too.

### We can't detect this at runtime

Azure DevOps does have a [PAT Lifecycle Management API](https://learn.microsoft.com/en-us/rest/api/azure/devops/tokens/pats) with a `targetAccounts` field that distinguishes global PATs (`null`) from org-scoped ones (a list of org GUIDs). We looked into using it, but it's not practical:

1. There's no token introspection endpoint. You need an `authorizationId` (UUID) to look up a PAT, and Backstage only has the raw token string.
2. The List endpoint returns all PATs for a user, but with `token: null` in each entry (the raw token is only ever returned at creation time). So you can't match a configured token to any entry in the list.
3. The API requires Entra OAuth with `vso.pats` scope, not PAT-based auth.

### What's not affected

Azure DevOps Server (on-premises) is not part of this retirement. Org-scoped PATs keep working. Entra ID credentials (`ClientSecret`, `ManagedIdentity`, `ManagedIdentityClientAssertion`) are unaffected and are the better long term option anyway. The Backstage credential system already supports per-org credential scoping via the `organizations` config field.

### 📝 Proposal

### 1. Documentation updates

Add a deprecation warning to the PAT section of `docs/integrations/azure/locations.md`:
- Call out the December 1, 2026 deadline
- Recommend Entra ID auth (service principal or managed identity) as the preferred path
- Explain how to use org-scoped PATs with the `organizations` config field for anyone who needs to stay on PATs
- Link to Microsoft's announcement

### 2. Startup warning when PAT credentials are configured

Log a warning when `PersonalAccessToken` credentials are configured for `dev.azure.com`. Since we can't tell whether the PAT is global or org-scoped (see above), the question is when to trigger the warning. Firing it for every PAT credential would be noisy for people who already use org-scoped tokens. A better heuristic: only warn when a PAT credential has no `organizations` field set. If you haven't scoped the credential to specific orgs in Backstage's config, there's a good chance the underlying PAT is global too.

This would go in `@backstage/integration`, probably in `DefaultAzureDevOpsCredentialsProvider.fromIntegrations()` or `readAzureIntegrationConfig()`.

### 3. Better error messages for auth failures

After December 1, 2026, a global PAT will just start failing. The `AzureUrlReader` already handles HTTP 203 specially (Azure returns a sign-in page instead of a 401 when a PAT is invalid). We should update these error paths to mention the global PAT retirement as a possible cause.

### Priority

The deadline is December 1, 2026. There's nothing to build; Backstage already supports org-scoped PATs and Entra ID credentials. This is about warning people before things break.

Global PATs were the path of least resistance, and the Backstage docs never distinguished between global and org-scoped. A lot of people are probably running global PATs without realizing it, and they'll hit auth failures on December 1 with no idea why.

| Date | What happens |
|---|---|
| Now | File this issue, start community awareness |
| Near-term | Doc updates, deprecation warning in code |
| December 1, 2026 | Global PATs stop working |

### References

- [Microsoft: Retirement of Global Personal Access Tokens in Azure DevOps](https://devblogs.microsoft.com/devops/retirement-of-global-personal-access-tokens-in-azure-devops/)
- [Azure DevOps PAT Lifecycle Management API](https://learn.microsoft.com/en-us/rest/api/azure/devops/tokens/pats)
- [Azure DevOps: Use service principals & managed identities](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity)
- [Backstage Azure DevOps integration docs](https://backstage.io/docs/integrations/azure/locations)
- Source: `packages/integration/src/azure/`

### 🔄 Alternatives

**Do nothing** and rely on Microsoft's own communications. The risk is that people don't connect an Azure DevOps announcement to their Backstage config until things break.

**Deprecate PAT support entirely** and require Entra ID auth. Too aggressive. Org-scoped PATs are still valid, and they're the only option for Azure DevOps Server (on-premises).

**Try to detect global PATs at runtime.** We investigated this (see "We can't detect this at runtime" above). The Azure DevOps PAT API doesn't support looking up a token by its string value, so there's no way to check.

### Have you read the Code of Conduct?

- [x] I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)

### Are you willing to submit a PR?

No, but I'm happy to collaborate on a PR with someone else

Contributor guide

Open the contributing guide

Research direction

Start with docs/integrations/azure/locations.md and the source under packages/integration/src/azure/, especially DefaultAzureDevOpsCredentialsProvider.fromIntegrations() and readAzureIntegrationConfig(). Then inspect the AzureUrlReader's HTTP 203 and authentication error paths and the listed Azure-consuming packages. Done means the PAT retirement guidance, unscoped-PAT warning, and relevant auth-failure messaging are covered without affecting other credential kinds.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, typescript
Domain
authentication, developer-experience, documentation
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
67/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.