Prompt
How do I implement OAuth login with GitHub and Apple?
Latest observation
To implement OAuth login with GitHub and Apple, the safest and most common approach is:
- Your app sends the user to the provider’s authorization page.
- The provider redirects back to your backend with an authorization code.
- Your backend exchanges that code for an access token.
- Your backend fetches the user identity.
- 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_IDGITHUB_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_IDAPPLE_KEY_IDAPPLE_PRIVATE_KEY
Step B: Redirect user to provider
Your backend generates:
statefor CSRF protection- optionally
noncefor 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
nonceand token validation.
Step C: Callback handling
Your backend receives:
codestate
Validate:
statematches 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_idclient_secretcoderedirect_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 = githubprovider_user_id = github_user_idemailnameavatar_url
3) Apple implementation
Apple is slightly more involved.
Exchange code for tokens
POST to:
https://appleid.apple.com/auth/token
With:
client_idclient_secret(a signed JWT you generate)codegrant_type=authorization_coderedirect_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 IDiat: issued-atexp: expiration (max 6 months)aud:https://appleid.apple.comsub: 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.comaud == your client_idexpnot expirednoncematches if used
Apple may also return:
access_tokenrefresh_tokenid_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
idemailnameavatar_urlcreated_at
oauth_accounts
iduser_idprovider(github,apple)provider_user_idaccess_token(optional, encrypted)refresh_token(optional, encrypted)created_at
When a provider login succeeds:
- Find
oauth_accountsby provider + provider_user_id - If found, log in linked user
- 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/startor/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-github2passport-appleopenid-clientsimple-oauth2
Python
Authlibdjango-allauthsocial-auth-app-django
Ruby
omniauth-githubomniauth-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:
- a complete Node.js/Express example for both GitHub and Apple,
- a Next.js implementation, or
- a database schema + account-linking logic.