Overview
The authentication script implements GitHub’s OAuth device code flow to obtain an access token for the Copilot API. It guides users through browser-based authentication and securely stores credentials.
Command
Authentication flow
The script follows a 5-step OAuth device code flow:
- Initiate device code - Request a user code from GitHub
- Display code - Show the code and verification URL to the user
- Poll for token - Wait for user authorization
- Verify token - Confirm the token works with GitHub API
- Save credentials - Store token to
~/.claude-copilot-auth.json
Functions
initiateDeviceCode()
Initiates the OAuth device code flow with GitHub.
async function initiateDeviceCode() {
const response = await fetch(DEVICE_CODE_URL, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"User-Agent": USER_AGENT,
},
body: JSON.stringify({
client_id: CLIENT_ID,
scope: "read:user",
}),
})
if (!response.ok) {
throw new Error(
`Failed to initiate device code flow: ${response.status} ${response.statusText}`
)
}
return response.json()
}
Returns: Object containing device_code, user_code, verification_uri, and interval
Error handling: Throws descriptive error if the initial request fails
pollForToken(deviceCode, interval)
Polls GitHub’s token endpoint until the user completes authorization.
Device code received from initiateDeviceCode()
Polling interval in seconds (adds 1 second safety margin)
async function pollForToken(deviceCode, interval) {
const pollInterval = (interval + 1) * 1000 // Add safety margin
while (true) {
await new Promise((resolve) => setTimeout(resolve, pollInterval))
const response = await fetch(ACCESS_TOKEN_URL, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"User-Agent": USER_AGENT,
},
body: JSON.stringify({
client_id: CLIENT_ID,
device_code: deviceCode,
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
}),
})
const data = await response.json()
if (data.access_token) {
return data.access_token
}
if (data.error === "authorization_pending") {
process.stdout.write(".")
continue
}
if (data.error === "slow_down") {
await new Promise((resolve) => setTimeout(resolve, 5000))
continue
}
if (data.error === "expired_token") {
throw new Error("Device code expired. Please try again.")
}
if (data.error === "access_denied") {
throw new Error("Authorization was denied by user.")
}
throw new Error(`Unexpected error: ${data.error} - ${data.error_description}`)
}
}
Returns: GitHub access token string
Error states:
authorization_pending - User hasn’t authorized yet (continues polling)
slow_down - Rate limit hit (waits 5 seconds)
expired_token - Code expired (throws error)
access_denied - User rejected authorization (throws error)
verifyToken(token)
Verifies the access token works by calling GitHub’s user API.
GitHub access token to verify
async function verifyToken(token) {
const response = await fetch("https://api.github.com/user", {
headers: {
Authorization: `Bearer ${token}`,
"User-Agent": USER_AGENT,
},
})
if (!response.ok) {
throw new Error("Token verification failed")
}
return response.json()
}
Returns: GitHub user object with login, name, etc.
Error handling: Throws error if token is invalid
checkCopilotAccess(token)
Verifies the token has Copilot API access.
GitHub access token to check
async function checkCopilotAccess(token) {
const response = await fetch("https://api.githubcopilot.com/models", {
headers: {
Authorization: `Bearer ${token}`,
"User-Agent": USER_AGENT,
"Openai-Intent": "conversation-edits",
},
})
return response.ok || response.status === 401
}
Returns: true if access is confirmed or likely valid (401 may indicate different auth format)
Token storage
Credentials are saved to ~/.claude-copilot-auth.json with the following structure:
{
"access_token": "gho_xxxxxxxxxxxxx",
"provider": "github-copilot",
"github_user": "username",
"created_at": "2026-03-03T12:00:00.000Z"
}
The auth file location can be customized via the COPILOT_AUTH_FILE environment variable:
COPILOT_AUTH_FILE=/custom/path/auth.json node scripts/auth.mjs
Configuration
OAuth client ID: Ov23li8tweQw6odWQebz
GitHub device code endpoint: https://github.com/login/device/code
GitHub token endpoint: https://github.com/login/oauth/access_token
Token storage location (default: ~/.claude-copilot-auth.json)
HTTP user agent: claude-code-copilot-provider/1.0.0
User experience
The script provides a guided authentication experience:
- Automatic browser launch - Opens verification URL automatically on macOS, Windows, and Linux
- Visual feedback - Box-formatted user code display
- Progress indicator - Dots printed during polling
- Re-authentication check - Detects existing valid tokens and prevents unnecessary re-auth
Example output
╔══════════════════════════════════════════════════════════╗
║ GitHub Copilot Authentication for Claude Code ║
╚══════════════════════════════════════════════════════════╝
Initiating GitHub OAuth device code flow...
┌──────────────────────────────────────────────────────────┐
│ │
│ Your code: ABCD-1234 │
│ │
│ Open this URL in your browser: │
│ https://github.com/login/device │
│ │
│ Enter the code above and authorize the application. │
│ │
└──────────────────────────────────────────────────────────┘
(Browser opened automatically)
Waiting for authorization......
✓ Authenticated as: username (Display Name)
✓ Token saved to: /home/user/.claude-copilot-auth.json
You can now start the proxy server:
node scripts/proxy.mjs
Then run Claude Code with:
ANTHROPIC_BASE_URL=http://localhost:18080 ANTHROPIC_API_KEY=copilot-proxy claude
Error handling
The script handles various error conditions:
Invalid existing token
if (existsSync(AUTH_FILE)) {
try {
const existing = JSON.parse(readFileSync(AUTH_FILE, "utf-8"))
if (existing.access_token) {
const user = await verifyToken(existing.access_token)
console.log(`Already authenticated as: ${user.login}`)
// ... instructions to re-authenticate
return
}
} catch {
// Token invalid, proceed with new auth
console.log("Existing token is invalid, starting fresh authentication...\n")
}
}
Authentication failure
main().catch((err) => {
console.error("\n✗ Authentication failed:", err.message)
process.exit(1)
})
Next steps
After successful authentication:
- Start the proxy server:
node scripts/proxy.mjs
- Launch Claude Code with the proxy:
ANTHROPIC_BASE_URL=http://localhost:18080 ANTHROPIC_API_KEY=copilot-proxy claude
Re-authentication
To re-authenticate with a different GitHub account:
rm ~/.claude-copilot-auth.json
node scripts/auth.mjs