Create a Repo with a GitHub App

Sign in as a least-privilege GitHub App to create an organization repository.

Introduction

30 Second Summary

A script may need permission to create one repository. A broad personal token can hand it far more access than that single job requires.

In this project, you will replace a classic personal access token with a GitHub App that can create repositories for your organization. You will prove least privilege by creating one repository successfully before watching an unauthorized deletion request fail.

What You'll Build

Your script creates a repository in your organization before GitHub rejects a deletion request made with the same app credentials.

By the end of this project, you'll have:

  • A GitHub App settings page that shows the app's single permission. This gives you a clear record of its narrow access.
  • A reusable authentication script that signs a JWT with your app's private key. You can exchange it for a short-lived installation token whose response shows the granted permission.
  • A live repository in your GitHub organization created through the app's approved scope. A rejected deletion request gives you visible proof of least privilege.
  • Secret Mission: An optional challenge to push your skills further.

Are there any prerequisites?

You'll need a GitHub account plus either Node or Python installed on your computer. The project is free to complete because the new app permission is available on all plans.

Before We Start

Your least-privilege workflow crosses two environments. You use GitHub in a browser.

You use a local terminal for signed requests. This step confirms both environments before permission troubleshooting begins.

In this step, get ready to:
  • Confirm browser access to GitHub.
  • Verify the command-line prerequisites.
  • Confirm a JavaScript runtime is ready.
Confirm GitHub access

A personal GitHub account gives you the identity used throughout this project. A verified email is required before GitHub lets that account create a repository.

  • Choose the tab that matches your current GitHub account state.

✔️ I can sign in

Your existing account can be used for this project.

Your credentials stay inside GitHub's sign-in flow throughout this project.

  • Open the GitHub login page in your browser.
  • Complete the sign-in flow with your account.
  • Complete any verification prompt shown by GitHub.
  • Confirm the page shows your signed-in account.

You are ready when GitHub shows your account as signed in.

ⓧ I need an account

Creating a personal GitHub account costs nothing. Your credentials stay inside GitHub's account flow.

  • Open the GitHub signup page in your browser.
  • Follow the prompts to create a free personal account.
  • Verify the email address connected to the account.
  • Return to the GitHub login page.
  • Complete the sign-in flow with your new account.
  • Confirm the page shows your signed-in account.

Your account is ready when GitHub shows you as signed in.

Having trouble signing in?

  • Use the recovery option presented by GitHub if you cannot access your account.
  • Check the inbox tied to your account if GitHub requests email verification.

help me troubleshoot GitHub account access without sharing my credentials.

Check local command-line tools

The curl command sends requests from your terminal. OpenSSL handles the cryptographic operations used for app authentication.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Search for your terminal application.
  • Press Enter to open the first terminal result.
  • Check your command-line prerequisites by running:
curl --version
openssl version

What Do These Checks Prove?

  • curl --version confirms the request client is available.
  • openssl version confirms the signing toolkit is available.
  • Choose the tab that matches your terminal output.

✔️ I see two version lines

That local foundation is in place. Your terminal can make requests.

OpenSSL can handle the key operations used later in the project.

ⓧ One command is not found

A missing command blocks the later authentication flow. Install the missing tool before continuing.

  • Identify which command did not print version information.
  • Install curl from the official curl downloads if the curl check failed.
  • Install openssl using the official OpenSSL installation guidance if the OpenSSL check failed.
  • Close the terminal after installation finishes.
  • Reopen your terminal through your operating system's search bar.
  • Return to the prerequisite check above.

Still Missing a Command?

  • Close every terminal window before trying the check again.
  • Restart your computer if the new installation is still unavailable.

help me find why curl or OpenSSL is unavailable in my terminal.

Choose and verify a runtime

Node.js is the JavaScript runtime used for the local script. It gives that script a place to create a signed JSON Web Token.

  • Return to the terminal from the previous check.
  • Check whether Node.js is available by running:
node --version

What Does This Check Prove?

The node --version command asks the runtime to print its installed version. A version line confirms your terminal can run the later JavaScript script.

  • Choose the tab that matches your terminal output.

✔️ I see a version number

Node.js is ready. Your script runtime can now execute local JavaScript.

ⓧ Command not found

