Skip to main content

GitHub SSO for AlmaForge

AlmaForge can authenticate users against GitHub and map GitHub organization teams to AlmaForge roles. GitHub is not a generic OIDC provider. It uses a plain OAuth2 flow with a provider-specific team membership API, so AlmaForge represents GitHub integrations as a dedicated resource kind rather than reusing OIDCConnector. The integration is built into almad. Configure it with a GithubConnector resource.

Different integration: GitHub SSH CA

This page is about authenticating AlmaForge users with GitHub as the SSO provider. If you are here because you want to use AlmaForge as the SSH Certificate Authority for a GitHub organization (so engineers push and pull git over SSH with short-lived AlmaForge certificates instead of uploading personal SSH keys to github.com), see GitHub SSH CA. The two integrations solve different problems and can run side by side on the same organization.

TL;DR​

  1. Create a GitHub OAuth app under the organization and copy the client ID and secret (Step 1).
  2. Write a GithubConnector YAML with the org slug and team slugs (Step 2).
  3. Run alma apply -f github.yaml.
  4. Verify by logging in and picking GitHub on the sign-in page (Step 3).

Prerequisites​

Before you start, make sure you have:

  • A running AlmaForge cluster with an Auth Service that you can reach over the network and a working alma CLI pointed at it.
  • A GitHub organization with at least one team. Personal GitHub accounts without an org cannot be used.
  • A supported GitHub deployment:
    • GitHub Cloud (github.com).
    • GitHub Enterprise Server.
  • If your GitHub organization already authenticates through another SSO provider, this integration still works. If that upstream provider is your source of truth for users and groups, AlmaForge should usually connect to that provider directly instead of chaining through GitHub.
  • Permission to create and manage an OAuth app that is controlled by the organization. Do not use an OAuth app that is owned only by one engineer's personal GitHub account. See the warning in Step 1 for why.
  • Outbound HTTPS egress from almad to the following hosts from every auth node:
    • GitHub Cloud: github.com and api.github.com (TCP/443).
    • GitHub Enterprise Server: the hostnames you plan to use for endpointURL and apiEndpointURL.
  • The public address of your AlmaForge Proxy Service, for example almaforge.example.com.
  • Recommended: read Break-glass access before you apply this connector on a production cluster.

Configuration​

Step 1. Create a GitHub OAuth app​

Sign in to GitHub as an org owner and open Settings → Developer settings → OAuth Apps → New OAuth App. Fill in:

GitHub OAuth apps settings

  • Application name: something recognizable, for example AlmaForge.
  • Homepage URL: https://almaforge.example.com.
  • Application description: GitHub sign-in for AlmaForge.
  • Authorization callback URL: https://almaforge.example.com/api/v1/github/callback, substituting your own proxy host.
  • Enable Device Flow: leave unchecked.

Click Register application.

Registering a GitHub OAuth app for AlmaForge

On the new application's page, copy the Client ID and click Generate a new client secret to produce a Client Secret. Copy the secret immediately. GitHub will not show it again after you leave the page.

Treat the client secret as a password

Anyone with the client secret can impersonate AlmaForge to GitHub's OAuth endpoints. Store it in the same secret manager you use for production credentials.

GitHub OAuth app credentials

Keep the OAuth app under company control

Do not let this integration depend on an OAuth app that exists only under one engineer's personal GitHub account. When that person leaves the company, loses org access, or rotates ownership, the app disappears and every SSO login through it fails with no warning. Create the OAuth app under the organization (or under a company-owned user account with a documented successor) and record the owner in your runbook.

If you want GitHub's own click path for this screen, see Creating an OAuth app.

Step 2. Configure a GithubConnector​

Write a YAML file describing the connector:

github.yaml:

