backstage-auth

backstage-auth is a skill for Claude Code from bendaamerahmed/backstage-idp-plugin. It costs 43 tokens per session (3,809 once invoked), scanned A, original, MIT.

A skill for configuring and troubleshooting Backstage login and identity features. Backstage is an internal developer portal, and the skill covers its login providers, user mapping, tokens, and OAuth security settings.

In plain words
What is it for?
Setting up or debugging Backstage authentication, sign-in pages, user-to-catalog mappings, service tokens, OAuth redirect addresses, and approved client metadata lists.
Why use it?
It helps identify which part of a login setup is failing and avoids confusion between signing users in and allowing access to outside services.

Skill for Claude Code

Written for Claude Code: when-to-use in frontmatter.

Part of the backstage-idp plugin — 15 skills, 1 agent shipped together

Good fit Setting up or debugging Backstage authentication, sign-in pages, user-to-catalog mappings, service tokens, OAuth redirect addresses, and approved client metadata lists.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/bendaamerahmed/backstage-idp-plugin/backstage-auth
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Any agent
npx skills add bendaamerahmed/backstage-idp-plugin --skill backstage-auth
Clone the repo
git clone --depth 1 https://github.com/bendaamerahmed/backstage-idp-plugin

Made for: Claude Code.

Or install backstage-idp, the plugin that ships this one along with the rest of its 15 skills, 1 agent.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for backstage-auth

README.md
[![agentmods](https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-auth/github.svg)](https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-auth)
Your own site
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-auth"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-auth/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for backstage-auth

Your own site · 80×15
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-auth"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-auth.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 43 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 3,809 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 1 finding. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00043 $0.03809
Opus 5 $0.00022 $0.01904
Sonnet 5 $0.00009 $0.00762
Haiku 4.5 $0.00004 $0.00381

Measured 12d ago against content hash 0a3b611702f4, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-12, from the pricing page.

Security

Grade A, and why

backstage-auth scanned grade A with 1 finding against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 12d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Makes network callslowCapability

Not a fault in itself. Listed so you know the mod talks to something, and to what.

- For external access, `curl -H "Authorization: Bearer <token>" <backend>/api/<plugin>/...` — a 401 means the token or its `accessRestrictions` is wrong, not the plugin.
plugins/backstage-idp/skills/backstage-auth/SKILL.md · 103 lines

How it starts

The opening of the file, as written. The whole thing — 103 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Backstage Authentication and Identity

Wire an identity provider into Backstage, map its users onto catalog entities, and tell apart the five ways sign-in fails.

Preconditions

  • Release line from backstage.json. Backend generation: createBackend() + backend.add(import('@backstage/plugin-auth-backend')) is the new backend system; a createRouter in packages/backend/src/plugins/auth.ts is legacy — migrate it before adding auth modules.
  • Frontend generation: SignInPageBlueprint from @backstage/plugin-app-react + createFrontendModule is NFS; a components: { SignInPage } option on createApp from @backstage/app-defaults is legacy.
  • Which provider is meant for sign-in and which only delegates access to third-party APIs. These are different jobs on the same config tree and most misconfigurations start here.
  • Exact factory and resolver signatures (createOAuthProviderFactory, createProxyAuthProviderFactory, authProvidersExtensionPoint, the ctx helpers) read from the installed @backstage/plugin-auth-node types, not from memory.
  • Anything requiring a new OAuth app registration, a client-secret rotation, or an IdP-side change is external mutation: stop and return a BLOCKED report with the exact redirect URI and scopes needed.