The official installer changes only the local runtime setup required for this project.

  • Open the official Node.js download page in your browser.
  • Download the recommended installer for your operating system.
  • Run the downloaded installer.
  • Allow the installation if your operating system requests permission.
  • Close the terminal after installation finishes.
  • Reopen your terminal through your operating system's search bar.
  • Return to the Node.js check above.

Node.js Still Unavailable?

  • Confirm the Node.js installer finished without reporting a failure.
  • Restart your computer if the terminal still cannot find Node.js.

help me troubleshoot why Node.js is unavailable after installation.

Before you run the final check, predict the three version lines your terminal will print:

  • Verify all three prerequisites in one pass by running:
curl --version
openssl version
node --version

What Does the Final Check Prove?

  • The first line confirms your request client is ready.
  • The second line confirms your signing toolkit is ready.
  • The third line confirms your script runtime is ready.

You should see version information from all three commands. No line should report a missing command.

Your local environment is ready. You can now build the authentication flow without a missing prerequisite hiding the real result.

Missing One Version Line?

  • Return to the matching outcome tab above.
  • Complete its installation path before running the final check again.

help me diagnose which project prerequisite is still missing.

Your browser access is confirmed. Your local prerequisites are ready.

Next, you'll create a free organization. You'll register the least-privilege GitHub App there.

Create Your Organization and App

A classic personal access token can carry far more authority than a repository-creation script needs. Your GitHub organization needs a dedicated identity that follows least privilege.

In this step, you will create a free organization for the lab. You will register a GitHub App that can create repositories without receiving unrelated access.

In this step, get ready to:
  • Create a free organization for the app lab.
  • Register the app under your new organization.
  • Confirm the app has one narrowly scoped permission.
Create your free organization

An organization gives the app its own account boundary. The test repository you create later belongs inside this boundary.

Choosing GitHub Free keeps this setup at $0. No paid upgrade is part of this step.

  • Click your profile picture in the upper-right corner of GitHub.
  • Click Settings.
  • Click Organizations in the Access section of the sidebar.
  • Click New organization next to the Organizations heading.
  • Click Choose GitHub Free under Choose a plan.
  • Enter <your-github-username>-app-lab in the Organization name field.
  • Enter the email address associated with your GitHub account in the Contact email field.
  • Leave This organization is owned by a business clear for this personal lab.
  • Click Next.
  • Complete any account verification GitHub requests.
  • Continue through the remaining prompts without adding organization members.

You will see the organization page for <your-github-username>-app-lab. Your signed-in account owns this free organization.

  • Record the organization page URL here: https://github.com/<your-github-username>-app-lab.

Can't create the organization?

Make sure the organization name starts with your exact GitHub username. Keep the -app-lab suffix at the end.

If GitHub requests verification or reports an account restriction, complete the displayed account check first. Help me resolve a GitHub organization creation problem.

Register the GitHub App

A GitHub App is an identity for automation. Registering it under the organization keeps its ownership separate from your personal account.

  • Click your profile picture in the upper-right corner of GitHub.
  • Click Your organizations.
  • Click Settings beside <your-github-username>-app-lab.
  • Click Developer settings in the left sidebar.
  • Click GitHub Apps in the left sidebar.
  • Click New GitHub App.

The registration form defines the app's identity. It also controls which GitHub features the app can access.

  • Enter Repo Creator Lab in the GitHub App name field.
  • Enter https://github.com/<your-github-username>-app-lab in the Homepage URL field.

The form now identifies the app as Repo Creator Lab. Its homepage points back to your organization.

A webhook sends event notifications to an external server. This app only makes API requests from your script.

  • Deselect Active in the webhook section.

GitHub no longer requires a webhook URL. The app will not subscribe to webhook events.

Grant only repository creation access

The permission list is long, so this part can feel fiddly. You only need the entry that allows the app to create repositories.

  • Find Repository permissions under Permissions.
  • Open the dropdown beside Repository creation.
  • Select Read & write.

The selected access corresponds to repository_creation:write. This permission allows the app to create a repository in the account where it is granted.

Why use one narrow permission?

Repository creation access gives the script the capability it needs. It does not grant broader repository administration controls such as deletion or transfer.

