GitHub OAuth 2.0 for CLI Applications

Implement GitHub OAuth 2.0 authentication in a command-line tool using the Device Code flow. Includes complete flow walkthrough.

Real-World Flows

Detailed Explanation

GitHub OAuth 2.0 for CLI Tools

GitHub supports the Device Code flow, making it ideal for CLI applications where users cannot enter credentials directly. The CLI displays a code and URL, the user authenticates in their browser, and the CLI receives the token.

GitHub OAuth Endpoints

Endpoint URL
Device Authorization https://github.com/login/device/code
Token https://github.com/login/oauth/access_token
User API https://api.github.com/user

Step 1: Create GitHub OAuth App

  1. Go to GitHub Settings > Developer settings > OAuth Apps
  2. Register a new application
  3. Note: GitHub OAuth Apps do not have a client_secret requirement for the device flow
  4. Save the client_id

Step 2: Request Device Code

curl -X POST https://github.com/login/device/code \
  -H "Accept: application/json" \
  -d "client_id=YOUR_CLIENT_ID&scope=repo%20user"

Response:

{
  "device_code": "3584d83530557fdd1f46af8289938c8ef79f9dc5",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://github.com/login/device",
  "expires_in": 899,
  "interval": 5
}

Step 3: Display to User

! First copy your one-time code: WDJB-MJHT
- Press Enter to open github.com in your browser...

Step 4: Poll for Token

curl -X POST https://github.com/login/oauth/access_token \
  -H "Accept: application/json" \
  -d "client_id=YOUR_CLIENT_ID\
  &device_code=3584d83530557fdd1f46af8289938c8ef79f9dc5\
  &grant_type=urn:ietf:params:oauth:grant-type:device_code"

Pending response: {"error": "authorization_pending"}

Success response:

{
  "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
  "token_type": "bearer",
  "scope": "repo,user"
}

Step 5: Use the Token

curl -H "Authorization: Bearer gho_16C7e42F292c6912E7710c838347Ae178B4a" \
  https://api.github.com/user

Node.js Implementation

const open = require("open");

async function githubDeviceAuth(clientId) {
  // Step 1: Get device code
  const codeRes = await fetch("https://github.com/login/device/code", {
    method: "POST",
    headers: {
      "Accept": "application/json",
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: `client_id=${clientId}&scope=repo user`,
  }).then(r => r.json());

  console.log(`Enter code: ${codeRes.user_code}`);
  await open(codeRes.verification_uri);

  // Step 2: Poll for token
  while (true) {
    await new Promise(r => setTimeout(r, codeRes.interval * 1000));
    const tokenRes = await fetch(
      "https://github.com/login/oauth/access_token",
      {
        method: "POST",
        headers: {
          "Accept": "application/json",
          "Content-Type": "application/x-www-form-urlencoded",
        },
        body: `client_id=${clientId}&device_code=${codeRes.device_code}&grant_type=urn:ietf:params:oauth:grant-type:device_code`,
      }
    ).then(r => r.json());

    if (tokenRes.access_token) return tokenRes.access_token;
    if (tokenRes.error === "authorization_pending") continue;
    throw new Error(tokenRes.error_description || tokenRes.error);
  }
}

Common GitHub Scopes

Scope Access
repo Full control of private repositories
repo:status Read/write commit statuses
user Read user profile data
read:org Read org membership
gist Create gists
workflow Update GitHub Actions workflows

Use Case

Building a CLI tool (like GitHub CLI, Terraform, or a custom deployment tool) that needs to authenticate with GitHub. The device code flow lets users authenticate securely in their browser without pasting tokens into the terminal.

Try It — OAuth 2.0 Flow Visualizer

Open full tool