Home

Using a GitHub App to Authenticate Private Python Dependencies in CI


When working with private Python repositories hosted on GitHub, ensuring your CI/CD pipeline can securely install dependencies can be tricky. Many developers default to using a Personal Access Token (PAT), but a more secure approach is to use a GitHub App to generate an OAuth token dynamically.

This guide walks through setting up a GitHub Actions workflow that:

  • Authenticates using a GitHub App (instead of a long-lived PAT).
  • Generates an OAuth token dynamically at runtime.
  • Uses uv to install private dependencies from a second GitHub repository.
  • Forces HTTPS authentication instead of SSH to prevent permission errors.
  • Keeps credentials secure while ensuring smooth package installation.

Why Use a GitHub App Instead of a PAT?

Many developers use a Personal Access Token (PAT) to access private repositories. However:

  • PATs have broad access and must be manually managed.
  • Tokens don’t expire unless explicitly revoked.
  • GitHub Apps allow more granular permissions and generate short-lived tokens dynamically.

A GitHub App provides a more secure, automated way to manage access for CI/CD workflows.


1. Create a GitHub App for Authentication

  1. Go to GitHub → Settings → Developer Settings → GitHub Apps.
  2. Click New GitHub App.
  3. Set the following configurations:
    • Repository Access: Select Only specific repositories and add both repositories.
    • Permissions:
      • Repository → Contents: Read-only.
    • Webhooks: Uncheck “Active” (not needed for this use case).
  4. Click Create GitHub App.
  5. Install the app on your organization or user account.
  6. Generate a private key and store it securely.

2. Store Credentials as GitHub Secrets

In the repository where CI runs (the dependent project):

  1. Go to Settings → Secrets and Variables → Actions.
  2. Add the following repository secrets:
    • GH_APP_ID → Your GitHub App ID.
    • GH_APP_PRIVATE_KEY → The base64-encoded private key.

To base64-encode the key (for multiline secret handling), run:

cat my-github-app.pem | base64 | pbcopy  # MacOS
cat my-github-app.pem | base64 | xclip -selection clipboard  # Linux

3. Modify Your GitHub Actions Workflow

Here’s the updated tests.yml file that:

  • Authenticates using the GitHub App.
  • Generates an OAuth token dynamically.
  • Forces HTTPS instead of SSH to avoid permission issues.
  • Uses uv to install private dependencies.
name: Tests with Coverage

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ["3.10", 3.11, 3.12, 3.13]
        os: [ubuntu-latest]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up mise
        uses: jdx/mise-action@v2

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: Authenticate GitHub App and generate token
        id: app-auth
        uses: tibdex/github-app-token@v1
        with:
          app_id: $
          private_key: $

      - name: Force HTTPS instead of SSH for GitHub URLs
        run: |
          git config --global url."https://x-access-token:$@github.com/".insteadOf "ssh://git@github.com/"

      - name: Install dependencies
        run: |
          uv venv
          GITHUB_ACCESS_TOKEN=$ uv sync --all-extras

      - name: Run tests with coverage
        run: mise run test-coverage

4. Ensure Your Dependencies Use HTTPS

Check your pyproject.toml to make sure dependencies are installed via HTTPS, not SSH:

[tool.uv]
dependencies = [
  "requests",
  "numpy",
  "git+https://github.com/2389-research/mbus.git@main"
]

If needed, allow the OAuth token to be passed dynamically:

[tool.uv]
dependencies = [
  "git+https://x-access-token:${GITHUB_ACCESS_TOKEN}@github.com/2389-research/mbus.git@main"
]

How This Works

  1. GitHub App Authentication
    • tibdex/github-app-token converts the App ID and Private Key into an OAuth token.
    • The generated token has temporary contents:read access to private repositories.
  2. Forcing HTTPS Instead of SSH
    • The git config command replaces all ssh://git@github.com/ URLs with an authenticated HTTPS version.
  3. uv Uses the Token for Private Dependencies
    • The GITHUB_ACCESS_TOKEN is injected so uv sync can use it for installation.

Final Notes

  • More Secure: Short-lived tokens reduce risk compared to PATs.
  • No Manual Token Rotation: Tokens refresh automatically.
  • Forces HTTPS Instead of SSH: Prevents permission errors in CI.
  • Better CI/CD Practices: This method scales well for multiple repositories.

This setup ensures a secure, automated, and scalable approach for managing private Python dependencies in GitHub Actions. 🚀