This narrow scope replaces the broad authority commonly carried by a classic personal access token. The permission boundary becomes something you can test.

  • Confirm every other dropdown under Repository permissions shows No access.
  • Confirm every dropdown under Organization permissions shows No access.
  • Confirm every dropdown under Account permissions shows No access.
  • Select Only on this account under Where can this GitHub App be installed?.
  • Click Create GitHub App.

That's the identity boundary in place. GitHub opens the settings page for Repo Creator Lab with an App ID assigned to it.

Can't finish registering the app?

Confirm you are editing Developer settings for the organization. Personal account settings would register the app under the wrong owner.

Check that Active is clear if GitHub asks for a webhook URL. Help me troubleshoot my GitHub App registration form.

Before you review the saved permissions, how many entries do you expect to have access above No access?

  • Click Permissions & events in the app settings sidebar.

You will see one enabled permission. Repository creation shows Read & write while unrelated repository, organization, and account permissions remain at No access.

  • Use your browser's back button to return to the app settings page showing the App ID.
  • Record the number beside App ID here: your App ID.

Your free organization is owned by the signed-in account. Repo Creator Lab is registered under it with only the organization repository-creation write permission required by this project.

Your organization and least-privilege app are ready. Next, you will generate the private key that lets your script prove it belongs to this app.

Generate an App Private Key

Your Repo Creator Lab GitHub App now has one narrow write permission. It needs a cryptographic identity before a script can authenticate as the app.

A private key signs the JWT used in that authentication flow. GitHub keeps the public portion.

The downloaded PEM file stays under your control.

In this step, get ready to:
  • Generate one private key for Repo Creator Lab.
  • Store the downloaded PEM file with owner-only permissions.
  • Confirm GitHub lists the generated key.
Generate a new private key

This is the sensitive part of the setup. The storage steps below keep the private portion local.

  • In the Repo Creator Lab settings page from earlier, click Credentials in the left sidebar.
  • Click Key pairs.
  • Click New key.
  • Choose your usual downloads folder if your browser asks for a destination.
  • Wait for the download to finish.

Good work getting through the sensitive click. Repo Creator Lab now has its key pair.

You should see one key listed in the Key pairs area. Your browser also downloads the matching .pem file.

Finding a browser download's full path can be fiddly. Your file manager's location details show where the file landed.

  • Use your browser's completed downloads list to locate the new .pem file.
  • Use your file manager to copy the file's full path.
  • Record the path here: ~/Downloads/downloaded-key.pem.

What Did GitHub Keep?

GitHub stores only the public portion of the key pair. That portion lets GitHub verify signatures from your app.

The downloaded PEM contains the private portion. It stays outside your code repository.

It also stays out of screenshots and messages.

Store and verify the private key

A predictable path lets your later script find the key. Owner-only permissions reduce access to the account that owns the file.

  • Open your operating system's application search.
  • Type Terminal.
  • Open the terminal result that uses your Unix shell.
  • Create the secure folder by running these commands:
mkdir -p ~/github-app-lab
mv [[DOWNLOADED_KEY_PATH="~/Downloads/downloaded-key.pem"]] ~/github-app-lab/repo-creator-lab.private-key.pem
chmod 600 ~/github-app-lab/repo-creator-lab.private-key.pem

What Do These Commands Do?

  • The mkdir command creates ~/github-app-lab in your home directory.
  • The mv command moves the downloaded key into that folder.
  • The destination path gives the key the stable name repo-creator-lab.private-key.pem.
  • The chmod command applies mode 600 to the file.
  • Mode 600 gives the owner read and write access. It gives no permissions to the group or other users.

Before you check, consider whether the file should show any permissions for its group or other users.

  • Inspect the stored key by running this command:
ls -l ~/github-app-lab/repo-creator-lab.private-key.pem

How to Read the Result

The permission pattern at the start of the line should be -rw-------. Only the file owner has read and write access.

The line should end with repo-creator-lab.private-key.pem. This confirms the key reached its required location.

Your local copy is now protected. The terminal confirms both its location and its owner-only permissions.

Don't See Owner-Only Permissions?

  • Confirm that ~/Downloads/downloaded-key.pem points to the downloaded .pem file.
  • Run the permission command again if the file exists with a different permission pattern.

