Understanding Authentication with GitHub (OAuth)
NicolasBrondinBernard
Discover how login with GitHub works using OAuth and learn how to easily implement a "Sign in with GitHub" button in your application.

Article published on 15/07/2026, last updated on 10/08/2026
Offering a "Sign in with GitHub" button in an application is a great way to optimize your conversion rate: it's convenient for the user, who doesn't need to create a new password.

But it's also convenient for the developer, who delegates authentication to GitHub. Yet many developers use OAuth without really understanding what happens behind the scenes.
Your application never knows the user's GitHub password. Instead, it lets GitHub verify their identity, then retrieves only the information it needs.
That's the whole principle of OAuth: delegating authentication to a third-party service.
Implementing GitHub Connect
Creating an OAuth App
The first step is to create an application on GitHub.
To do this, go to your account settings, then to Developer settings > OAuth Apps.

You'll need to fill in in particular:
- your application's name;
- its URL;
- a callback URL.
For example:
http://localhost:3000/auth/github/callback
Once the application is created, GitHub provides you with two important pieces of information:
Client ID
Client Secret
The Client ID identifies your application.
The Client Secret, on the other hand, is confidential.
It must never be sent to the browser or embedded in your frontend code.
Tip
You can create as many applications as you like, so it's at least recommended to create one for your production environment and one for local use.
You could call it "MyApp (local)" for example.
Redirecting the user to GitHub
When the user clicks "Sign in with GitHub," your application doesn't try to authenticate them itself.
It simply redirects them to GitHub.
For example:
const params = new URLSearchParams({
client_id: process.env.GITHUB_CLIENT_ID,
redirect_uri: "http://localhost:3000/auth/github/callback",
scope: "read:user user:email",
state: crypto.randomUUID(),
});
response.redirect(
`https://github.com/login/oauth/authorize?${params}`
);
The scope parameter indicates the information your application wants to retrieve.
The state parameter is a random identifier used to protect the flow against certain attacks. It must be saved before the redirect and then verified when GitHub sends the user back.
GitHub authenticates the user
The user then lands on GitHub, and if they aren't already logged in, GitHub asks them to authenticate.
GitHub then shows them the permissions requested by your application and asks them to accept them. Your application never sees this step.
It simply waits for GitHub to redirect the user back to the callback URL.

Retrieving a temporary code
Once the permissions are accepted, GitHub redirects the browser to your callback.
The URL looks like this:
/auth/github/callback?code=...&state=...
The
codeis a temporary identifier. On its own, it doesn't allow you to call the GitHub API.
Your server must first exchange it for an Access Token.
const tokenResponse = 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,
}),
}
);
const { access_token } = await tokenResponse.json();
This request must always be made server-side, since it uses your Client Secret.
Retrieving the user's email
Once you have the token, you can call the GitHub API, for example to retrieve the account's email addresses:
const emailsResponse = await fetch(
"https://api.github.com/user/emails",
{
headers: {
Authorization: `Bearer ${access_token}`,
Accept: "application/vnd.github+json",
},
}
);
const emails = await emailsResponse.json();
const primaryEmail = emails.find(
(email) => email.primary && email.verified
)?.email;
The primary email address is generally the best identifier to use.
Indeed, if a user already has an account created with Google or with an email and password, it's likely that this address is the same.
This makes it possible to offer several login methods while keeping a single user account.
Generating your application's JWT
At this point, GitHub has finished its job, your application now knows the user's identity.
All that remains is to look up a user with this email address.
- If they don't exist yet, you can create an account for them automatically.
- Otherwise, you simply retrieve their information from the database.
Once this step is complete, you generate your own JWT token:
const token = jwt.sign(
{
userId: user.id,
email: user.email,
},
process.env.JWT_SECRET,
{
expiresIn: "7d",
}
);
This JWT is then sent back to the browser, usually in a secure cookie or in your API response.
The GitHub token is only used to communicate with GitHub. Once the user is authenticated, it's your own JWT that will identify them within your application.
How it works, summarized
Even though OAuth may seem impressive at first glance, the mechanism is actually quite simple:
- Your application redirects the user to GitHub.
- GitHub verifies their identity and returns a temporary code.
- Your server exchanges this code for a token, retrieves the user's email address, then generates its own JWT.
- Ultimately, GitHub only does one thing: confirm the user's identity.
All account management, permissions, and authentication within your application remain your responsibility.
No spam. Only free content, news, and ever more resources to level up your skills!
Join +1500 developers
No comments yet