examples/sso/github.yaml
# GitHub OAuth connector with organization team-to-role mappings.
apiVersion: almaforge.com/v1
kind: GithubConnector
metadata:
name: github
spec:
display: GitHub
clientID: <client-id>
clientSecret: <client-secret>
teamsToRoles:
- organization: example-org
team: sre
roles: [admin, access]
- organization: example-org
team: developers
roles: [access]
- organization: example-org
team: interns
roles: [access]

Replace:

  • <client-id> and <client-secret> with the values from Step 1.
  • organization with your GitHub org's slug. This is the value that appears in the URL when you visit https://github.com/orgs/<org>. Org display names with spaces, capital letters, or Unicode characters will not work. Always use the slug.
  • team with the GitHub team's slug, for the same reason. A team called Site Reliability has the slug site-reliability. Read it off the URL https://github.com/orgs/<org>/teams/site-reliability.

Each teamsToRoles entry assigns a list of AlmaForge roles to every member of the named team. You can list multiple entries for the same org or across different orgs. A user who is in several listed teams receives the union of the roles.

GitHub Enterprise Server​

If you use GitHub Enterprise Server, also set:

github-enterprise.yaml:

examples/sso/github-enterprise.yaml
# GitHub Enterprise Server connector with organization team mappings.
apiVersion: almaforge.com/v1
kind: GithubConnector
metadata:
name: github
spec:
display: GitHub Enterprise
endpointURL: https://github.example.com
apiEndpointURL: https://github.example.com/api/v3
clientID: <client-id>
clientSecret: <client-secret>
teamsToRoles:
- organization: example-org
team: sre
roles: [admin, access]

endpointURL is the base URL of your GHES installation. apiEndpointURL is the REST API base. On most GHES installations, it is <endpoint>/api/v3. The callback URL is still /api/v1/github/callback. Update the egress list to use the GHES hostnames instead of github.com and api.github.com.

Use slugs, not display names

GitHub organization names and team names in the connector must be slugs, not the display names shown in the UI. GitHub lowercases the name, normalizes special characters, and replaces spaces with -. For example, My Team Näme becomes my-team-name. The GitHub web UI URL or the GitHub API is the easiest way to confirm the slug.

Test break-glass before applying

AlmaForge has no local accounts. If this is the only connector, a wrong claim mapping or expired client secret prevents new logins. Confirm your break-glass procedure works before applying the connector to a production cluster.

Apply the connector:

Terminal
alma apply -f github.yaml

Step 3. Verify the connector​

Open the cluster's audit log for your test account. See SSO verification for event fields and the distinction from service logs.

Sign out and log in through GitHub:

Terminal
alma logoutalma login --proxy=almaforge.example.com

A browser opens the cluster's sign-in page. Choose GitHub. If this is the user's first login through the app, GitHub shows a consent screen requesting access to read:org and lets the user choose which organizations to grant. After approval, GitHub redirects back to the proxy and alma prints the assigned roles.

read:org lets AlmaForge read the user's GitHub organization and team membership. You do not configure that scope on the OAuth app page. AlmaForge requests it during sign-in, and GitHub shows it on the consent screen.

A successful first-time login emits a UserLogin event and the CertificateCreate event for the certificate it mints:

JSON
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"id": "9b4d2e71-0c83-4a15-9e26-3f7a8b9c0d1e",
"index": 0,
"timestamp": "2026-03-30T15:58:58.999000Z",
"clusterName": "almaforge.example.com",
"code": "T1001I",
"type": "UserLogin",
"verb": "login",
"actor": {
"name": "alice",
"type": "user"
},
"status": {
"success": true
},
"userLogin": {
"method": "github",
"attributes": {
"connector_id": "github",
"connector_type": "github",
"github_org_to_teams": {
"example-org": [
"sre",
"developers"
]
},
"github_teams": [
"sre",
"developers"
],
"github_username": "alice",
"username": "alice"
}
}
}
}
JSON
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"id": "4e5f6a7b-8c9d-40e1-a2b3-c4d5e6f70819",
"index": 0,
"timestamp": "2026-03-30T15:58:58.999000Z",
"clusterName": "almaforge.example.com",
"code": "TC000I",
"type": "CertificateCreate",
"verb": "create",
"certificateCreate": {
"certType": "user",
"identity": {
"user": "alice",
"roles": [
"access"
],
"logins": [
"tester",
"ec2-user",
"root",
"alice"
]
}
}
}
}