help me fix my GitHub App private key path or permissions.

  • Switch back to the Repo Creator Lab settings page from earlier.
  • Close your browser's downloads panel.
  • Confirm the Key pairs area shows one active key.

That sensitive piece is now under your control. Repo Creator Lab has one active key record.

Your matching private key is stored at ~/github-app-lab/repo-creator-lab.private-key.pem with owner-only access.

Your app's private credential is ready for controlled use. Next, you'll install Repo Creator Lab on your organization.

Install the App on Your Organization

The private key from the last step gives Repo Creator Lab a secure way to prove its identity. The app still needs a home where that identity can act.

An installation connects a GitHub App to one account within the permission boundary you already chose. In this step, you will install the app on your lab organization. You will capture the installation ID that scripts use to target that connection.

In this step, get ready to:
  • Install Repo Creator Lab on your lab organization.
  • Review the permission granted to the installation.
  • Capture the installation ID from the installation page.
Install Repo Creator Lab

Installing Repo Creator Lab authorizes it to act inside one account. The permission review is your chance to confirm that narrow boundary before approval.

Granting an app access can feel risky. Repo Creator Lab requests only the repository-creation write permission that you configured earlier.

  • Select Install App in the left sidebar of the app settings page from earlier.
  • Click Install beside <your-github-username>-app-lab.

GitHub shows a final permission review for the organization. Repository selection controls are omitted because Repo Creator Lab requests no repository permissions.

  • Confirm that <your-github-username>-app-lab appears as the target account.
  • Confirm that the summary shows only the repository-creation write permission configured earlier.
  • Click Install to approve the installation.

Good work. Repo Creator Lab is now installed on <your-github-username>-app-lab.

Don't see the installation page?

  • Return to the Repo Creator Lab settings page from earlier through your browser history.
  • Select Install App in the left sidebar.
  • Choose Install beside <your-github-username>-app-lab.
  • Click the final Install button after checking the permission summary.

Ask for help if the installation flow still does not reach the organization page: help me install my organization-owned GitHub App on the correct organization.

Capture the installation ID

GitHub assigns an installation ID to the connection you just created. Your script uses this value to request credentials for that exact installation.

App ID and Installation ID

The App ID identifies the Repo Creator Lab registration.

The installation ID identifies its installation on <your-github-username>-app-lab.

Pulling one number out of a long browser address is a little fiddly. The value you need is the final number in the installation page URL.

  • Click the address bar in the browser tab showing the installation page.
  • Drag across the final number in the URL to select it.
  • Press Cmd+C (macOS) or Ctrl+C (Windows) to copy the selected number.
  • Record the copied value here: <INSTALLATION_ID>.

Before you check, do you expect the account on this page to be your personal account or your lab organization?

  • Confirm that the page names <your-github-username>-app-lab as the account where Repo Creator Lab is installed.
  • Compare the final number in the URL with <INSTALLATION_ID>.

You should see Repo Creator Lab installed on <your-github-username>-app-lab. The final number in the URL should match <INSTALLATION_ID>.

Can't find the installation ID?

  • Return to the installation page that opened after approval through your browser history.
  • Select only the final numeric segment of the page URL.

Ask for help if the page URL still does not reveal a numeric installation ID: help me identify the installation ID on my GitHub App installation page.

Repo Creator Lab now has a least-privilege installation. You also have the installation ID that targets it. Next, you will combine that ID with the private key to request a short-lived token.

Exchange a JWT for an Installation Token

Your GitHub App is installed on the organization. Its permission boundary is ready.

The installation still needs a temporary credential before it can call GitHub's REST API. A signed JSON Web Token (JWT) proves the app's identity before GitHub returns a short-lived installation token.

In this step, get ready to:
  • Configure a local script with the app installation details.
  • Sign a short-lived JWT with the app's private key.
  • Exchange the JWT for an installation token and inspect its permissions.
Capture the app identifiers

The script needs the App ID to identify Repo Creator Lab. It also needs the installation ID to target the copy installed on your organization.

  • Switch back to the Repo Creator Lab settings tab from earlier.
  • Copy the number next to App ID.
  • Store the copied number here: <APP_ID>.
  • Store the installation ID you copied earlier here: <INSTALLATION_ID>.
  • Store your organization name here: <your-github-username>-app-lab.

