PlatformEngr® Backstage course

Module 05 · The portal and its catalog · 45 min · You are: Platform team

Sign in with GitHub

In this module

  • You learn: How an auth provider, a sign-in resolver and a User entity fit together, and how the new frontend system swaps the sign-in page with a module.
  • You do: Create a GitHub OAuth app, configure the GitHub provider with a resolver, add a sign-in page module to the app, and sign in as yourself.
  • You end with: The portal only lets in people who have a User entity. You sign in with GitHub and the portal knows you are in platform and team-checkout.

With guest sign-in everyone is user:development/guest, so "my services", ownership and permissions mean nothing. Real sign-in turns a GitHub login into a catalog User, and from there into groups. The catalog becomes the source of who you are, which is why the User entity has to exist first.

Open this module in the interactive course to track progress.

Concepts

Auth provider
Backend module that talks to an identity provider (GitHub, Entra ID, Okta). Configured under auth.providers.<name>.<environment>.
Sign-in resolver
Maps the provider's identity to a User entity. usernameMatchingUserEntityName looks for a User named like the GitHub username. No match, no sign-in.
Ownership entities
The user plus the groups they are a member of. Backstage puts them in the token; "owned by me" and permissions read them.
Frontend module
In the new frontend system, a module overrides extensions of an existing plugin. The sign-in page is an extension of the app plugin.

Files to know

Open them in your editor or on GitHub; the bullets say what to look at.

Steps

1. Create a GitHub OAuth app

GitHub needs to know where to send people back after they log in.

1. Open the link above (GitHub, Settings → Developer settings → OAuth Apps → New OAuth App). 2. Application name: Acme Portal (local). Homepage URL: http://localhost:3000. 3. Authorization callback URL: http://localhost:7007/api/auth/github/handler/frame. The backend handles the callback, so it is port 7007. 4. Register application. Copy the Client ID, then Generate a new client secret and copy it: GitHub shows it once.

Add to ~/work/portal/.env at the end

AUTH_GITHUB_CLIENT_ID=paste-the-client-id
AUTH_GITHUB_CLIENT_SECRET=paste-the-client-secret

New OAuth App on GitHub ↗

2. Configure the provider

Replace guest with GitHub. The resolver is the important line: it decides who may come in.

Change in ~/work/portal/app-config.yaml
Under auth:.

-   providers:
-     # See https://backstage.io/docs/auth/guest/provider
-     guest: {}
+   environment: development
+   providers:
+     # See https://backstage.io/docs/auth/github/provider
+     github:
+       development:
+         clientId: ${AUTH_GITHUB_CLIENT_ID}
+         clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
+         signIn:
+           resolvers:
+             # Your GitHub username must match a User entity name.
+             - resolver: usernameMatchingUserEntityName

Add to ~/work/portal/packages/backend/src/index.ts after backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
The package is already a dependency of the backend.

backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));

3. Swap the sign-in page

The default page only offers guest. A frontend module replaces the app plugin's sign-in page extension with one that offers GitHub.

Create ~/work/portal/packages/app/src/modules/signIn/index.tsx

import { SignInPage } from '@backstage/core-components';
import {
  createFrontendModule,
  githubAuthApiRef,
} from '@backstage/frontend-plugin-api';
import { SignInPageBlueprint } from '@backstage/plugin-app-react';

// Replaces the app's default sign-in page (guest only) with GitHub.
export const signInModule = createFrontendModule({
  pluginId: 'app',
  extensions: [
    SignInPageBlueprint.make({
      params: {
        loader: async () => props => (
          <SignInPage
            {...props}
            providers={[
              {
                id: 'github-auth-provider',
                title: 'GitHub',
                message: 'Sign in with your GitHub account',
                apiRef: githubAuthApiRef,
              },
            ]}
          />
        ),
      },
    }),
  ],
});

Change in ~/work/portal/packages/app/src/App.tsx

- import { homeModule } from './modules/home';
+ import { homeModule } from './modules/home';
+ import { signInModule } from './modules/signIn';

Change in ~/work/portal/packages/app/src/App.tsx

-   features: [catalogPlugin, navModule, homeModule],
+   features: [catalogPlugin, navModule, homeModule, signInModule],

4. Sign in as yourself

Restart the portal (Ctrl+C) so it reads the new environment variables.

cd ~/work/portal && set -a && . ./.env && set +a
yarn start

