Beta . Ortha CMS is in public beta. Every plan is free until it ends.
Features Ortha CMS on GitHub

GitHub sign-in

Identity plugin @orthacms/identity-server@orthacms/identity-admin

Its own package because GitHub is not OpenID Connect — and that changes three things.

Documents 0.13.1 Updated Edit this page Report a problem

On this page

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-github

Configure

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=GitHub

The optional variables are named differently in the two apps:

OptionReference serverGenerated appDefault
enterpriseBaseUrlSSO_GITHUB_ENTERPRISE_URLSSO_GITHUB_ENTERPRISE_BASE_URLgithub.com
organizationSSO_GITHUB_ORGSSO_GITHUB_ORGANIZATIONunset
scopes—SSO_GITHUB_SCOPESread: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:

OptionDefaultNotes
clientIdrequired
clientSecretrequiredNo PKCE here; the secret is the only proof of the redeemer
labelGitHub
scopesread:user, user:emailWider scopes grant access the CMS never uses
enterpriseBaseUrlgithub.come.g. https://github.acme.com. HTTPS required
organizationunsetShapes the account chooser only. Not an access control
fetchglobal fetchTest 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).
  • state is the whole replay defence. No nonce, no PKCE challenge is sent.
  • The token exchange sends accept: application/json, and a 200 carrying an error field counts as a failure.