Skip to content

GitHub OAuth

Sign in with GitHub. Requires the oauth extra (httpx).

from fastauth.providers.github import GitHubProvider

GitHubProvider(client_id="...", client_secret="...", scopes=None)
# default scopes: ["user:email"]

Prerequisites

  1. Create an OAuth App in GitHub Developer Settings.
  2. Set the Authorization callback URL to https://your-app.example/auth/oauth/github/callback.
  3. Set oauth_allowed_redirect_uris=["https://your-app.example/auth/oauth/github/callback"].

Setup

from fastauth import FastAuth, FastAuthConfig
from fastauth.providers.github import GitHubProvider
from fastauth.session_backends.redis import RedisSessionBackend

config = FastAuthConfig(
    ...,
    providers=[
        CredentialsProvider(),
        GitHubProvider(client_id=os.environ["GITHUB_CLIENT_ID"],
                       client_secret=os.environ["GITHUB_CLIENT_SECRET"]),
    ],
    oauth_adapter=adapter.oauth,
    oauth_state_store=RedisSessionBackend(url=os.environ["REDIS_URL"]),
    oauth_redirect_url="https://app.example.com/auth/callback",
    oauth_allowed_redirect_uris=[
        "https://your-app.example/auth/oauth/github/callback",
    ],
)

Email policy

GitHub's /user endpoint often omits the user's email (especially if it is private). GitHubProvider falls back to the /user/emails endpoint and picks the primary verified email. If no email is available, FastAuth raises ProviderError("No email found on GitHub account").

If a user already exists in your DB with the same email and GitHub reports email_verified=False, sign-in is rejected with ProviderError("OAuth provider email is not verified") to prevent account takeover.

Scopes

Default scope user:email is required to fetch the email when it is private. Add read:user if you need profile metadata via get_user_info.

Common mistakes

  • ProviderError: No email found on GitHub account — the GitHub user has no verified primary email, and revoking the user:email scope makes this worse. Ask the user to set a primary verified email in GitHub settings.
  • Forgetting user:email scope — without it, the /user/emails call fails.