splash image

Still Using PATs in 2025? Time to move to Github Apps

Avoiding PATs and service accounts

Posted on 9 min read

Github Github Actions cloud devops

If you’re a developer using Github and working with CI/CD pipelines or custom tooling, chances are you’ve used Personal Access Tokens (PATs) to interact with GitHub. They’re simple and get the job done - but they also come with serious downsides: over-permissioned access, long-lived credentials, limited visibility, and no clear separation between user and automation; even if you are using a service user for your automation, there’s still downsides to it.

The good news? GitHub Apps are now a mature alternative which is both safer and scalable. Designed specifically for automation, they provide fine-grained permissions, short-lived tokens, and a clear identity for your tooling — so your scripts and bots stop pretending to be you. Whether you’re maintaining open source projects or deploying enterprise software, you can now move away from PATs and build your tooling on something better. Let’s look at the reasoning and a simple example to show how to use Github Apps in CI/CD workflows!

Personal Access Tokens (PATs) & Github Apps

PATs are a way to authenticate to GitHub on behalf of a user, allowing access to the GitHub API or perform Git operations. They function like passwords and are tied directly to an user account. When used, a PAT grants access to the resources and repositories that the user has permissions for.

GitHub offers two types of PATs:

  • Classic PATs are the older option; they provide broad access to an user’s account based on generic scopes. While easier to set up, it is harder or impossible to limit their access to specific repositories or permissions. Github organizations can block the usage of such tokens too, limiting their usage.

  • Fine-Grained PATs are a more secure alternative, allowing their access to be limited to specific repositories and permissions. Github organizations can limit, review and audit this type of token, increasing auditibility and visibility of their use.

Even with the security improvements when using Fine-Grained PATs, both types are still tied to a user identity - meaning automation that uses them to impersonate a person. Another concern is their token: long-lived and hard to rotate, a risk if it is leaked. A lot of improvements have been done to PATs, but they are still difficult to scope precisely, to identify their ownership and trace their usage.

Setting up a classic PAT

Setting up a classic PAT

GitHub Apps are the alternative designed to provide a way of building integrations and automating workflows within the GitHub ecosystem. Unlike PATs, GitHub Apps have their own identity and authenticate using short-lived tokens. This allows them to act as standalone entities with precisely scoped permissions and limited access to repositories. They can be installed from individual repositories, to entire organizations, and even across organizations, making them highly flexible for everything from CI/CD pipelines to custom bots and internal tools. They also provide better auditability, as all actions taken by the app are clearly logged as coming from the app itself and not an user.

GitHub Apps also offer significantly higher API rate limits compared to Personal Access Tokens. While PATs are restricted to 5,000 requests per hour per user, GitHub Apps can make up to 15,000 requests per hour per installation. For GitHub Enterprise customers, this limit is even higher - 15,000 requests per repository per hour. This substantial increase in throughput is especially valuable in high-volume CI/CD environments, where automated processes can quickly hit API limits due to frequent builds, deployments, and status checks. Relying on PATs in such scenarios can lead to throttling, delays, and operational bottlenecks—issues that GitHub Apps are far better equipped to handle.


Feature / CapabilityClassic PATsFine-Grained PATsGitHub Apps
IdentityTied to a user accountTied to a user accountIndependent app identity
Permission ScopeBroad scopes (e.g., repo, admin:org)Narrow, repository or account specific, action-based scopesHighly granular, repo/org-level per permission
Token LifetimeLong-lived (optional expiration feature added)Long-lived (with optional expiration)Short-lived (1 hour) tokens
Authentication ModelStatic tokenStatic tokenJWT + short-lived access token
Audit ClarityAppears as the userAppears as the userClearly shown as app activity
Repository Access ControlAll user-accessible reposSpecific repos chosen at token creationInstallable per repo or org
API Rate Limit5,000 req/hr per user5,000 req/hr per user15,000 req/hr per app installation
Token RotationManualManualAutomated / programmatic
Risk of OverexposureHigh (broad, static access)Medium (scoped but still user-based)Low (scoped, short-lived, app-isolated)
Recommended ForLegacy scripts, simple toolsManually controlled automation, limited scopeCI/CD, bots, secure integrations
User DependencyYesYesNo (user-independent)

Using Github Apps - an example

For this example, we will look at a simple (but one of the most common!) use cases for using PATs: committing changes to a repository. We will be creating and running a Github workflow that uses a Github App to commit to a different repository.

To start, have a github user account, and create two repositories, for example: gitapp_source and gitapp_destination. Make sure the destination repository has at least one commit (so that refs/heads/main is present for this example to work).

