Connecting Backstage to external services securely: forwarding requests through the /api/proxy backend proxy to backend services or API gateways, managing the token lifecycle with short-lived tokens and refresh, handling OIDC sessions, and rotating credentials regularly.

In episode 13, you locked down internal access with the permission framework and RBAC. Episode 14 reverses the direction: from protecting inward to connecting outward. A useful developer portal almost always needs to pull data from other systems — CI/CD, monitoring, ticketing, or internal services. The problem: connecting systems means sharing credentials, and leaked credentials are the biggest entry point. This episode covers both sides at once: how to forward requests to external services through a backend proxy, and how to keep credentials secure throughout their lifecycle.
The most common way Backstage communicates with external services is through the backend proxy. Rather than the browser calling an external service directly — which would expose credentials client-side — requests are sent to Backstage's /api/proxy/... endpoint, and the backend forwards the request to the real target. This feature is implemented via @backstage/plugin-proxy-backend.
proxy:
endpoints:
'/github':
target: 'https://api.github.com'
changeOrigin: true
headers:
Authorization: Bearer ${GITHUB_TOKEN}On the frontend side, you just call /api/proxy/github, and Backstage handles the authentication to GitHub. Credentials never leave the backend. The changeOrigin keyword ensures the host header follows the target, and credentials are pulled from environment variables so they aren't written in the repo.
| Purpose | Target | Notes |
|---|---|---|
| Pipeline status | CI/CD system | Data read, rarely written |
| Service metrics | Monitoring or observability | Query with parameters |
| Tickets and issues | Service desk or issue tracker | Needs minimal token scope |
| Provisioning | Internal API or API gateway | The most sensitive path |
The key pattern: the backend proxy makes Backstage the only party holding credentials, and each endpoint can be locked down with permissions from episode 13 — so it's not just about the network, but also about who is allowed to call which endpoint.
Besides choosing the target, decide the method each endpoint uses: read-only for sources that are only read, and dedicated paths for operations that write. By separating read and write endpoints, you can give each a different token scope — and shrink the blast radius if one token leaks.
Warning
Never call an external service directly from the browser with a token
injected through JavaScript. A token in the browser can be read by anyone
who opens that page. Always go through /api/proxy so credentials
only live on the backend side.
When choosing a proxy target, you have two common options:
An API gateway adds one hop, but brings benefits: traffic control, centralized logging, and the ability to swap the backend behind the gateway without changing the Backstage proxy config. For integrations into third-party systems, a gateway is almost always the safer choice.
Before wiring up the UI, test the proxy endpoint from the terminal so network and credential problems show up first:
curl -s http://localhost:7007/api/proxy/github/rate_limitIf a response comes back from GitHub, the proxy flow, token, and routing are correct. If not, check in order: backend logs to confirm the request arrived, the target config to confirm the URL, then the token to confirm its scope is sufficient. The habit of testing from the terminal saves far more debugging time than you'd expect.
Run this testing from the same environment as production — network differences, outbound proxies, and firewalls are hidden causes that only surface at deploy time.
Sending a token in a config is easy; keeping it secure for its whole life is the hard part. The recommended token model follows the access token and refresh token pattern:
The flow: when an access token expires, Backstage uses the refresh token to get a new access token, and the refresh token itself lives long — so the integration keeps running without human intervention. All these values are stored as secrets (see episode 9), not in a committed app-config.yaml.
| Token type | Lifetime | Function |
|---|---|---|
| Access token | Minutes to hours | Used for each request |
| Refresh token | Days to weeks | Only exchanges for a new access token |
| Static API key | Stays until rotated | Best avoided when an alternative exists |
Many external services use OIDC for service-to-service authentication. Backstage handles this through OIDC sessions: when the backend calls an OIDC service, it stores a session containing the token and refreshes it when the token is close to expiring.
What needs guarding is session validity: an expired session must be refreshed or deleted, and a session that fails validation must be treated as invalid, not forwarded as-is. The configured scope and audience must match what the target requires, and session secrets must be stored securely so they can't be hijacked to issue tokens on Backstage's behalf.
Good logging also helps: record when a token is refreshed, when a session is terminated, and when validation fails. Recurring failure patterns are often an early sign of problems with the scope config or clock skew between Backstage and the OIDC provider.
No matter how securely tokens are stored, a token that's never replaced is a risk that accumulates. Credential rotation — replacing credentials periodically — is a mandatory habit for production integrations. Some plugins automate this rotation for certain credential types, such as GitHub App tokens or service accounts.
rotate-credentials --provider github-app
verify --provider github-appRotation can run on a schedule, be triggered before a token expires, or be manual when a leak is suspected. Ideally, rotation doesn't take down services: Backstage keeps using the old token for a while until the new one is verified, then switches over smoothly. Keep a rotation history in the logs — when credentials were replaced and by whom — so you know which are still active and which are no longer used.
Episode 14 connected Backstage with the outside world securely: forwarding requests through the backend proxy, choosing between a backend service and an API gateway, managing the token lifecycle with the short-lived and refresh pattern, handling OIDC sessions, and rotating credentials regularly. Your portal is now not only protected on the inside, but can also interact with the ecosystem around it.
The key takeaways:
/api/proxy — never expose credentials to the browser.In the next episode, episode 15, you secure the deployment itself: deployment security & hardening — reverse proxy and TLS, trusted proxies, CSP, secure cookies, and the principle of least privilege for all integrations. It's time to make your Backstage instance battle-ready in production.