The key field to check is github_teams. It lists the team slugs GitHub returned for the user, filtered by the organizations GitHub itself exposed to the OAuth app. Each value in your teamsToRoles entries must match one of these slugs verbatim. github_org_to_teams shows the same slugs grouped by organization. If github_teams is empty or missing the team you expect:

  • The user skipped the org on the GitHub consent screen. Have them revisit Settings → Applications → Authorized OAuth Apps on GitHub and grant access.
  • The org has OAuth app access restrictions enabled and the AlmaForge app has not been approved. An org owner needs to grant the app access under Organization settings → Third-party access → OAuth app policy.
  • The organization or team value in the connector is a display name rather than a slug.

After a successful login, you can also run alma get user/<username> -o yaml and inspect spec.traits.teams.

Read the full audit-event breakdown on the generic OIDC verification section. The event codes and structure are the same for GithubConnector as for OIDCConnector.

If UserLogin shows success:true and lists the expected team slugs under github_teams, the integration is working. For running it in production, see Operations.

Troubleshooting​

Start with the exact GitHub error or the login symptom you see.

The redirect_uri is not associated with this application.​

GitHub returns The redirect_uri is not associated with this application. when the Authorization callback URL in the OAuth app differs from the URL AlmaForge sends as redirect_uri. AlmaForge sends https://<proxy_public_addr>/api/v1/github/callback with port 443 omitted. Register that exact string in the GitHub OAuth app and retry.

No roles match the login​

The GitHub sign-in completed, but AlmaForge rejects the login when no team mapping grants a role. Either the user is not a member of any listed team, or the organization and team fields in teamsToRoles are not slugs. Visit https://github.com/orgs/<org>/teams/<team> and verify the URL. The last two path segments are the values you should put in the connector. Confirm the user actually sees their teams on the GitHub consent screen. Public and private teams both work here, as long as the user is actually a member and GitHub returns that membership during login. Team changes affect the next login. See Revocation window for existing credentials. If the login succeeds but the stored identity still looks wrong, inspect the user record as described in Claims, traits, and role templates.

The org has OAuth app access restrictions enabled, and the AlmaForge OAuth app has not been approved. An org owner needs to grant the app access under Organization settings → Third-party access → OAuth app policy on GitHub. Affected users see the org listed on the consent screen with a "Request" button instead of "Grant".

GitHub returns 401 Bad credentials​

401 Bad credentials from the GitHub API in the auth service logs. The client ID and client secret in the connector do not match what GitHub has on file. Re-copy both values from the OAuth app settings page and re-apply the connector.

GitHub returns 404 Not Found​

The GitHub API returns 404 Not Found on token exchange, typically after the OAuth app was deleted on GitHub. Create a new OAuth app under the organization (Step 1), update the connector with the new client ID and secret, and re-apply.

GitHub returns 403 or 429​

This usually means GitHub rate limiting during token exchange or team lookup. GitHub documents 403 and 429 for rate-limit failures. 403 by itself does not always mean rate limiting, so check the GitHub response body in the auth journal first. Reduce login bursts, verify the app still has org approval, and check Rate limits and capacity planning for the GitHub.com request budgets this connector uses.

Operations​

Break-glass access​

Use break-glass access on an Auth host to recover a broken github connector, including during a GitHub outage.

Revocation window​

GitHub team membership is checked only at login time. If a user no longer belongs to any mapped team, is disabled in GitHub, or loses access to the OAuth app, they cannot start a new AlmaForge login through that connector.

