0
# GitHub Apps
1
2
Support for GitHub Apps authentication including JWT creation and installation access token retrieval for app-based integrations. GitHub Apps provide a more secure and scalable way to integrate with GitHub compared to OAuth tokens.
3
4
## Capabilities
5
6
### JWT Token Generation
7
8
Create JSON Web Tokens (JWT) for GitHub App authentication.
9
10
```python { .api }
11
def get_jwt(
12
*,
13
app_id: str,
14
private_key: str,
15
expiration: int = 10 * 60
16
) -> str:
17
"""
18
Construct the JWT (JSON Web Token) for GitHub App authentication.
19
20
Parameters:
21
- app_id: GitHub App ID (numeric string)
22
- private_key: RSA private key in PEM format
23
- expiration: Token expiration time in seconds (default: 600, max: 600)
24
25
Returns:
26
- JWT bearer token string
27
28
Note: JWT tokens are used to authenticate as the GitHub App itself,
29
not as an installation of the app.
30
"""
31
```
32
33
### Installation Access Tokens
34
35
Obtain installation access tokens for GitHub App installations.
36
37
```python { .api }
38
async def get_installation_access_token(
39
gh: GitHubAPI,
40
*,
41
installation_id: str,
42
app_id: str,
43
private_key: str
44
) -> Dict[str, Any]:
45
"""
46
Obtain a GitHub App's installation access token.
47
48
Parameters:
49
- gh: GitHubAPI instance for making the request
50
- installation_id: Installation ID (numeric string)
51
- app_id: GitHub App ID (numeric string)
52
- private_key: RSA private key in PEM format
53
54
Returns:
55
- Dictionary containing:
56
- "token": Installation access token string
57
- "expires_at": ISO 8601 expiration datetime string
58
59
Note: Installation access tokens allow acting on behalf of the
60
app installation with permissions granted to the app.
61
"""
62
```
63
64
## Usage Examples
65
66
### Basic GitHub App Authentication
67
68
```python
69
import asyncio
70
from gidgethub.aiohttp import GitHubAPI
71
from gidgethub.apps import get_jwt, get_installation_access_token
72
import aiohttp
73
74
async def github_app_example():
75
# GitHub App credentials
76
app_id = "12345"
77
private_key = """-----BEGIN RSA PRIVATE KEY-----
78
MIIEpAIBAAKCAQEA...
79
-----END RSA PRIVATE KEY-----"""
80
installation_id = "67890"
81
82
async with aiohttp.ClientSession() as session:
83
gh = GitHubAPI(session, "my-app/1.0")
84
85
# Get installation access token
86
token_response = await get_installation_access_token(
87
gh,
88
installation_id=installation_id,
89
app_id=app_id,
90
private_key=private_key
91
)
92
93
access_token = token_response["token"]
94
expires_at = token_response["expires_at"]
95
print(f"Token expires at: {expires_at}")
96
97
# Create new client with installation token
98
gh_installed = GitHubAPI(session, "my-app/1.0", oauth_token=access_token)
99
100
# Now you can act on behalf of the installation
101
repos = []
102
async for repo in gh_installed.getiter("/installation/repositories"):
103
repos.append(repo["name"])
104
105
print(f"App has access to {len(repos)} repositories")
106
107
asyncio.run(github_app_example())
108
```
109
110
### JWT Token Usage
111
112
```python
113
import asyncio
114
from gidgethub.aiohttp import GitHubAPI
115
from gidgethub.apps import get_jwt
116
import aiohttp
117
118
async def jwt_example():
119
app_id = "12345"
120
private_key = """-----BEGIN RSA PRIVATE KEY-----
121
MIIEpAIBAAKCAQEA...
122
-----END RSA PRIVATE KEY-----"""
123
124
# Generate JWT token
125
jwt_token = get_jwt(app_id=app_id, private_key=private_key)
126
127
async with aiohttp.ClientSession() as session:
128
gh = GitHubAPI(session, "my-app/1.0")
129
130
# Use JWT to authenticate as the app
131
app_info = await gh.getitem("/app", jwt=jwt_token)
132
print(f"App name: {app_info['name']}")
133
print(f"App owner: {app_info['owner']['login']}")
134
135
# Get app installations
136
installations = []
137
async for installation in gh.getiter("/app/installations", jwt=jwt_token):
138
installations.append({
139
"id": installation["id"],
140
"account": installation["account"]["login"],
141
"permissions": installation["permissions"]
142
})
143
144
print(f"App has {len(installations)} installations")
145
146
asyncio.run(jwt_example())
147
```
148
149
### Complete GitHub App Workflow
150
151
```python
152
import asyncio
153
from gidgethub.aiohttp import GitHubAPI
154
from gidgethub.apps import get_jwt, get_installation_access_token
155
import aiohttp
156
157
class GitHubAppClient:
158
def __init__(self, app_id: str, private_key: str):
159
self.app_id = app_id
160
self.private_key = private_key
161
self._jwt_token = None
162
self._installation_tokens = {}
163
164
def _get_jwt(self):
165
"""Get or refresh JWT token."""
166
# In production, you'd want to cache and refresh JWT tokens
167
return get_jwt(app_id=self.app_id, private_key=self.private_key)
168
169
async def get_installation_client(self, session, installation_id: str):
170
"""Get a GitHubAPI client for a specific installation."""
171
gh = GitHubAPI(session, "my-app/1.0")
172
173
# Get installation access token
174
token_response = await get_installation_access_token(
175
gh,
176
installation_id=installation_id,
177
app_id=self.app_id,
178
private_key=self.private_key
179
)
180
181
# Return client with installation token
182
return GitHubAPI(
183
session,
184
"my-app/1.0",
185
oauth_token=token_response["token"]
186
)
187
188
async def get_app_client(self, session):
189
"""Get a GitHubAPI client authenticated as the app."""
190
jwt_token = self._get_jwt()
191
return GitHubAPI(session, "my-app/1.0", jwt=jwt_token)
192
193
async def app_workflow_example():
194
app_client = GitHubAppClient(
195
app_id="12345",
196
private_key=open("app-private-key.pem").read()
197
)
198
199
async with aiohttp.ClientSession() as session:
200
# Get app-level client
201
app_gh = await app_client.get_app_client(session)
202
203
# List all installations
204
installations = []
205
async for installation in app_gh.getiter("/app/installations"):
206
installations.append(installation)
207
208
# Process each installation
209
for installation in installations:
210
installation_id = str(installation["id"])
211
account_name = installation["account"]["login"]
212
213
print(f"Processing installation for {account_name}")
214
215
# Get installation-specific client
216
install_gh = await app_client.get_installation_client(
217
session, installation_id
218
)
219
220
# List repositories for this installation
221
async for repo in install_gh.getiter("/installation/repositories"):
222
print(f" Repository: {repo['full_name']}")
223
224
# Example: Create an issue
225
await install_gh.post(
226
f"/repos/{repo['full_name']}/issues",
227
data={
228
"title": "Automated issue from GitHub App",
229
"body": "This issue was created by our GitHub App!"
230
}
231
)
232
233
# asyncio.run(app_workflow_example())
234
```
235
236
### Webhook Event Handling with Apps
237
238
```python
239
from gidgethub.routing import Router
240
from gidgethub.apps import get_installation_access_token
241
import aiohttp
242
243
router = Router()
244
245
@router.register("installation", action="created")
246
async def handle_new_installation(event):
247
"""Handle new app installation."""
248
installation = event.data["installation"]
249
print(f"New installation: {installation['account']['login']}")
250
251
# You might want to store installation info in a database
252
# or perform initial setup tasks
253
254
@router.register("pull_request", action="opened")
255
async def auto_review_pr(event):
256
"""Automatically review pull requests."""
257
installation_id = str(event.data["installation"]["id"])
258
repo_name = event.data["repository"]["full_name"]
259
pr_number = event.data["pull_request"]["number"]
260
261
# Get installation token
262
async with aiohttp.ClientSession() as session:
263
gh = GitHubAPI(session, "pr-reviewer-app/1.0")
264
265
token_response = await get_installation_access_token(
266
gh,
267
installation_id=installation_id,
268
app_id="your_app_id",
269
private_key="your_private_key"
270
)
271
272
# Create installation client
273
install_gh = GitHubAPI(
274
session,
275
"pr-reviewer-app/1.0",
276
oauth_token=token_response["token"]
277
)
278
279
# Add review comment
280
await install_gh.post(
281
f"/repos/{repo_name}/pulls/{pr_number}/reviews",
282
data={
283
"body": "Thanks for the PR! Our automated review is complete.",
284
"event": "COMMENT"
285
}
286
)
287
```
288
289
### Error Handling
290
291
```python
292
import asyncio
293
from gidgethub.aiohttp import GitHubAPI
294
from gidgethub.apps import get_installation_access_token
295
from gidgethub import HTTPException
296
import aiohttp
297
import jwt
298
299
async def robust_app_auth():
300
async with aiohttp.ClientSession() as session:
301
gh = GitHubAPI(session, "my-app/1.0")
302
303
try:
304
token_response = await get_installation_access_token(
305
gh,
306
installation_id="12345",
307
app_id="67890",
308
private_key="invalid_key"
309
)
310
except HTTPException as exc:
311
if exc.status_code == 401:
312
print("Authentication failed - check app ID and private key")
313
elif exc.status_code == 404:
314
print("Installation not found - check installation ID")
315
else:
316
print(f"HTTP error: {exc.status_code}")
317
except jwt.InvalidTokenError:
318
print("Invalid JWT token - check private key format")
319
except Exception as exc:
320
print(f"Unexpected error: {exc}")
321
322
asyncio.run(robust_app_auth())
323
```
324
325
### Private Key Management
326
327
```python
328
import os
329
from pathlib import Path
330
331
def load_private_key():
332
"""Load private key from various sources."""
333
334
# Option 1: Environment variable
335
if "GITHUB_PRIVATE_KEY" in os.environ:
336
return os.environ["GITHUB_PRIVATE_KEY"]
337
338
# Option 2: File path from environment
339
if "GITHUB_PRIVATE_KEY_PATH" in os.environ:
340
key_path = Path(os.environ["GITHUB_PRIVATE_KEY_PATH"])
341
return key_path.read_text()
342
343
# Option 3: Default file location
344
default_path = Path("github-app-key.pem")
345
if default_path.exists():
346
return default_path.read_text()
347
348
raise ValueError("GitHub App private key not found")
349
350
# Usage
351
try:
352
private_key = load_private_key()
353
app_client = GitHubAppClient("12345", private_key)
354
except ValueError as exc:
355
print(f"Configuration error: {exc}")
356
```
357
358
## GitHub App Permissions
359
360
When creating a GitHub App, you need to configure permissions for what the app can access:
361
362
### Repository Permissions
363
- **Contents**: Read/write repository files
364
- **Issues**: Create and manage issues
365
- **Pull requests**: Create and manage PRs
366
- **Metadata**: Access repository metadata
367
- **Deployments**: Create deployments
368
- **Statuses**: Create commit statuses
369
- **Checks**: Create check runs
370
371
### Organization Permissions
372
- **Members**: Access organization membership
373
- **Administration**: Manage organization settings
374
375
### Account Permissions
376
- **Email addresses**: Access user email addresses
377
- **Profile**: Access user profile information
378
379
## Types
380
381
```python { .api }
382
from typing import Any, Dict
383
from gidgethub.abc import GitHubAPI
384
385
# JWT payload structure (internal)
386
JWTPayload = Dict[str, Any] # {"iat": int, "exp": int, "iss": str}
387
388
# Installation access token response
389
InstallationTokenResponse = Dict[str, Any] # {"token": str, "expires_at": str}
390
```