1. Open http://localhost:3000. The page now says Sign in with your GitHub account. 2. Click Sign in. A GitHub pop-up asks you to authorise Acme Portal (local); accept. 3. Open Settings at the bottom of the sidebar. User Entity shows user:default/YOUR_GITHUB_USER. Ownership Entities lists you, group:default/platform and group:default/team-checkout. 4. Open the catalog and pick Owned: you see what your teams own.

5. Ship it

cd ~/work/portal && git status --short

Expect app-config.yaml, App.tsx, the backend index.ts and the new modules/signIn folder. Not .env.

Propose it as a pull request. Put it on its own branch and push it:

git switch -c github-sign-in && git add -A && git commit -qm "Auth: sign in with GitHub"
git push -qu origin HEAD

Open the pull request. It prints a link:

gh pr create --fill

Open that link in your browser. Files changed shows exactly your diff.

Merge it on GitHub (Squash and merge, then Delete branch), or from the terminal:

gh pr merge --squash --delete-branch

Either way, bring your copy up to date:

git switch -q main && git pull -q

Simplified for the course

One OAuth app for localhost. Production gets its own OAuth app (or a GitHub App) with the real URL, and usually a company identity provider (Entra ID, Okta) with an org provider that imports the same people as Users.

Sign-in is a door, not a lock: with the allow-all permission policy every signed-in user can do everything. Permission policies are covered in the plugins course.

Break it on purpose: A new colleague cannot get in

Someone tidies org.yaml and renames your User to a "nicer" name. The catalog accepts it. The next morning, you sign out and in again.

You practise: how sign-in depends on the catalog, and where the resolver's failure shows up.

1. Break it

Change in ~/work/portal/catalog/org.yaml
In the User at the end of the file.

-   name: YOUR_GITHUB_USER
- spec:
+   name: YOUR_GITHUB_USER-renamed
+ spec:

Wait half a minute for the catalog to read the file. In the portal: Settings → Sign out, then sign in with GitHub again.

2. What you see

Expected: you are back in the portal. You get: Failed to sign-in, unable to resolve user identity. Please verify that your catalog contains the expected User entities that would match your configured sign-in resolver.

3. Fix it yourself

Work out which User entity the resolver looked for and why it did not find it. Fix it and sign in.

Hint 1

The resolver is usernameMatchingUserEntityName. Which name does it look for, and which Users does the catalog have?

Hint 2

List Users with the catalog API (filter=kind=user) and compare with your GitHub username.

The fix

Change in ~/work/portal/catalog/org.yaml

-   name: YOUR_GITHUB_USER-renamed
+   name: YOUR_GITHUB_USER

Expect your GitHub username:

curl -s -H "Authorization: Bearer $PORTAL_API_TOKEN" "localhost:7007/api/catalog/entities?filter=kind=user" | jq -r '.[].metadata.name'

Sign in again.

After you fixed it: the five questions
What failed?
No User entity matched the GitHub username, so the resolver refused the sign-in.
How was it detected?
By the person signing in, with an error that points at the catalog.
Who responds?
The platform team: they own org.yaml and the resolver.
How is it corrected?
Make the User entity name match the GitHub username again.
How is it prevented?
Import users with an org provider instead of typing them, and review org.yaml changes in pull requests with CODEOWNERS.

Quiz

  1. What does the sign-in resolver decide?
    • Which catalog User a provider identity becomes, and so whether the person may sign in at all
    • Which OAuth scopes GitHub asks the user for
    • Which frontend page the user sees first
  2. Why is the OAuth callback URL on port 7007?
    • GitHub only allows ports above 7000
    • The auth backend plugin finishes the OAuth flow, and the backend listens on 7007
    • The frontend proxies all of /api to GitHub
  3. How did you replace the sign-in page?
    • By editing a file in node_modules
    • By setting app.signInPage in app-config.yaml
    • With a frontend module that overrides the app plugin's sign-in page extension
Answers
  1. Which catalog User a provider identity becomes, and so whether the person may sign in at all No matching User, no sign-in.
  2. The auth backend plugin finishes the OAuth flow, and the backend listens on 7007 The frontend opens a pop-up; the backend handles the redirect.
  3. With a frontend module that overrides the app plugin's sign-in page extension In the new frontend system, modules override extensions of existing plugins.

Field note

Most "Backstage login is broken" tickets I have seen were catalog problems: a missing User, a renamed one, or an org import that failed quietly. Check the catalog before you check OAuth.

Report a problem with this module