GitHub sign-in for github.com and GitHub Enterprise Server. OAuth2 without an
identity token, so it is its own adapter, not an OIDC preset. No SDK: plain
fetch.
Install
npm install @orthacms/identity-provider-githubConfigure
Create an OAuth app (GitHub → Settings → Developer settings → OAuth Apps). Set its Authorization callback URL to:
https://cms.example.com/api/auth/sso/github/callback# .env — registered only when BOTH are set
SSO_GITHUB_CLIENT_ID=Iv1.0123456789abcdef
SSO_GITHUB_CLIENT_SECRET=...
# route segment and link-row key; renaming it orphans existing links
SSO_GITHUB_NAME=github
SSO_GITHUB_LABEL=GitHubThe optional variables are named differently in the two apps:
| Option | Reference server | Generated app | Default |
|---|---|---|---|
enterpriseBaseUrl | SSO_GITHUB_ENTERPRISE_URL | SSO_GITHUB_ENTERPRISE_BASE_URL | github.com |
organization | SSO_GITHUB_ORG | SSO_GITHUB_ORGANIZATION | unset |
scopes | — | SSO_GITHUB_SCOPES | read:user,user:email |
The reference server, apps/server/config/identity.ts:
function githubProvider(): (GithubProviderConfig & { name: string }) | undefined {
const clientId = readEnv('SSO_GITHUB_CLIENT_ID');
const clientSecret = readEnv('SSO_GITHUB_CLIENT_SECRET');
return when(clientId && clientSecret, () =>
defined({
name: readEnv('SSO_GITHUB_NAME') ?? 'github',
clientId: clientId as string,
clientSecret: clientSecret as string,
label: readEnv('SSO_GITHUB_LABEL'),
enterpriseBaseUrl: readEnv('SSO_GITHUB_ENTERPRISE_URL'),
organization: readEnv('SSO_GITHUB_ORG')
})
);
}All options
GithubProviderConfig:
| Option | Default | Notes |
|---|---|---|
clientId | required | |
clientSecret | required | No PKCE here; the secret is the only proof of the redeemer |
label | GitHub | |
scopes | read:user, user:email | Wider scopes grant access the CMS never uses |
enterpriseBaseUrl | github.com | e.g. https://github.acme.com. HTTPS required |
organization | unset | Shapes the account chooser only. Not an access control |
fetch | global fetch | Test seam |
Register
// apps/server/src/plugins.ts
import { createGithubProvider } from '@orthacms/identity-provider-github';
const { github } = config.plugins.identity.ssoProviders;
const providers = [];
if (github) {
const { name, ...settings } = github;
providers.push({ name, provider: createGithubProvider(settings) });
}
IdentityPlugin(config.plugins.identity, { sso: { providers } });Verify
curl -s http://localhost:3000/api/auth/sso
# [{"name":"github","label":"GitHub","kind":"oauth2"}]
curl -si http://localhost:3000/api/auth/sso/github/start | grep -i '^location'
# location: https://github.com/login/oauth/authorize?client_id=Iv1.…&state=…Enterprise Server authorizes at <enterpriseBaseUrl>/login/oauth/authorize and
reads the API under <enterpriseBaseUrl>/api/v3.
A refused sign-in redirects to /identity/signin?error=sso and logs
SSO sign-in refused: <reason>.
Behaviour worth knowing
- The address comes from the verified-emails endpoint, never the profile. An account with no verified address is refused, not reported as unverified.
- The subject is the numeric user id, never the login (logins can be renamed and reclaimed).
stateis the whole replay defence. No nonce, no PKCE challenge is sent.- The token exchange sends
accept: application/json, and a200carrying anerrorfield counts as a failure.