Why are both IDs needed?

The App ID becomes the JWT issuer. The installation ID selects the organization installation that receives the temporary token.

Keeping these roles separate lets one app authenticate independently for each place where it is installed.

Build the token exchange script

The script combines the identifiers with the private key stored in ~/github-app-lab. It keeps the private key on your machine throughout the exchange.

  • Start a Bash terminal from your computer's app search.
  • Begin writing ~/github-app-lab/create-repo-with-app by running this command:
cat > ~/github-app-lab/create-repo-with-app

Why is the terminal waiting?

The cat command directs your next input into the new file. The terminal keeps waiting until you signal that the input is complete.

This part is a little fiddly because the terminal stays blank while it accepts the script. The prompt returns as soon as you finish the input correctly.

  • Paste the script below into the waiting terminal.
  • Press Ctrl+D after pasting the final line.
#!/usr/bin/env bash
set -euo pipefail
# Configure this installation without storing any credential.
APP_ID="[[APP_ID=\"<APP_ID>\"]]"
INSTALLATION_ID="[[INSTALLATION_ID=\"<INSTALLATION_ID>\"]]"
ORGANIZATION="[[ORGANIZATION=\"<your-github-username>-app-lab\"]]"
PRIVATE_KEY_PATH="$HOME/github-app-lab/repo-creator-lab.private-key.pem"

# Backdate issuance to absorb small clock differences.
NOW=$(date +%s)
IAT=$((NOW - 60))
EXP=$((NOW + 600))

# Encode and sign the JWT with the app private key.
b64enc() {
  openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'
}
HEADER=$(printf '%s' '{"typ":"JWT","alg":"RS256"}' | b64enc)
PAYLOAD=$(printf '{"iat":%s,"exp":%s,"iss":"%s"}' "$IAT" "$EXP" "$APP_ID" | b64enc)
SIGNATURE=$(printf '%s' "${HEADER}.${PAYLOAD}" | openssl dgst -sha256 -sign "$PRIVATE_KEY_PATH" | b64enc)
JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}"

# Exchange the JWT for the installation's short-lived token.
RESPONSE=$(curl --request POST \
  --url "https://api.github.com/app/installations/${INSTALLATION_ID}/access_tokens" \
  --header "Accept: application/vnd.github+json" \
  --header "Authorization: Bearer ${JWT}" \
  --header "X-GitHub-Api-Version: 2026-03-10")
# Redact the credential while leaving its permissions visible.
printf '%s\n' "$RESPONSE" | sed 's/\("token"[[:space:]]*:[[:space:]]*\)"[^"]*"/\1"REDACTED"/'

What does this script do?

  • The configuration variables identify Repo Creator Lab and its organization installation.
  • The iat value starts slightly in the past to absorb small clock differences.
  • The exp value limits the JWT to ten minutes.
  • OpenSSL signs the encoded header and payload with the private key using RS256.
  • The curl request sends the JWT to POST /app/installations/<INSTALLATION_ID>/access_tokens.
  • The final filter replaces the returned credential with REDACTED while preserving the permissions response.

Your shell prompt returns after Ctrl+D. That confirms the script has been saved at ~/github-app-lab/create-repo-with-app.

✔️ Awesome, I've got everything!

Your token exchange script is configured and saved.

ⓧ I'd like to double check the full code

#!/usr/bin/env bash
set -euo pipefail
# Configure this installation without storing any credential.
APP_ID="[[APP_ID=\"<APP_ID>\"]]"
INSTALLATION_ID="[[INSTALLATION_ID=\"<INSTALLATION_ID>\"]]"
ORGANIZATION="[[ORGANIZATION=\"<your-github-username>-app-lab\"]]"
PRIVATE_KEY_PATH="$HOME/github-app-lab/repo-creator-lab.private-key.pem"

# Backdate issuance to absorb small clock differences.
NOW=$(date +%s)
IAT=$((NOW - 60))
EXP=$((NOW + 600))