Setting up the Github App

  1. Create a new Github App

    Let’s start by creating a Github App. Go to your user Settings -> Under Developer Settings and under Github Apps, click New Github App.

    Github Apps can be owned by Users or Organizations. For Organization owned Apps, go to the Organization Developer Settings instead. Organization Github Apps can have administrator users assigned!

    Fill the mandatory fields GitHub App name and Homepage URL. Disable the Webhook option for events, and under permissions, click Repository Permissions and toggle the Contents access to Read and write; this will grant the Application full source code access in the repositories it gets installed to.

    Scroll down and click Create Github App. You will be redirected to the newly create Github App’s page. Take note of the App ID number, we will need it later!

  2. Generate a Github App private key

    Now the application is created, we need to generate a Private key, used to assume the Github App.

    In the Github App page, scroll down to the Private Keys section. Click Generate a new Private Key, and save it in a safe location.

  3. Installing the Github App

    Let’s install our Github App in the destination repository.

    On the left hand side, click on the Install App page, choose the account of the destination repository and click Install.

    Installing a Github App

    Installing a Github App

    Github will now ask you which repositories you want to install the App; by default it installs for all repositories. Following best practices, we select Only selected repositories, choose the previously created destination repository and click Install. Notice the list the permissions that we are granting*.

    Github App repository instalation

    Github App repository instalation

And we are done for the Github App! You should have the App ID and the generated Private key required for the next steps.

*We can change the permissions an application requires any time after creation. Entities that have the app installed will be notified of the new requirements and have a choice to refuse or accept the new requirements.

Github Actions environment variables

We will be assuming the Github App identity from the source repository to write into the destination repository. Let’s configure the Github Actions variables and secrets in the source repository with the required values.

  1. Under the source repository Settings, go to Secrets And Variables and click Actions.

  2. In the Secrets tab, click New Repository Secret, set the name to PRIVATE_KEY and the copy the content of the key file that you downloaded in the previous steps to the value field.

  3. Click Add Secret.

  4. Now go to the Variables tab and create the following repository variables: Name: APP_ID Value: the app id you saved in the previous steps (Example: 1394137)

    Name: TARGET_REPOSITORY Value: the target repository name, in our example gitapp_example_destination

You should now have under Secrets tab the PRIVATE_KEY, and under the Variables tab APP_ID and TARGET _REPOSITORY.

Github Variables screenshot

Secrets are designed to store sensitive data such as API keys, credentials, or tokens. They are encrypted, hidden after creation, and GitHub automatically redacts their values in logs to prevent accidental exposure. Variables are stored in plain text and are visible in the repository settings, making them unsuitable for confidential data.

Github workflow using a Github App

The only piece missing is the github action workflow! In the source repository, create and commit the following workflow (.github/workflows/createCommit.yml).

name: Create file & commit to another repo
on: workflow_dispatch

jobs:
    create_and_build:
        name: Create & Save
        runs-on: ubuntu-latest
        steps:
            # Generate a temporary token for this run
            - name: Generate token
              uses: actions/create-github-app-token@v1.11.1
              id: app-token
              with:
                app-id: ${{ vars.APP_ID }}
                private-key: ${{ secrets.PRIVATE_KEY }}
                owner: ${{ github.repository_owner }}
                repositories: ${{ vars.TARGET_REPOSITORY }}
            # Checkout the target repository 
            - name: Set/get environment from checkout
              uses: actions/checkout@v4
              with:
                token: ${{ steps.app-token.outputs.token }}
                repository: ${{ github.repository_owner }}/${{ vars.TARGET_REPOSITORY }} 
                path: target_repo
            # Create file
            - name: Push code
              run: |
                echo "Hello from ${{ github.action_repository }} run ${{ github.run_id }}" > target_repo/hello.txt
            # Commit and Push
            - name: Push code
              run: |
                cd target_repo
                git config user.name github-actions
                git config user.email github-actions@github.com
                git add .
                git commit -m "Update run ${{ github.run_id }}"
                git push

Go to the actions -> workflows for the source repository and manually trigger a run:

alt text

Once the workflow finishes, you should see a new file in the destination repository!


Conclusion

GitHub Apps provide a secure, scalable, and modern alternative to Personal Access Tokens (PATs). They authenticate with their own identities, and can be set up with granular and repository level permissions. Their tokens are short-lived, and enable the Centralized revocation and rotation of credentials. Because Apps can target only the repos that need them, blast radius shrinks dramatically compared with PATs that inherit a user’s full access. And by tying automation to a dedicated app rather than individual employees, we can make our CI more resilient, avoiding risks and paying an extra user license for every bot account.

Moreover a single GitHub App can be installed on dozens of repositories and organizations without multiplying credentials, making it perfect for multi‑tenant SaaS integrations. You can even list it on the GitHub Marketplace to reach new customers, something PATs can’t offer.

Hopefully with this example you understand Github Apps a bit better, and can take steps to make your platform more secure, manageable and built to scale with your use cases.

References