This does not terminate a session that already exists. Existing certificates can authorize new connections until expiry unless you lock the user. Ending active sessions at expiry also requires disconnectExpiredCert. The default certificate lifetime is 12 hours unless the AuthConfig default session TTL or a role-specific max session TTL shortens it. See Access changes for the generic session and revocation behavior.

Secret storage​

Keep clientID and clientSecret outside the manifest for production. Use the file or environment forms in Secret storage, including its rotation and backup guidance.

Multiple connectors​

When using a GithubConnector alongside an OIDCConnector, follow Claims, traits, and role templates for cross-connector identity and trait handling.

Configuration changes and recovery​

Apply GithubConnector changes before testing the next login. No almad restart is needed. The shared change lifecycle applies, except that GitHub does not use OIDC discovery.

Keep the connector YAML in Git. The connector lives in the cluster state store. Recovery is a normal apply against the saved manifest.

High availability​

Every auth node talks to the GitHub API independently. See High availability on the generic OIDC page for the full list of per-instance assumptions. The discovery cache discussion does not apply (GitHub does not use OIDC discovery), but the per-instance egress guidance still does.

Egress requirements​

Auth hosts require outbound TCP/443 to:

  • GitHub Cloud: github.com and api.github.com.
  • GitHub Enterprise Server: the hostnames configured in spec.endpointURL and spec.apiEndpointURL.

No inbound connectivity from GitHub is required, and the integration does not open any listening sockets.

For GHES, outbound access to the GHES hostnames is enough for this integration. GHES itself may still talk to github.com for unrelated features, but AlmaForge does not need that path.

Rate limits and capacity planning​

GitHub.com uses two different budgets in this flow:

  • AlmaForge exchanges the OAuth code for an access token once per login. GitHub documents a secondary limit of no more than 2,000 OAuth access token requests per hour for GitHub Apps and OAuth apps.
  • After the token exchange, AlmaForge makes at least two REST API calls per login: GET /user and GET /user/teams. Users in many teams can trigger extra GET /user/teams pages.

The 2,000 token-request limit is per OAuth app across the whole cluster. Adding auth nodes does not increase it or divide it. OAuth access tokens generated by an OAuth app use the authenticated user's REST API budget. GitHub documents 5,000 requests per hour for authenticated users. That REST budget is combined with the user's other API activity, including personal access tokens and other apps. GitHub Enterprise Server uses instance-local rate limits instead.

The auth service does not retry token exchange or GitHub API reads in this flow, so short GitHub 5xx spikes show up as immediate login failures.

Monitoring​

There are no GitHub-specific Prometheus metrics today. The auth service only exposes generic counters such as user_login_total and user_certificates_generated. The generic counters do not break down failures by connector.

For GitHub-specific signal, monitor the audit log for login outcomes and the Auth and Proxy journals for diagnostic errors. On each service host, tail the journal with:

Terminal
journalctl -u almad -f | grep -i github

Check UserLogin failures and their spec.status.error field. See SSO verification for event codes, certificate fields, and debug-only journal copies. GitHub API errors include the HTTP status and GitHub's description in the Auth journal. Collect records from audit event storage for SIEM ingestion. Collect service diagnostics from the journal separately.

There is no built-in GitHub connector dry-run or synthetic login probe today. For automation, alert on UserLogin failures with method:github in the audit stream and on auth:github errors in the auth journal.

Removal​

Delete the connector resource:

Terminal
alma delete githubconnector/github

After deletion, GitHub sign-in stops working and team-to-role mappings stop being applied on future logins. Users are not deleted from AlmaForge. Delete orphaned dynamic users separately if you do not want to keep them in inventory: alma delete user/<name>. See Revocation window for the session TTL behavior.

You may also want to delete the OAuth app on GitHub so the client ID and secret can no longer be used. Users can independently revoke their personal grants under Settings → Applications → Authorized OAuth Apps on GitHub.

References​