The proxy uses GitHub’s OAuth device code flow to authenticate with GitHub Copilot. This is the same authentication method used by VS Code and other GitHub Copilot clients.
Device code flow
The device code flow is designed for devices that don’t have a web browser or have limited input capabilities. It works by:
- Requesting a device code from GitHub
- Displaying a user code for the user to enter on GitHub’s website
- Polling GitHub’s token endpoint until the user authorizes the application
- Saving the access token for future use
Authentication endpoints
Device code initiation
The authentication script requests a device code from GitHub:
const CLIENT_ID = "Ov23li8tweQw6odWQebz"
const DEVICE_CODE_URL = "https://github.com/login/device/code"
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",
}),
})
return response.json()
}
The OAuth application client ID. Uses GitHub Copilot’s public client ID.
OAuth scopes to request. Only read:user is needed for Copilot access.
Response
Device verification code used for polling.
User-friendly code displayed to the user (e.g., "ABCD-1234").
URL where the user enters the code (typically https://github.com/login/device).
Minimum number of seconds to wait between polling requests.
Number of seconds until the device code expires.
{
"device_code": "3584d83530557fdd1f46af8289938c8ef79f9dc5",
"user_code": "ABCD-1234",
"verification_uri": "https://github.com/login/device",
"interval": 5,
"expires_in": 900
}
Token polling
The script polls GitHub’s token endpoint until the user authorizes the application:
const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
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
}
// Handle other error cases...
}
}
Polling errors
The polling loop handles several error states:
The user hasn’t authorized yet. Continue polling.
Polling too frequently. Add a 5-second delay before the next request.
The device code has expired. Start the flow over.
The user denied authorization. Stop polling.
Success response
{
"access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
"token_type": "bearer",
"scope": "read:user"
}
Token verification
After receiving an access token, the script verifies it by calling GitHub’s user API:
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()
}
This returns the authenticated user’s information:
{
"login": "octocat",
"id": 1,
"name": "The Octocat",
"email": "[email protected]",
"avatar_url": "https://avatars.githubusercontent.com/u/1?v=4"
}
Token storage
The access token is saved to a JSON file for persistence:
const AUTH_FILE = process.env.COPILOT_AUTH_FILE ||
join(homedir(), ".claude-copilot-auth.json")
const authData = {
access_token: accessToken,
provider: "github-copilot",
github_user: user.login,
created_at: new Date().toISOString(),
}
writeFileSync(AUTH_FILE, JSON.stringify(authData, null, 2))
The authentication file contains:
The OAuth access token used for API requests.
Always "github-copilot" to identify the token source.
The GitHub username of the authenticated user.
ISO 8601 timestamp of when the token was created.
{
"access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
"provider": "github-copilot",
"github_user": "octocat",
"created_at": "2026-03-03T12:00:00.000Z"
}
Token loading
The proxy server loads the token at startup:
function loadAuth() {
if (!existsSync(AUTH_FILE)) {
console.error(`✗ Auth file not found: ${AUTH_FILE}`)
console.error(" Run 'node scripts/auth.mjs' first to authenticate.")
process.exit(1)
}
try {
const data = JSON.parse(readFileSync(AUTH_FILE, "utf-8"))
if (!data.access_token) {
throw new Error("No access_token in auth file")
}
return data.access_token
} catch (err) {
console.error(`✗ Failed to read auth file: ${err.message}`)
process.exit(1)
}
}
If the auth file is missing or invalid, the proxy will exit with an error. Run node scripts/auth.mjs to authenticate.
Re-authentication
To re-authenticate with a different GitHub account:
-
Delete the existing auth file:
rm ~/.claude-copilot-auth.json
-
Run the authentication script again:
The script checks for an existing valid token before starting the flow:
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}`)
return
}
} catch {
console.log("Existing token is invalid, starting fresh authentication...")
}
}
Security considerations
The auth file contains a sensitive access token. Keep it secure and never commit it to version control.
- The token grants access to your GitHub Copilot subscription
- Store the token file with restricted permissions (chmod 600)
- Use environment variable
COPILOT_AUTH_FILE to customize the location
- Tokens don’t expire automatically but can be revoked from GitHub settings
Environment variables
COPILOT_AUTH_FILE
string
default:"~/.claude-copilot-auth.json"
Path to the authentication file. Both the auth script and proxy server use this variable.
# Custom auth file location
export COPILOT_AUTH_FILE="/secure/location/copilot-auth.json"
node scripts/auth.mjs
node scripts/proxy.mjs