What are the architectural differences between GitHub Apps and OAuth Apps, and how does the installation token model affect security and rate limiting?
Quick Answer
GitHub Apps act as first-class integrations with granular permissions, installation-scoped tokens, and higher rate limits (5,000 requests/hour/installation), while OAuth Apps act on behalf of users with broad scope-based permissions and user-level rate limits (5,000 requests/hour/user).
Detailed Answer
Think of GitHub Apps and OAuth Apps like the difference between a building security badge and a master key. A security badge (GitHub App) grants access to specific floors and rooms based on pre-configured permissions, works independently of any individual person, and its access is tracked separately. A master key (OAuth App) opens doors based on who is carrying it—it inherits that person's full access level and every action is attributed to them.
GitHub Apps are standalone identities that authenticate in two distinct ways. First, they authenticate as themselves using a JWT signed with their private key to perform app-level operations like listing installations. Second, they generate installation access tokens scoped to a specific organization or user account installation. These installation tokens last only one hour, carry only the permissions the app was granted during installation, and can be further narrowed at creation time to specific repositories. The permissions model is granular: you can request read-only access to pull requests, write access to issues, and no access to code—impossible with OAuth's coarse scopes.
OAuth Apps authenticate users through the standard OAuth 2.0 flow, generating user access tokens that carry the user's identity and permissions. The token's effective access is the intersection of the requested OAuth scopes (like repo, admin:org) and the user's actual permissions on each resource. This means an OAuth token with the 'repo' scope gives access to every repository the user can access—there is no way to scope it to a single repo. OAuth Apps also lack webhook subscriptions at the app level and cannot act independently of a user context.
In production, the choice has significant operational implications. A CI/CD bot like the one managing deployments for user-auth-service should be a GitHub App because it needs to act independently (no human user), requires limited permissions (contents: read, deployments: write), and benefits from installation tokens that are short-lived and repo-scoped. Rate limiting differs dramatically: GitHub Apps get 5,000 API requests per hour per installation (scaling linearly as you install across organizations), plus an additional 15,000 for GitHub Enterprise Cloud. OAuth Apps share the authenticated user's 5,000 requests/hour limit, meaning a busy integration can exhaust a developer's personal rate limit. GitHub Apps also support webhook delivery with secret verification, can be listed on the GitHub Marketplace, and receive granular webhook events rather than organization-wide hooks.
The most critical gotcha involves installation token lifecycle management. Installation tokens expire after one hour and cannot be refreshed—you must generate a new one. If your application caches tokens, you need proper expiry tracking with a buffer (regenerate at 50 minutes, not 59). The JWT used to create installation tokens has only a 10-minute validity window, so clock synchronization matters. Another subtle issue: when a GitHub App is installed on an organization, repository administrators can further restrict which repositories the app can access, creating a gap between the app's requested permissions and its effective permissions. Always handle 403 responses gracefully. Finally, GitHub Apps have a 10 concurrent installation token limit per app per installation—exceeding this invalidates the oldest token, which can break parallel CI jobs.