# Encode and sign the JWT with the app private key.
b64enc() {
  openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'
}
HEADER=$(printf '%s' '{"typ":"JWT","alg":"RS256"}' | b64enc)
PAYLOAD=$(printf '{"iat":%s,"exp":%s,"iss":"%s"}' "$IAT" "$EXP" "$APP_ID" | b64enc)
SIGNATURE=$(printf '%s' "${HEADER}.${PAYLOAD}" | openssl dgst -sha256 -sign "$PRIVATE_KEY_PATH" | b64enc)
JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}"

# Exchange the JWT for the installation's short-lived token.
RESPONSE=$(curl --request POST \
  --url "https://api.github.com/app/installations/${INSTALLATION_ID}/access_tokens" \
  --header "Accept: application/vnd.github+json" \
  --header "Authorization: Bearer ${JWT}" \
  --header "X-GitHub-Api-Version: 2026-03-10")
# Redact the credential while leaving its permissions visible.
printf '%s\n' "$RESPONSE" | sed 's/\("token"[[:space:]]*:[[:space:]]*\)"[^"]*"/\1"REDACTED"/'
Run the exchange and inspect permissions

Execution permission lets your shell run the script directly. Each run creates a fresh JWT before requesting an installation token that lasts for one hour.

  • Make the script executable by running this command:
chmod +x ~/github-app-lab/create-repo-with-app

What does this command do?

The chmod +x command adds execution permission to create-repo-with-app. Your shell can now run the file as a program.

Before you run the script, how many permission entries do you expect the installation token to receive?

  • Exchange the JWT for an installation token by running this command:
~/github-app-lab/create-repo-with-app

What should I see?

The response includes a token field with the value REDACTED. It also includes an expires_at timestamp.

Inside the permissions object, you should see one repository-creation entry set to write. That single entry mirrors the permission granted to Repo Creator Lab.

That exchange worked. Your app can now request a short-lived credential with only its repository-creation permission.

Token exchange rejected?

  • Confirm the App ID matches the value shown on the Repo Creator Lab settings page.
  • Confirm the installation ID belongs to the Repo Creator Lab installation on your organization.
  • Confirm your computer's clock is set automatically because JWT validation depends on accurate timestamps.
  • Confirm the private key still exists at ~/github-app-lab/repo-creator-lab.private-key.pem.

help me diagnose why my GitHub App JWT exchange failed

Your exchange script now produces the credential Repo Creator Lab needs. Next, you will use that installation token to create a repository in your organization.

Create a Repository with the App

Your GitHub App can already mint a short-lived installation token. The next proof is a real repository created with that token.

In this step, you will extend the existing script with GitHub's organization repository endpoint. You will run it once to create app-created-repo in your organization.

In this step, get ready to:
  • Add an authenticated repository creation request to the existing script.
  • Run the script to create app-created-repo in your organization.
  • Confirm the new repository appears on GitHub.
Add the repository request

The REST API endpoint accepts a GitHub App installation token. Its request body needs the name of the repository to create.

What does the request need?

  • The endpoint pattern is POST /orgs/{org}/repos.
  • The authorization header carries the short-lived installation token from the existing token exchange.
  • The required name field contains app-created-repo.
  • GitHub checks the token's repository_creation:write grant before creating the repository.
  • The script displays the repository creation response so you can confirm which repository GitHub created.
  • Switch back to ~/github-app-lab/create-repo-with-app from the previous step.
  • Find the block that reads the permissions returned with the installation token.
  • Extend the script after that block with the repository request defined above.
  • Save ~/github-app-lab/create-repo-with-app.

Why use the installation token?

The JWT proves the app's identity during the token exchange. The installation token carries the permission granted to that app installation.

Using that installation token ties the repository request to the app's approved scope. The request can only reach capabilities represented by the token.

Run the script and confirm the repository

The updated script now completes the full authentication flow before sending the repository request. One run should create exactly one repository named app-created-repo.

Before you run it, consider whether the installation token can create a repository with its single grant.

  • Run the updated executable from your terminal with this command:
~/github-app-lab/create-repo-with-app

What happens during this run?

  • The script signs a short-lived JWT with the app's private key.
  • The script exchanges that JWT for an installation token.
  • The script sends the installation token to POST /orgs/{org}/repos.
  • GitHub creates app-created-repo under your organization.

You should see a successful repository creation response that identifies app-created-repo in your organization.

Repository request not succeeding?

