How to streamline GitHub API calls in Azure Pipelines
Build a custom Azure DevOps extension that eliminates the complexity of JWT generation and token management, enabling powerful automation and enhanced security controls.
Azure Pipelines is a cloud-based continuous integration and continuous delivery (CI/CD) service that automatically builds, tests, and deploys code similarly to GitHub Actions. While it is part of Azure DevOps, Azure Pipelines has built-in support to build and deploy code stored in GitHub repositories.
Because Azure Pipelines is fully integrated into GitHub development flows, pipelines can be triggered by pushes or pull requests, and it reports the results of the job execution back to GitHub via GitHub status checks. This way, developers can easily see if a given commit is healthy or block pull request merges if the pipeline is not compliant with GitHub rulesets.
When you need additional functionality, you can use either extensions available in the marketplace or GitHub APIs to deepen the integration with GitHub. Below, we’ll show how you can streamline the process of calling the GitHub API from Azure Pipelines by abstracting authentication with GitHub Apps and introducing a custom Azure DevOps extension, this will allow pipeline authors to easily authenticate against GitHub and call GitHub APIs without implementing authentication logic themselves. This approach provides enhanced security through centralized credential management, improved maintainability by standardizing GitHub integrations, time savings through cross-project reusability, and simplified operations with centrally managed updates for bug fixes.
Common use cases and scenarios
The GitHub API is very rich, so the possibilities for customization are almost endless. Some of the most common scenarios for GitHub calls in Azure Pipelines include:
Setting status checks on commits or pull requests: Report the success or failure of pipeline steps (like tests, builds, or security scans) back to GitHub, enabling rulesets utilization to enforce policies, and providing clear feedback to developers about the health of their code changes.
Adding comments to pull requests: Automatically post pipeline results, test coverage reports, performance metrics, or deployment information directly to pull request discussions, keeping all relevant information in one place for code reviewers.
Updating files in repositories: Automatically update documentation, configuration files, or version numbers as part of your CI/CD process, such as updating a CHANGELOG.md file or bumping version numbers in package files.
Managing GitHub Issues: Automatically create, update, or close issues based on pipeline results, such as creating bug reports when tests fail or closing issues when related features are successfully deployed.
Integrating with GitHub Advanced Security: Send code scanning results to GitHub’s code scanning, enabling centralized vulnerability management, security insights, and supporting DevSecOps practices across your development workflow.
Managing releases and assets: Automatically create GitHub releases and upload build artifacts, binaries, or documentation as release assets when deployments are successful, streamlining your release management process.
Tracking deployments with GitHub deployments: Integrate with GitHub’s deployment API to provide visibility into deployment history and status directly in the GitHub interface.
Triggering GitHub Actions workflows: Orchestrate hybrid CI/CD scenarios where Azure Pipelines handles certain build or deployment tasks and then triggers GitHub Actions workflows for additional processing or notifications.
Understanding GitHub API: REST vs. GraphQL
The GitHub API provides programmatic access to most of GitHub’s features and data, offering two distinct interfaces: REST and GraphQL. The REST API follows RESTful principles and provides straightforward HTTP endpoints for common operations like managing repositories, issues, pull requests, and workflows. It’s well documented, easy to get started with, and supports authentication via personal access tokens, GitHub Apps, or OAuth tokens.
GitHub’s GraphQL API offers a more flexible and efficient approach to data retrieval. Unlike REST, where you might need multiple requests to gather related data, GraphQL allows you to specify exactly what data you need in a single request, reducing over-fetching and under-fetching of data. This is particularly valuable when you need to retrieve complex, nested data structures or when you want to optimize network requests in your applications. You can see some examples in Exploring GitHub CLI: How to interact with GitHub’s GraphQL API endpoint.
Both APIs serve as the foundation for integrating GitHub’s functionality into external tools, automating workflows, and building custom solutions that extend GitHub’s capabilities.
How to choose the right authentication method
GitHub offers three primary authentication methods for accessing its APIs. Personal Access Tokens (PATs) are the simplest method, providing a token tied to a user account with specific permissions. OAuth tokens are designed for third-party applications that need to act on behalf of different users, implementing a standard authorization flow where users grant specific permissions to the application.
GitHub Apps provide the most robust and scalable solution, operating as their own entities with fine-grained permissions, installation-based access, and higher rate limits — making them ideal for organizations and production applications that need to interact with multiple repositories or organizations while maintaining tight security controls.
Authentication Type
Pros
Cons
Personal Access Tokens (PATs)
– Simple to create and use – Quick to get started – Good for personal automation – Can be scoped to multiple organizations – Configurable permissions per token – Admins can revoke organization access – Configurable expiration dates – Work with most GitHub API libraries – No additional infrastructure needed
– Tied to user account lifecycle – Limited to user’s permissions – Classic PATs have coarse-grained permissions – Require manual rotation – Browser-based management only – If compromised, expose all accessible organization(s)/repositories
OAuth Tokens
– Standard OAuth 2.0 flow – Organization admins control app access – Can act on behalf of multiple users – Excellent for web applications – User-approved permissions – Refresh token mechanism – Widely supported by frameworks – Good for user-facing applications
– Require storing refresh tokens securely – Need server infrastructure – More complex than PATs for simple automation – Still tied to user accounts – Require initial browser authorization – Token management complexity – Potential for scope creep – User revocation affects functionality
GitHub Apps
– Act as independent identity – Fine-grained, repository-level permissions – Installation-based access control – Tokens can be scoped down at runtime – Short-lived tokens (1 hour max) – Higher rate limits – Best security model available – No user account dependency – Audit trail for all actions – Can be installed across multiple orgs
– More complex initial setup – Require JWT implementation – May be overkill for simple scenarios – Require understanding of installation concept – Private key management responsibility – More moving parts to maintain – Not all APIs support Apps
PATs have two flavors: classic and fine-grained. Classic PATs provide repository-wide access with coarse permissions. Fine-grained PATs offer more granular control, since they are scoped to a single organization, allow specified permissions at the repository level, and limit access to specific repositories. Administrators can also require approval of fine-grained tokens before they can be used, making them a more secure choice for repository access management. However, they currently do not support all API calls and still have some limitations compared to classic PATs.
Because of their fine-grained permissions, security features, and higher rate limits, GitHub Apps are the ideal choice for machine-to-machine integration with Azure Pipelines. What’s more, the short-lived tokens and installation-based access model provide better security controls compared to PATs and OAuth tokens, making them particularly well-suited for automation in CI/CD scenarios.
Registering and installing a GitHub App
In order to use an application for authentication, register it as a GitHub App, and then install it on the accounts, organizations, or enterprises the application will interact with.
Make sure to select the appropriate permissions for the application. The permissions will determine what the application can do in the enterprise, organization, and repositories to which it has access.
Permissions may be modified at any time. Note that if the application is already installed, changes will require a new authorization from the owner administrators before they take effect.
Take care to understand the consequences of making the app public or private. It is very likely that you will want to make the app private, as it is only intended to be used by you or your organization. The semantics of public and private also vary depending on the GitHub Enterprise Cloud type (Enterprise with personal accounts, with managed users, or with data residency).
If a private key was generated, save it in a safe place. Private keys are used to authenticate against GitHub to generate an installation token. Note that a key can be revoked or up to 20 more may be generated if desired.
Install the GitHub App on the accounts or organizations the application will interact with.
When an app is installed, select which repositories the app will have access to. Options include all repositories (current and future) or you can select individual repositories.
Note: An unlimited number of GitHub Apps may be installed on each account, but only 100 GitHub Apps may be registered per enterprise, organization, or account.
GitHub App authentication flow
GitHub Apps use a two-step authentication process to access the GitHub API. First, the app authenticates itself using a JSON Web Token (JWT) signed with its private key. This JWT proves the app’s identity but doesn’t provide access to any GitHub resource. To call GitHub APIs, the app needs to obtain an installation token. Installation tokens are scoped (enterprise, organization, or account) access tokens that are generated using the app’s JWT authentication. These tokens are short-lived (valid for one hour) and can only access the resources on the scope they are installed on (enterprise, organization, or repository) and use at max the permissions granted during the app’s installation.
To obtain an installation token, there are two approaches: either use a known installation ID, or retrieve the ID by calling the installations API. Once the app has the installation ID, it requests a new token using that ID. The resulting installation token inherits the app’s permissions and repository access for that installation. It can optionally request the token with reduced permissions or limited to specific repositories — a useful security feature when you don’t need the app’s full access scope.
The resulting installation token can then be used to make GitHub API calls with the returned permissions.
Note: The application can also authenticate on a user’s behalf, but it’s not an ideal scenario for CI/CD pipelines where we want to use a service account and not a user account.
From a pipeline perspective, generating an installation token is all that’s needed to call GitHub APIs.
Pipeline authors have three main options to generate installation tokens in Azure Pipelines:
Use a command-line tool: Several tools are available that can generate installation tokens directly from a pipeline step. For example, gh-token is a popular open source tool that handles the entire token generation process.
Write custom scripts: Implement the token generation process using bash/curl or PowerShell scripts following the authentication steps described above. This grants full control over the process but requires more implementation effort.
Use Azure Pipeline tasks: While Azure Pipelines doesn’t provide built-in GitHub App authentication, you can either:
Create a custom task that implements the GitHub App authentication flow.
Next, we’ll explore creating a custom task using an Azure DevOps extension to provide an integration with GitHub App authentication and dynamically generated installation tokens.
Azure DevOps extension for GitHub App authentication
When creating an integration between Azure Pipelines and GitHub, security of the app private key should be top of mind. Possession of this key grants permissions to generate installation tokens and make API calls on behalf of the app, so it must be stored securely. Within Azure Pipelines, we have several options for storing sensitive data:
Azure Pipeline secrets store, which can be accessed via secret variables
Azure Pipelines service connections, which are project-level resources used to store authentication details for external services
Service connections in Azure Pipelines provide several key benefits for managing external service authentication, including:
Centralized access control where administrators can specify which pipelines can use the connection
Support for multiple authentication schemes
Ability to share connections across multiple pipelines within a project
Built-in security controls for managing who can view or modify connection details
Keep sensitive credentials hidden from pipeline authors while still allowing usage
Shared connections across multiple projects, reducing duplication and management overhead
For GitHub App authentication, service connections are particularly valuable because they:
Securely store the app’s private key
Allow administrators to configure and enforce connection behaviors
Provide better security compared to storing secrets directly in pipelines or variable groups
For those eager to explore the sample code, check out the repository. The key components and configuration are detailed below.
Creating a custom Azure DevOps extension
Azure DevOps extensions are packages that add new capabilities to Azure DevOps services. In our case, we need to create an extension that provides two key components:
Custom service connection type for securely storing GitHub App credentials (and other settings)
Custom task that uses those credentials to generate installation tokens
An extension consists of a manifest file that describes what the extension provides, along with the actual implementation code.
The development process involves creating the extension structure, defining the service connection schema, implementing the custom task logic in PowerShell (Windows only) or JavaScript/TypeScript for cross-platform compatibility, and packaging everything into a distributable format. Once created, the extension can be published privately for your organization or shared publicly through the Azure DevOps Marketplace, making it available for others who have similar GitHub integration needs.
We are not going to do a full walkthrough of the extension creation process, but we will demonstrate the most important steps. You can find all the information here:
To enable GitHub App authentication in Azure Pipelines, we need to create a custom service connection type since there isn’t a built-in one. This can be done by adding a custom endpoint contribution to our extension, which will define how the service connection stores and validates the GitHub App credentials, and provides a user-friendly UI for configuring the connection settings like App ID, private key, and other properties.
We need to add a contribution of type ms.vss-endpoint.service-endpoint-type to the extension contributions manifest. This contribution will define the service connection type and its properties, like the authentication scheme, the endpoint schema, and the input fields that will be displayed in the service connection configuration dialogue.
Something like this (see a snippet below, or explore the full contribution definition in reference implementation):
Once you install the extension, you can add/manage the service connection of type “GitHub App” and configure the app’s ID, private key, and other settings. The service connection will securely store the private key and can be used by custom tasks to generate installation tokens in a pipeline.
In addition to storing the private key, the custom service connection can also store other settings, such as the GitHub API URL and the app client ID. It can also be used to limit token permissions or scope the token to specific repositories. By optionally enforcing these settings at the service connection level, administrators can ensure consistency and security, rather than leaving configuration decisions to pipeline authors.
Adding a custom task
Now that we have a secure way to store the GitHub App credentials, we can create a custom task that will use the service connection to generate an installation token. The task will be a TypeScript application (cross platform) and use the Azure DevOps Extension SDK.
Declare the inputs and outputs on the task manifest (task.json)
Implement the code
Declare the task and its assets on the extension manifest (vss-extension.json)
I have created an extension sample that contains both the service connection as well as a custom task that generates a GitHub installation token for API calls. Since the extension is not published to the marketplace, you have to (privately) publish under your account, share it with your Azure DevOps enterprise or organization, and then install it on all organizations where you want to use the custom task.
Jump to the next section If you choose this path, as you are now ready to use the custom task in your pipeline.
Note: The sample includes both a GitHub Actions workflow and an Azure Pipelines YAML pipeline that builds and packages the extension as an Azure DevOps extension that can be published in the Azure DevOps marketplace.
Using the custom task in Azure Pipelines
The task supports receiving the private key, as a string, a file (to be combined with secure files), or preferably a service connection (see input parameters).
Assuming you have a service connection named my-github-app-service-connection, let’s see how can use task to create a comment in a pull request in the GitHub repository that triggers the pipeline using the GitHub CLI to call the GitHub API:
steps:
- task: create-github-app-token@1
displayName: create installation token
name: getToken
inputs:
githubAppConnection: my-github-app-service-connection
- bash: |
pr_number=$(System.PullRequest.PullRequestNumber)
repo=$(Build.Repository.Name)
echo "Creating comment in pull request #${pr_number} in repository ${repo}"
gh api -X POST "/repos/${repo}/issues/${pr_number}/comments" -f body="Posting a comment from Azure Pipelines"
displayName: Create comment in pull request
condition: eq(variables['Build.Reason'], 'PullRequest')
env:
GH_TOKEN: $(getToken.installationToken)
Running this pipeline will result in a comment being posted in the pull request:
Pretty simple, right? The task will create an installation token using the service connection and export it as a variable, which can be accessed as getToken.installationToken (with getToken being the identifier of the step). It can then be used to authenticate against GitHub, in this case using the GitHub CLI command, which will take care of the API call and authentication for us (we could have also used curl or any other HTTP client).
The task also exports other variables:
tokenExpiration: the expiration date of the generated token, in ISO 8601 format
installationId: the ID of the installation for which the token was generated
By leveraging GitHub Apps for authentication, organizations can establish secure, scalable Azure Pipelines integrations that provide fine-grained permissions, short-lived tokens, and better security controls compared to traditional PATs.
The custom Azure DevOps extension approach provides a seamless integration experience that abstracts away the complexities of GitHub App authentication. Through service connections and custom tasks, pipeline authors can easily generate installation tokens without worrying about JWT generation, installation ID management, or token lifecycle concerns.
The streamlined approach also enables development teams to implement rich GitHub integrations, including automated status checks, pull request comments, issue management, security scanning integration, and deployment tracking. The result? A more cohesive development workflow where Azure Pipelines and GitHub work together seamlessly to provide comprehensive visibility and automation throughout the software development lifecycle.
Whether you’re looking to enhance your existing CI/CD processes or build entirely new automated workflows, the combination of Azure Pipelines and GitHub API through GitHub Apps provides a robust foundation for modern DevOps practices. This will allow you to enrich your existing pipelines with GitHub capabilities as you move your code from Azure Repos to GitHub.
Tiago Pascoal a Staff DevOps Architect on the GitHub FastTrack team, before joining GitHub he was a DevOps Architect at Microsoft in the Azure DevOps team.
He spends his day helping people with their DevOps practices, with a focus on GitHub, DevOps and AI.
Comparing GitHub-hosted vs self-hosted runners for your CI/CD workflows? This deep dive explores important factors to consider when making this critical infrastructure decision for your development team.
Learn how GitHub Artifact Attestations can enhance your build security and help your organization achieve SLSA Level 3. This post breaks down the basics of SLSA, explains the importance of artifact attestations, and provides a step-by-step guide to securing your build process.