Procedure

  1. Inventory before changing. Read every auth: block across app-config.yaml, app-config.production.yaml and local overrides, the auth imports in packages/backend/src/index.ts, and the app's sign-in component. Print the effective merge with yarn backstage-cli config:print --lax. Note auth.environment — only the provider sub-block matching it is loaded.
  2. Classify each provider. A provider is a sign-in provider only if it has signIn.resolvers in config or a signInResolver in code. Providers used purely for API delegation (SCM tokens, Google APIs) are configured under auth.providers.<id>.<env> with client credentials and no resolver, and are consumed in the frontend through their *AuthApiRef. Adding a resolver to a delegation-only provider silently creates a second sign-in path.
  3. Configure the provider block and register its module.
    • Credentials live at auth.providers.<id>.<env>.clientId / clientSecret, sourced from env vars.
    • callbackUrl is optional and defaults to <backend.baseUrl>/api/auth/<id>/handler/frame. Set it explicitly only when an ingress rewrites the path.
    • Add backend.add(import('@backstage/plugin-auth-backend-module-<id>-provider')) after @backstage/plugin-auth-backend. Confirm the package name and its exports from the installed package; provider ids and module names do not always match the vendor's name.
  4. Ingest users and groups before wiring a resolver. Every built-in resolver is a catalog lookup and cannot work against an empty catalog. Use an org provider — @backstage/plugin-catalog-backend-module-github-org (config under catalog.providers.githubOrg), or the MS Graph / GitLab / LDAP equivalents — or a custom EntityProvider (backstage-catalog). Verify the User entities exist before touching sign-in config.
  5. Choose exactly one sign-in resolver, in the provider's signIn.resolvers list.
    • Provider-agnostic built-ins: emailMatchingUserEntityProfileEmail, emailLocalPartMatchingUserEntityName. Provider-specific ones such as GitHub's usernameMatchingUserEntityName are listed in that provider's doc page.
    • Always set allowedDomains on emailLocalPartMatchingUserEntityName; without it any account whose email local part matches an entity name can sign in.
    • More than one resolver, or more than one sign-in provider, is acceptable only when they provably resolve the same human to the same entity ref. Otherwise it is an account-takeover path.
  6. Write a custom resolver only when no built-in fits.
    • Backend module with pluginId: 'auth' (exact) and a unique moduleId, registering through authProvidersExtensionPoint with providerId matching the config key exactly.
    • Pass signInResolver to createOAuthProviderFactory({ authenticator, ... }); proxy-based providers use the proxy factory instead. Read both signatures from the installed @backstage/plugin-auth-node.
    • Remove signIn.resolvers from config first: config resolvers take priority over code resolvers, so leaving them in makes the module dead code.
    • Prefer ctx.signInWithCatalogUser. Drop to ctx.findCatalogUser + ctx.resolveOwnershipEntityRefs + ctx.issueToken({ claims: { sub, ent } }) only for custom ownership. resolveOwnershipEntityRefs includes only groups with a direct MEMBER_OF relation by default — the usual cause of missing ownership in nested group trees (backstage-permissions).
  7. Use profileTransform on the same factory for authorization and display fields. That is where you validate the upstream response, shape displayName / email / picture, and throw to reject a user outright — not the resolver.
  8. Wire the app-side sign-in page.
    • NFS: SignInPageBlueprint.make({ params: { loader: async () => props => <SignInPage {...props} provider={{ id, title, message, apiRef }} /> } }), registered via createFrontendModule({ pluginId: 'app', extensions: [signInPage] }).
    • SignInPage and ProxiedSignInPage come from @backstage/core-components; the providers array form accepts 'guest' alongside provider configs, and can be selected conditionally off configApi.getString('auth.environment').
    • Behind an auth proxy (AWS ALB, Azure EasyAuth, Cloudflare Access, Google IAP, OAuth2 Proxy) use ProxiedSignInPage provider="<id>"; it only calls /refresh. Extra provider headers go through its headers prop, which may be sync or async.
    • enableExperimentalRedirectFlow: true at the config root replaces the popup with a full redirect.
  9. Keep guest access out of production. auth.providers.guest: {} is development-only; userEntityRef and ownershipEntityRefs customise the identity. dangerouslyAllowOutsideDevelopment: true is required anywhere else — never set it without explicit authorization.
  10. Attach tokens correctly in frontend plugin clients.
    • Calls to Backstage backends: use fetchApiRef from @backstage/core-plugin-api, which adds the Backstage token itself. Do not read identityApiRef.getCredentials() and build the header by hand. Resolve base URLs through discoveryApiRef.
    • Calls to third-party services: take a short-lived token from that provider's *AuthApiRef (githubAuthApiRef and friends) with the scopes actually needed, or from scmAuthApiRef when the target host varies. Extend SCM coverage with ScmAuth.merge(ScmAuth.forGithub(...)) in the app's API factories.
  11. Use the auth core services for backend-to-backend calls.
    • httpAuth.credentials(req) turns an incoming request into credentials; auth.getPluginRequestToken({ onBehalfOf, targetPluginId }) mints the outgoing token; await auth.getOwnServiceCredentials() supplies onBehalfOf for self-initiated work such as scheduled tasks.
    • Mint a token immediately before each request. Never store or reuse one.
    • Narrow principals with auth.isPrincipal(credentials, 'user' | 'service').
    • In the new backend system ownership refs are not read off the token's ent claim; get them from the userInfo service.
  12. Grant non-browser callers access through backend.auth.externalAccess.
    • type: static with options.token and options.subject; generate the token with node -p 'require("crypto").randomBytes(24).toString("base64")'.
    • type: jwks with url, issuer, algorithm, audience, subjectPrefix; verified subjects get an external: prefix.
    • Always add accessRestrictions (plugin, permission, permission attribute). An entry without them has unlimited access to every plugin.
    • backend.auth.pluginKeyStore static ES256 keys are for multi-replica deployments needing stable plugin-to-plugin signing.
  13. Treat the OAuth allowlists as hardened. These apply where Backstage acts as an OAuth server for MCP clients.
    • CIMD is stable: auth.clientIdMetadataDocuments.enabled: true, with optional allowedClientIdPatterns and allowedRedirectUriPatterns. auth.experimentalClientIdMetadataDocuments survives as a deprecated alias.
    • The block belongs under the top-level auth:, never backend.auth:.
    • Patterns are matched per URL component, not against the whole URL string. Wildcards do not cross host or path boundaries; a pattern without an explicit protocol is rejected; redirect URIs with embedded credentials are always rejected; a wildcard port no longer implies any path, so write http://localhost:*/*, http://127.0.0.1:*/*, http://[::1]:*/*.
    • Setting allowedClientIdPatterns replaces the built-in Claude and VS Code defaults entirely; the Backstage CLI client stays allowed regardless.
    • CIMD requires the new frontend system plus the @backstage/plugin-auth frontend plugin in packages/app. On a legacy app it cannot work at all.
    • auth.experimentalDynamicClientRegistration (DCR) is deprecated, logs a startup warning, and must not be used for new setups. Migrate existing ones to CIMD.
  14. Pin signing keys when tokens must survive restarts. auth.keyStore.provider: static with ES256 keys (privateKeyFile in PKCS#8, publicKeyFile in SPKI; the first key signs, later keys only validate, which is what makes rotation safe). The default is ephemeral in-memory keys.

Read the full file on GitHub · 103 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 12d ago First seen · 103 lines · 43 tokens per session scan A 0a3b611702f4

Subscribe to this mod's changes

backstage-auth is a skill published in the GitHub repository bendaamerahmed/backstage-idp-plugin (1 stars, last pushed 1mo ago), licensed MIT. It adds 43 tokens to every session and 3,809 once invoked, about $0.0002 per session on Opus 5. A static security scan graded it A with 1 finding (makes network calls). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.

Related

Other skills, from other repositories

ash-framework

Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.

oliver-kriska/claude-elixir-phoenix · 48 tokens

deploy

Elixir/Phoenix deployment patterns — Dockerfile, fly.toml, runtime.exs, mix release, rel/ overlays. Use when configuring Fly.io, Docker, CI/CD, health checks, or production migrations.

oliver-kriska/claude-elixir-phoenix · 46 tokens

liveview-patterns

Build LiveView: async data (assignasync), PubSub (check connected?), phx-change events, form components/modals/uploads, streams for lists, livepatch. Use when handling interactions, debugging events, or tracking Presence.

oliver-kriska/claude-elixir-phoenix · 51 tokens

tidewave-integration

Tidewave MCP runtime tools — debugging, smoke testing, live state inspection, SQL queries, hex docs. Use when evaluating code in a running Phoenix app.

oliver-kriska/claude-elixir-phoenix · 38 tokens

oban

Oban job processing — workers, perform/1 (OSS) and process/1 (Pro), queues, cron, retries, unique jobs, idempotency, Oban Pro (Workflow, Batch, Chunk, Smart Engine), Testing. Use when writing Oban workers, queue config, or debugging jobs.

oliver-kriska/claude-elixir-phoenix · 64 tokens

perf

Analyze Elixir/Phoenix performance — N+1 queries, assign bloat, ecto optimization, genserver bottlenecks. Use when slowness, timeouts, or high memory reported.

oliver-kriska/claude-elixir-phoenix · 43 tokens