Check that the request uses the installation token returned by the token exchange. A JWT only authenticates the app for installation token operations.

Confirm the configured organization matches the organization where Repo Creator Lab is installed. Also confirm that a repository named app-created-repo does not already exist.

Compare your request method, endpoint pattern, authorization header, and repository name with the contract above.

Help me diagnose why my GitHub App installation token cannot create the organization repository.

  • Return to the GitHub organization page from earlier.
  • Refresh the organization page.
  • Confirm app-created-repo appears in the repository list.

You should see app-created-repo inside your organization. That is the key proof that Repo Creator Lab can create a repository with its short-lived installation token.

Your app has now turned its narrow permission into a real repository. Next, you will test where that permission stops.

Verify the Permission Boundary

Repo Creator Lab has already created app-created-repo with its single repository-creation permission. That result proves your GitHub App can perform its intended job.

Least privilege also needs proof that the app cannot cross its permission boundary. You will point the same short-lived token at a delete request. The response shows what GitHub allows.

In this step, get ready to:
  • Reuse the app's installation token for an out-of-scope delete request.
  • Observe GitHub refuse the delete request.
  • Prove the repository remains after the refused request.
Prepare the out-of-scope request

An installation token carries the exact permissions granted to the app installation. Reusing that token for a deletion attempt isolates authorization as the deciding factor.

A deletion request can feel risky. This one targets only your test repository.

Shape of the boundary test

The script mints a fresh token during this run. The temporary request reuses that token.

Its method is DELETE. Its path is DELETE /repos/<your-github-username>-app-lab/app-created-repo.

The request contains no repository-creation payload. Its response stays visible in the terminal output.

  • Return to the editor you used earlier for ~/github-app-lab/create-repo-with-app.
  • Locate the final API request by looking for POST /orgs/<your-github-username>-app-lab/repos.
  • Temporarily replace that request block with the boundary-test shape above.
  • Save ~/github-app-lab/create-repo-with-app.
Run the boundary test

Before you run it, consider whether GitHub permits the deletion.

  • Run the temporarily adapted script by using this command:
~/github-app-lab/create-repo-with-app

What does this test prove?

The script first signs its short-lived JWT. It exchanges the JWT for an installation token.

The temporary request sends that token to the deletion endpoint. GitHub evaluates the request against the app's granted permission.

You'll see GitHub refuse the deletion request. This is the intended boundary result.

That refusal proves your permission boundary works. Repo Creator Lab can create repositories without deletion authority.

Did the request behave differently?

  • Confirm the temporary request uses the installation token generated by the script.
  • Verify the request method is DELETE.
  • Verify the path is DELETE /repos/<your-github-username>-app-lab/app-created-repo.
  • Return to Repo Creator Lab's settings page if the repository disappeared.
  • Confirm the app still has no unrelated permissions.

Help me diagnose why my GitHub App deletion boundary test did not produce the expected refusal.

Confirm the repository survived

The terminal shows GitHub's decision. A browser check shows the effect on the repository.

  • Return to the editor containing ~/github-app-lab/create-repo-with-app.
  • Press Cmd+Z (macOS) or Ctrl+Z (Windows) until the original request shows POST /orgs/<your-github-username>-app-lab/repos.
  • Save ~/github-app-lab/create-repo-with-app.

Your script now points back to the repository-creation endpoint. The refused response remains available in your terminal.

Before you refresh the organization page, predict whether app-created-repo is still there.

  • Switch back to the GitHub organization page from earlier.
  • Refresh the organization page.

You'll still see app-created-repo in <your-github-username>-app-lab. The refused request left the repository intact.

You have now demonstrated both sides of the permission boundary. The app can create a repository while GitHub blocks an action beyond its granted scope.

Secret mission

Verify a Private-Key Rotation Candidate

A replacement key is trustworthy after you verify which GitHub App it belongs to. Generate a temporary candidate for a fingerprint check. Revoke it without disturbing the working key.

Clean Up Your Resources

Clean Up Your Resources

The GitHub resources in this project have no ongoing cost. Choose whether to keep them available, pause access, or delete them.

