Prompt

How do I implement OAuth login with GitHub and Apple?

Technology / Identity & Access1 observationLast seen Oct 6, 2026

Latest observation

Oct 6, 2026OpenAI APIWeb search: off

To implement OAuth login with GitHub and Apple, the safest and most common approach is:

  1. Your app sends the user to the provider’s authorization page.
  2. The provider redirects back to your backend with an authorization code.
  3. Your backend exchanges that code for an access token.
  4. Your backend fetches the user identity.
  5. Your backend creates or logs in the local user and issues your own session/JWT.

Important: GitHub uses standard OAuth 2.0.
Apple uses OpenID Connect on top of OAuth 2.0 and has a few extra requirements.


Recommended architecture

Use a backend-driven flow:

  • Frontend: starts login, receives redirect back.
  • Backend: handles callback, exchanges code, validates tokens, creates session.

This avoids exposing client secrets and keeps token handling secure.


1) Common flow

Step A: Register your app

GitHub

Create an OAuth App in GitHub Developer Settings:

  • Homepage URL
  • Authorization callback URL, e.g. https://yourapp.com/auth/github/callback

You’ll get:

  • GITHUB_CLIENT_ID
  • GITHUB_CLIENT_SECRET

Apple

Create:

  • An App ID / Service ID in Apple Developer
  • A Sign in with Apple key
  • Configure redirect URL, e.g. https://yourapp.com/auth/apple/callback

You’ll get:

  • APPLE_CLIENT_ID (often your Service ID)
  • APPLE_TEAM_ID
  • APPLE_KEY_ID
  • APPLE_PRIVATE_KEY

Step B: Redirect user to provider

Your backend generates:

  • state for CSRF protection
  • optionally nonce for Apple/OpenID Connect

Then redirects to provider authorization URL.

GitHub authorization URL

https://github.com/login/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/auth/github/callback
  &scope=read:user user:email
  &state=RANDOM_STATE

Apple authorization URL

https://appleid.apple.com/auth/authorize
  ?response_type=code
  &response_mode=form_post
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/auth/apple/callback
  &scope=name%20email
  &state=RANDOM_STATE
  &nonce=RANDOM_NONCE

Notes:

  • Apple often uses response_mode=form_post.
  • Apple returns user info (name, email) only on the first consent.
  • Apple requires nonce and token validation.

Step C: Callback handling

Your backend receives:

  • code
  • state

Validate:

  • state matches what you stored in session/cookie/server-side.

Then exchange the code for tokens.


2) GitHub implementation

Exchange code for access token

POST to:

https://github.com/login/oauth/access_token

With:

  • client_id
  • client_secret
  • code
  • redirect_uri (must match)

Request headers:

Accept: application/json

Example response:

{
  "access_token": "gho_xxx",
  "scope": "read:user,user:email",
  "token_type": "bearer"
}

Fetch user profile

Use the access token:

GET https://api.github.com/user
Authorization: Bearer gho_xxx
Accept: application/vnd.github+json

This returns GitHub user id, login, avatar, etc.

If you need email:

GET https://api.github.com/user/emails
Authorization: Bearer gho_xxx

Then:

  • prefer the primary verified email
  • if unavailable, use the GitHub ID as the stable identity

User mapping

Store something like:

  • provider = github
  • provider_user_id = github_user_id
  • email
  • name
  • avatar_url

3) Apple implementation

Apple is slightly more involved.

Exchange code for tokens

POST to:

https://appleid.apple.com/auth/token

With:

  • client_id
  • client_secret (a signed JWT you generate)
  • code
  • grant_type=authorization_code
  • redirect_uri

Apple client secret

Apple requires client_secret to be a JWT signed with your Apple private key.

Claims usually include:

  • iss: your Apple Team ID
  • iat: issued-at
  • exp: expiration (max 6 months)
  • aud: https://appleid.apple.com
  • sub: your client_id / service_id

Verify ID token

Apple returns an id_token (JWT). You should:

  • verify signature using Apple public keys
  • validate:
    • iss == https://appleid.apple.com
    • aud == your client_id
    • exp not expired
    • nonce matches if used

