Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MomenSherif/react-oauth/llms.txt

Use this file to discover all available pages before exploring further.

The authorization code returned by onSuccess is a short-lived, single-use token. To get a usable access token you must exchange it on your backend using your Client Secret. This exchange must never happen in the browser.

Why the exchange must be server-side

The code-for-token exchange requires your Client Secret. If you performed it client-side:
  • Your Client Secret would be visible in browser network requests.
  • Anyone who found it could impersonate your OAuth App and make requests on behalf of any user.
  • GitHub would eventually revoke the secret, breaking authentication for all users.
Keep your Client Secret in an environment variable on your server. The frontend only ever sees the Client ID and the short-lived authorization code.

Authorization code flow

Here is the sequence of events from click to authenticated user:
  1. User clicks your login button.
  2. Your React app opens a GitHub popup and sends client_id, redirect_uri, scope, and state.
  3. User authorizes on GitHub.
  4. GitHub redirects the popup to your redirect_uri with code and state.
  5. The hook verifies state and calls onSuccess with the code.
  6. Your frontend sends code to your backend (e.g., POST /api/github/callback).
  7. Your backend calls GitHub’s token endpoint with client_id, client_secret, and code.
  8. GitHub returns an access_token.
  9. Your backend calls GET https://api.github.com/user with the access token.
  10. GitHub returns the user’s profile.
  11. Your backend creates or looks up the user in your database and returns a session token to the frontend.

Backend examples

app.post('/api/github/callback', async (req, res) => {
  const { code } = req.body;

  if (!code) {
    return res.status(400).json({ error: 'Missing authorization code' });
  }

  // Step 1: Exchange code for access token
  const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({
      client_id: process.env.GITHUB_CLIENT_ID,
      client_secret: process.env.GITHUB_CLIENT_SECRET,
      code,
    }),
  });

  const tokenData = await tokenResponse.json();

  if (tokenData.error) {
    return res.status(400).json({ error: tokenData.error_description || tokenData.error });
  }

  const { access_token } = tokenData;

  // Step 2: Fetch user data with the access token
  const userResponse = await fetch('https://api.github.com/user', {
    headers: {
      Authorization: `token ${access_token}`,
      Accept: 'application/vnd.github.v3+json',
    },
  });

  const user = await userResponse.json();

  // Step 3: Create or update user in your database, then return a session
  // const session = await createSession(user);

  res.json({ user });
});

Token exchange endpoint

The GitHub token exchange endpoint:
POST https://github.com/login/oauth/access_token
Request body:
FieldRequiredDescription
client_idYesYour OAuth App’s Client ID
client_secretYesYour OAuth App’s Client Secret
codeYesThe authorization code from the OAuth callback
redirect_uriNoMust match the redirect URI used in the initial request, if one was specified
Response (JSON with Accept: application/json):
FieldDescription
access_tokenThe GitHub access token
scopeScopes granted, comma-separated
token_typeAlways "bearer"
errorPresent only when the exchange fails
error_descriptionHuman-readable error detail

Using the access token

Once you have the access token, use it to call GitHub API endpoints:
// Fetch the authenticated user's profile
const userResponse = await fetch('https://api.github.com/user', {
  headers: {
    Authorization: `token ${access_token}`,
    Accept: 'application/vnd.github.v3+json',
  },
});
const user = await userResponse.json();

GitHub user response fields

The GET /user endpoint returns an object with many fields. The most commonly used ones:
FieldTypeDescription
idnumberUnique numeric GitHub user ID
loginstringGitHub username (e.g., "octocat")
namestring | nullDisplay name (may be null)
emailstring | nullPrimary email (null if not public; request user:email scope for guaranteed access)
avatar_urlstringURL to the user’s profile picture
html_urlstringURL to the user’s GitHub profile
biostring | nullUser’s biography
companystring | nullUser’s company
locationstring | nullUser’s location
If email is null, the user has not made their email public. To always receive an email address, request the user:email scope (the default) and call GET https://api.github.com/user/emails to retrieve all email addresses including private ones.

Frontend: sending the code to your backend

In your React component, forward the authorization code to your backend endpoint inside onSuccess:
onSuccess: async response => {
  try {
    const res = await fetch('/api/github/callback', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code: response.code }),
    });

    if (!res.ok) {
      throw new Error('Backend authentication failed');
    }

    const { user } = await res.json();
    // Update your app's auth state
    setCurrentUser(user);
  } catch (err) {
    console.error('Failed to complete authentication:', err);
  }
},
Never call GitHub’s token endpoint directly from your frontend code. The client_secret required for that request must stay on the server.

Build docs developers (and LLMs) love