Resources you used:

  • Free GitHub organization named <your-github-username>-app-lab.
  • Test repository named app-created-repo.
  • GitHub App registration named Repo Creator Lab.
  • GitHub App installation on <your-github-username>-app-lab.
  • Active private key registered for Repo Creator Lab.
  • Local ~/github-app-lab folder containing the script and private key.

Keep everything running

No action is needed. Choose this option if you plan to reuse the app or adapt the script for another least-privilege workflow.

  • Keep <your-github-username>-app-lab available for future GitHub App experiments.
  • Keep app-created-repo as evidence that the scoped app created a repository.
  • Leave Repo Creator Lab installed on the organization.
  • Protect ~/github-app-lab/repo-creator-lab.private-key.pem with its existing owner-only permissions.
  • Keep ~/github-app-lab/create-repo-with-app as a template for replacing other broad personal access tokens.

Pause - I'll come back to this later

Pausing here means disabling the current signing key while keeping the app registration and project files. You can generate a replacement key when you return.

  • Switch back to the Repo Creator Lab settings page from earlier.
  • Locate the active private key listed for the app.
  • Revoke the active private key.
  • Confirm that the app no longer lists an active private key.

The revoked key can no longer sign new authentication requests. Removing its local file prevents it from being mistaken for a working credential later.

  • Delete ~/github-app-lab/repo-creator-lab.private-key.pem with your file browser.
  • Keep ~/github-app-lab/create-repo-with-app as your reusable script template.
  • Generate a new private key from the app settings page when you return.
  • Store the replacement key at ~/github-app-lab/repo-creator-lab.private-key.pem.
  • Restore owner-only permissions on the replacement key file.

Delete - I don't want to use this again

Remove all project resources to dismantle the lab completely. Deletion is permanent, so each check below confirms the target before you remove it.

  • Return to the app-created-repo repository from earlier.
  • Go to the repository settings area.
  • Use the permanent repository deletion control.
  • Enter <your-github-username>-app-lab/app-created-repo in the confirmation prompt.
  • Complete the repository deletion.

You should no longer see app-created-repo in the organization. This confirms that the project repository is gone.

  • Switch back to the Repo Creator Lab settings page.
  • Locate the active private key listed for the app.
  • Revoke the active private key.
  • Return to the GitHub App installation page from earlier.
  • Uninstall Repo Creator Lab from <your-github-username>-app-lab.
  • Complete the installation removal confirmation.

The app installation should no longer appear for the organization. This confirms that the app has lost its organization access.

  • Return to the Repo Creator Lab settings page.
  • Go to the permanent app deletion area.
  • Delete the Repo Creator Lab registration.
  • Complete the app deletion confirmation.

You should no longer see Repo Creator Lab among the organization's registered apps. The organization can now be removed without leaving the project app behind.

  • Return to the settings area for <your-github-username>-app-lab.
  • Confirm that the organization contains no unrelated work.
  • Use the permanent organization deletion control.
  • Complete the organization deletion confirmation.

You should no longer see <your-github-username>-app-lab in your organization list. The remaining project resource is the local folder.

Check the Folder Before Deleting It

Folder deletion is permanent. Confirm that ~/github-app-lab contains only the project script and private key before continuing.

  • Delete the local ~/github-app-lab folder by running this command:
rm -rf ~/github-app-lab

What Does This Command Remove?

The rm -rf command permanently removes ~/github-app-lab. It removes create-repo-with-app plus repo-creator-lab.private-key.pem if that key file is still present.

  • Return to your home folder with your file browser.
  • Confirm that github-app-lab no longer appears.

Folder Still Present?

  • Check that the command targets the exact path ~/github-app-lab.
  • Confirm that your signed-in user owns the folder.
  • Ask for help removing the folder safely.

Nice Work!

Nice Work!

You did it! You replaced a broad personal access token with a least-privilege GitHub App that creates a repository through a short-lived credential. GitHub then refused the app's out-of-scope deletion request.

You've learned how to:

  • Register a least-privilege GitHub App with a single write permission for organization repository creation.
  • Create a signed JWT. Exchange it for a short-lived installation token whose response shows the app's narrow permission.
  • Create app-created-repo in your organization. Prove the permission boundary by watching GitHub refuse an out-of-scope deletion request.
  • Secret Mission: Extend the project through an optional challenge that pushes your least-privilege automation skills further.

Ready to quiz yourself?