Apple may also return:

  • access_token
  • refresh_token
  • id_token

User identity

The stable unique identifier is the sub claim in the ID token.
Use that as the provider user id.

Important Apple behaviors

  • Apple may hide the user’s real email with a relay address.
  • User name is only returned the first time in the initial authorization response.
  • You must save the name/email then, because they may not be sent again.

4) Account linking strategy

Best practice:

  • Create a local user record keyed by your own user id.
  • Attach multiple provider identities to one local account.

Example schema:

users

  • id
  • email
  • name
  • avatar_url
  • created_at

oauth_accounts

  • id
  • user_id
  • provider (github, apple)
  • provider_user_id
  • access_token (optional, encrypted)
  • refresh_token (optional, encrypted)
  • created_at

When a provider login succeeds:

  1. Find oauth_accounts by provider + provider_user_id
  2. If found, log in linked user
  3. If not found:
    • if email matches existing user, optionally link
    • otherwise create a new user and link provider account

5) Security requirements

  • Use HTTPS only
  • Always validate state
  • For Apple, validate nonce and JWT signature
  • Do not trust email alone as identity
  • Encrypt stored tokens if you keep them
  • Use short-lived sessions or signed JWTs
  • Rotate secrets and revoke unused credentials
  • Use PKCE if your architecture supports it, especially for public clients

6) Example backend pseudocode

Start login

app.get("/auth/github શરૂ", (req, res) => {
  const state = randomString();
  saveStateToSession(req, state);

  const url = new URL("https://github.com/login/oauth/authorize");
  url.searchParams.set("client_id", प्रक्रिया.env.GITHUB_CLIENT_ID);
  url.searchParams.set("redirect_uri", "https://yourapp.com/auth/github/callback");
  url.searchParams.set("scope", "read:user user:email");
  url.searchParams.set("state", state);

  res.redirect(url.toString());
});

GitHub callback

app.get("/auth/github/callback", async (req, res) => {
  const { code, state } = req.query;
  if (state !== getStateFromSession(req)) {
    return res.status(400).send("Invalid state");
  }

  const tokenResp = await fetch("https://github.com/login/oauth/access_token", {
    method: "POST",
    headers: { "Accept": "application/json", "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.GITHUB_CLIENT_ID,
      client_secret: process.env.GITHUB_CLIENT_SECRET,
      code,
      redirect_uri: "https://yourapp.com/auth/github/callback"
    })
  });

  const tokenData = await tokenResp.json();

  const userResp = await fetch("https://api.github.com/user", {
    headers: {
      Authorization: `Bearer ${tokenData.access_token}`,
      Accept: "application/vnd.github+json"
    }
  });

  const ghUser = await userResp.json();

  const localUser = await findOrCreateUserFromGithub(ghUser);
  createSession(res, localUser.id);

  res.redirect("/app");
});

7) If you use a frontend SPA

You have two main options:

Option A: Backend handles OAuth

  • Frontend just opens /auth/github/start or /auth/apple/start
  • Backend redirects back to frontend after login

This is simplest and most secure.

Option B: SPA + PKCE

  • Frontend initiates login
  • Backend exchanges code, or frontend does PKCE token exchange
  • More complex, especially with Apple

For Apple and GitHub, backend-handled OAuth is usually easiest.


8) Practical library choices

Depending on your stack:

Node.js

  • passport-github2
  • passport-apple
  • openid-client
  • simple-oauth2

Python

  • Authlib
  • django-allauth
  • social-auth-app-django

Ruby

  • omniauth-github
  • omniauth-apple

Go

  • golang.org/x/oauth2
  • custom OpenID Connect verification for Apple

9) Simple recommendation

If you want the easiest secure implementation:

  • Use a backend framework
  • Implement GitHub OAuth 2.0
  • Implement Apple Sign in with Apple as OpenID Connect
  • Store provider IDs and link them to one local user
  • Issue your own app session after authentication

If you want, I can provide:

  1. a complete Node.js/Express example for both GitHub and Apple,
  2. a Next.js implementation, or
  3. a database schema + account-linking logic.

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as dotdotPR. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.