Automating GitHub OAuth Apps with Playwright

5min read10views

The GitHub REST API for creating OAuth apps was deprecated in 2020. ????? ragebait much. I don't know about you, but I am actively working on 5-7 projects where most of them end up in the graveyard because I just came up with a new million-dollar idea that typically requires authentication. I've left my stubbornness aside and swapped my own rolled auth for BetterAuth. Having full auth in my own database setup under five minutes is nice. But then comes the most annoying part. OAuth.

I got sick of it, and I don't value my time, and I value yours, hence I made this tool that creates OAuth apps in a split second.

The Strategy: Playwright + Persistent Auth

Since I've been using GitHub the OAuth portal hasn't changed so a tool like ChromeDriver, Puppeteer or Selenium would do the job, worst case I change out some top level constants which are DOM pointers. Standard automation (Selenium/Puppeteer) is often "headless" and "ephemeral," meaning you'd have to log in and handle 2FA every single time. That defeats the entire purpose, so after some digging I got persistence working. Which I also implemented in my instagram follower tracker where I might write about.

1. Reusing Your Session

I originally built this to save a local session folder. You'd log in once, and it would save the cookies.

But then I realized: Why log in at all?

I updated the tool to attach directly to your existing Brave or Chrome profile.

github_oauth_automator.py
1
2
3
4
5
6
# From github_oauth_automator.py
context = playwright.chromium.launch_persistent_context(
    user_data_dir="${HOME}/.config/BraveSoftware/Brave-Browser/Default", # <-- Your actual browser!
    executable_path="/usr/bin/brave-browser",
    headless=False
)

This means if you're logged into GitHub in your daily browser, the script is instantly authenticated. No login screens, no 2FA fatigue. It just works.

(Note: You do have to close your browser first, as Playwright needs exclusive file locks. That's why I use Chrome as Brave is my default browser)

2. Handling "Sudo Mode"

GitHub wants you to verify your password here and there. The script simply waits until it finds the "Confirm password" prompt.

It then automatically fills your password (if you put it in .env) or pauses for you to type it manually in the open browser window.

handle-sudo-mode.py
1
2
3
4
5
6
7
8
9
if page.query_selector("text='Confirm password'"):
    if user_password:
        # Option A: Auto-fill if configured
        page.fill("input[name='password']", user_password)
        page.click("button:has-text('Confirm')")
    else:
        # Option B: Wait for you to type it manually
        print("Please enter your password in the browser...")
        page.wait_for_selector("input[name='password']", state="detached")

After that the oauth app is created and the client is generated. Last thing to do is capture the secret as that sits behind a button that is hidden by default. Same principle as before, we wait for the button to appear and click it.

generate-secret.py
1
2
if page.query_selector("text='Generate secret'"):
    page.click("button:has-text('Generate secret')")

Now the client and secret are exposed in the DOM and we can capture them like this:

capture-credentials.py
1
2
client_id = page.query_selector("text='Client ID'").text_content()
client_secret = page.query_selector("text='Client Secret'").text_content()

And finally we write the captured client and secret via FS to the .env file.

Furthermore I've added couple of features to the tool for bulk (prod and dev) creation, and easy deletion or re-generation of existing apps. I'm a JavaScript andy and have only done a little Django and built some scrapers. But this works. Could've also built it in Node now that I think of it.

run-script.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
❯ time ./run.sh
[INFO] Launching GitHub OAuth Automator...

╔═════════════════════════════════════════════════╗
║                                                 ║
║   🔐      GitHub OAuth App Creator              ║
║       Automated OAuth application setup         ║
║                                                 ║
╚═════════════════════════════════════════════════╝


┌─────────────────────────────────────┐
│  What would you like to do?         │
├─────────────────────────────────────┤
│  1. Create new OAuth app            │
│  2. Verify existing credentials     │
│  3. View saved credentials          │
│  4. Delete OAuth app from GitHub    │
│  5. Clear browser session           │
│  6. Exit                            │
└─────────────────────────────────────┘

➤ Enter choice (1-6): 1

📝 Create New OAuth Application
────────────────────────────────────────

➤ Application name [poop]:
➤ Homepage URL [http://localhost:3000]:
➤ Callback URL [http://localhost:3000/api/auth/callback/github]:
➤ GitHub password (for sudo mode): (loaded from .env)
➤ Save to .env file? [Y/n]:
➤ Verify credentials after creation? [Y/n]:

────────────────────────────────────────
📋 Configuration Summary:
   • App Name:     poop
   • Homepage:     http://localhost:3000
   • Callback:     http://localhost:3000/api/auth/callback/github
   • Password:     ••••••••
   • Write .env:   Yes
   • Verify:       Yes
────────────────────────────────────────

➤ Proceed with these settings? [Y/n]:
20:44:43 | Using custom browser profile: /home/remcostoeten/.config/google-chrome/Default

20:44:43 | Using configured browser: /usr/bin/google-chrome
20:44:43 | Launching browser with profile: /home/remcostoeten/.config/google-chrome/Default

20:44:44 | ✅ Already logged in (session restored)
20:44:44 | 📝 Navigate to create app: poop
20:44:46 |    Filling form details...
20:44:46 |    Submitting form...
20:44:47 |    Extracting Client ID...
20:44:47 |    ✅ Client ID: 0000000000000000000000
20:44:47 |    Generating Client Secret...
20:44:48 |    Clicked via selector: input[type="submit"][value*="client secret" i]
20:44:50 |    ✅ Client Secret captured successfully!

────────────────────────────────────────────────────────────
 SUCCESS: Application Created Successfully
────────────────────────────────────────────────────────────

GITHUB_CLIENT_ID="0000000000000000000000"
GITHUB_CLIENT_SECRET="0000000000000000000000"

20:44:50 | ✅ Saved to .env file
20:44:50 | 🔍 Verifying OAuth credentials...
20:44:50 |    ✅ Client ID is recognized by GitHub
20:44:50 |    ✅ Client secret format is valid
20:44:50 |    ✅ Credentials verified successfully!
20:44:50 | 🎉 All checks passed!

┌─────────────────────────────────────┐
│  What would you like to do?         │
├─────────────────────────────────────┤
│  1. Create new OAuth app            │
│  2. Verify existing credentials     │
│  3. View saved credentials          │
│  4. Delete OAuth app from GitHub    │
│  5. Clear browser session           │
│  6. Exit                            │
└─────────────────────────────────────┘

➤ Enter choice (1-6): 6

👋 Goodbye!
________________________________________________________
Executed in   11.70 secs    fish           external
   usr time    1.61 secs  220.00 micros    1.61 secs
   sys time    0.41 secs   82.00 micros    0.41 secs

Little over 10 seconds, not bad.

Project Link: View the full script in the repository

React:

Comments

Sign in to join the conversation

Loading sign-in options...