Tutorials Search / Backend integrations / Sign in with GitHub (OAuth)
📝 Written ● Intermediate Updated 2026-05-13

Sign in with GitHub (OAuth)

If your audience is developers, GitHub OAuth is the right second sign-in option (or the only one). It's the same flow as Google OAuth — but registering the app is one form on GitHub instead of a tour through Google Cloud Console, and the user identity comes with their existing developer reputation attached.

The right set of OAuth providers depends on who's signing in. A consumer-facing app picks Google first because everyone has a Google account. A B2B app picks Microsoft because that's where the work accounts live. A developer-facing app picks GitHub — it's their professional identity, they're already logged in there for code work, and "the GitHub username on file" carries social proof that an arbitrary email doesn't. If your product touches code, devops, or developer workflows, GitHub OAuth is table stakes.

Mechanically, it's the same OAuth 2.0 flow as the Google version: redirect, consent screen, callback with auth code, server-side exchange for access token, user lookup. The differences are surface-level. GitHub's app registration is a single form on github.com/settings. There's no "verification process" — your app works for any GitHub user the moment you create it. GitHub's user object includes the username (which is meaningful in a way emails aren't), the avatar URL, and optionally the public email (private by default; you have to request the user:email scope to get something usable).

This tutorial covers the registration form, the scopes worth requesting, and the one-place-it-trips-up-first-timers: getting a stable email address out of the response.

What you'll learn

Step 1: Register a new OAuth App

1

github.com/settings/developers

Go to your GitHub settings, scroll to Developer settings (left sidebar at the bottom), click OAuth Apps → New OAuth App. Fill in:

  • Application name: what users see on the consent screen.
  • Homepage URL: your app's landing page.
  • Authorization callback URL: where GitHub sends users after they approve. Format: https://yourapp.com/api/auth/callback/github. For local dev, register a second app or use http://localhost:3000/api/auth/callback/github — exact match required.
  • Application description: optional; helpful to your future self.

Click Register application. GitHub shows your Client ID immediately. Click Generate a new client secret to get the secret; copy it now, you only see it once.

Step 2: Multiple environments — one app or many

2

GitHub only allows one callback URL per app

Unlike Google, GitHub OAuth Apps accept exactly one callback URL. To support dev + production, the easiest approach is to register two apps:

  • App #1: "MyApp (dev)" → callback http://localhost:3000/...
  • App #2: "MyApp (prod)" → callback https://yourapp.com/...

Each gets its own Client ID + Secret. Your dev env points at app #1; production at app #2. The user-visible names can differ — your dev users see "MyApp (dev)" on the consent screen, which is actually nice for debugging.

Workaround for "one callback URL": GitHub allows callback URLs to share a prefix. If the registered callback is https://yourapp.com/auth/callback/github, GitHub accepts callbacks to any subpath under that — so you can include state in the path. Most libraries don't take advantage of this; just remember the rule when debugging "why does this redirect URL not work."

Step 3: Add the credentials to your code

3

Env vars, same pattern as everything else

GITHUB_CLIENT_ID=Iv1.xxxxxxxxx
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxx

In Auth.js / NextAuth, the config is the standard:

import GitHubProvider from "next-auth/providers/github";

export const authOptions = {
  providers: [
    GitHubProvider({
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
    }),
  ],
};

If you've already configured a Google provider, add GitHub alongside — the library lets you stack providers, and your sign-in UI shows buttons for each.

Step 4: Request the right scopes

4

For sign-in: user:email

By default, GitHub OAuth only grants access to the user's public profile, which may not include an email address (most users keep their primary email private). To get a usable email, request the user:email scope.

Auth.js does this by default. Manually constructed auth URLs need &scope=user:email in the query string. The user sees this on the consent screen ("Access your email addresses").

Other scopes exist for advanced use cases:

  • read:user — fetch profile info beyond the public defaults.
  • repo — read/write the user's repositories (only if your app does that).
  • read:org — see which orgs they belong to.

The rule: request only what you'll use. Each additional scope is a friction point on the consent screen and a privacy implication for the user.

Step 5: Get a real email from the response

5

The thing that breaks most often

After a successful auth, GitHub's /user endpoint returns the user's profile. The email field on this response is the user's public email — often null, because many users keep their email private.

To get a usable email, hit /user/emails instead, which (with user:email scope) returns all the user's email addresses including the verified primary one:

const emailsRes = await fetch('https://api.github.com/user/emails', {
  headers: { Authorization: `Bearer ${accessToken}` },
});
const emails = await emailsRes.json();
const primary = emails.find(e => e.primary && e.verified)?.email;

Always pick the verified primary; that's the one GitHub itself trusts for communication. Auth.js handles this automatically; manual implementations have to do it themselves and frequently don't.

Step 6: Store the GitHub user ID, not just the username

6

Usernames can change; IDs don't

The /user response includes both login (the username, e.g., octocat) and id (a stable numeric ID, e.g., 583231). Store both, but use the ID as the foreign key.

GitHub allows users to change their username; if you key on login, a user changing their username breaks your records. Key on id and store the current login as a separate field that you update on each sign-in.

Step 7: OAuth Apps vs. GitHub Apps

7

Two products that sound similar but aren't

GitHub has two app types:

  • OAuth Apps — what this tutorial is about. Best for "let users sign in." Acts on behalf of a user. Standard OAuth 2.0 flow.
  • GitHub Apps — more sophisticated. Can act on behalf of an installation (a user or org); has fine-grained permissions; uses short-lived tokens. Best for "an app that integrates with repos as a first-class GitHub citizen" — bots, CI tools, code-review automation.

For sign-in only, OAuth Apps is right and simpler. For anything that interacts with code (creating PRs, posting checks, accessing repo content), GitHub Apps is the modern path. They can coexist — many products ship an OAuth App for sign-in and a separate GitHub App for the repo-integration features.

Rate limits matter. OAuth App auth requests count against the app's rate limit (5,000 requests/hour at the app level). High-traffic sign-in pages can hit this. If you're approaching the limit, consider GitHub Apps (which have separate, larger limits) or cache the user profile aggressively.

What's next