End-to-end OAuth Device Flow examples for both GitHub Apps (ghu_ tokens) and OAuth Apps (gho_ tokens), in four languages — perfect for CLI applications that need user-attributed access without standing up a callback web server.
Warning
These scripts are for demonstration and testing purposes only. The full token is printed to stdout, which may expose it in logs and shell history. Do not use as-is in production.
GitHub App (github-app/) |
OAuth App (oauth-app/) |
|
|---|---|---|
| Token prefix | ghu_ |
gho_ |
| Permission model | Fine-grained app-level permissions | OAuth scopes |
| Client Secret on token exchange? | ❌ Not used (public client) | ✅ Required (confidential client) |
scope parameter? |
❌ Ignored | ✅ Honoured |
| Default token TTL | ~8 hours, refreshable | Non-expiring |
| Server-to-server installation tokens? | ✅ Yes (separate flow, not in this repo) | ❌ No |
| Recommended for new builds? | ✅ Yes | Use only when you need scopes / non-expiring tokens |
If you're starting fresh, prefer GitHub Apps. If you're reproducing an OAuth-specific issue or integrating with an existing OAuth-only flow, use OAuth Apps.
.
├── github-app/ # GitHub App device flow (ghu_ tokens)
│ ├── README.md ← start here for github-app
│ ├── setup.md ← creating a GitHub App
│ ├── device_flow.{sh,py,js,go}
│ ├── shell.md / python.md / nodejs.md / go.md
│ └── requirements.txt ← Python deps
│
├── oauth-app/ # OAuth App device flow (gho_ tokens)
│ ├── README.md ← start here for oauth-app
│ ├── setup.md ← creating an OAuth App + EMU notes
│ ├── device_flow.{sh,py,js,go}
│ ├── shell.md / python.md / nodejs.md / go.md
│ └── requirements.txt ← Python deps
│
└── common-issues.md ← cross-mode troubleshooting
The same four-step OAuth 2.0 Device Authorization Grant (RFC 8628) underpins both flows:
- The CLI calls
POST /login/device/codeto obtain auser_codeand averification_uri. - You visit
github.com/login/device, enter the user code, and authorise. - The CLI polls
POST /login/oauth/access_tokenuntil the user authorises (or denies / expires). - GitHub returns the user access token.
The differences between the two flows live in the parameters of those two calls — see the comparison table above and the per-subdir READMEs for protocol detail.
Pick a subdir based on the table above, then follow that subdir's README.
OAuth App example:
cd oauth-app
export GITHUB_CLIENT_SECRET='your-client-secret'
./device_flow.sh --client-id YOUR_CLIENT_IDGitHub App example:
cd github-app
./device_flow.sh --client-id YOUR_CLIENT_IDFor OAuth Apps, the Client Secret is read from the GITHUB_CLIENT_SECRET env var only — never from a CLI flag. Flags leak to shell history, ps output, and audit logs, so we deliberately don't accept them. The Client ID is public and can be passed either way.
See common-issues.md for cross-mode gotchas (Client ID format mismatches, EMU SSO behaviour, common 403 causes, and more).