Secure Authentication from GitHub Actions to Microsoft Graph using OIDC

Passwordless authentication is a modern security best practice that eliminates the need to store and manage secrets in your CI/CD pipelines. In this post, I'll walk through how to set up secure, token-based authentication from GitHub Actions to Microsoft Graph API using OpenID Connect (OIDC).

Why OIDC for GitHub Actions?

Traditional authentication methods typically require storing API keys or service principal credentials in GitHub Secrets. While this works, it introduces security risks and maintenance overhead. With OIDC, GitHub Actions can request short-lived tokens on-demand, significantly enhancing your security posture.

Prerequisites

  • An Azure subscription
  • Administrator access to Microsoft Entra ID (formerly Azure AD)
  • A GitHub repository with Actions enabled


Step 1: Configure Microsoft Entra ID App Registration

First, you need to create an App Registration in Microsoft Entra ID:

  1. Go to the Azure Portal and navigate to Microsoft Entra ID > App registrations
  2. Click New registration
  3. Enter a name for your application (e.g., "GitHub Actions Graph Access")
  4. Keep the default settings for supported account types and redirect URI
  5. Click Register

After registration, make note of:

  • Application (client) ID
  • Directory (tenant) ID


Step 2: Configure API Permissions

Now, configure the permissions your app needs:

  1. In your App Registration, go to API permissions
  2. Click Add a permission > Microsoft Graph > Application permissions
  3. Select the permissions you need (e.g., User.Read.All for user management)
  4. Click Add permissions
  5. Click Grant admin consent to approve these permissions


Step 3: Set Up Federated Credentials

This is where the magic happens - connecting GitHub's identity system to Microsoft Entra ID:

  1. Navigate to your App Registration
  2. Go to Certificates & secrets > Federated credentials
  3. Click Add credential
  4. For Federated credential scenario, select GitHub Actions deploying Azure resources
  5. Configure the credential with:

Organization: Your GitHub organization/username

Repository: Your repository name

Entity type: Choose either "Environment", "Branch", or "Pull request" depending on your workflow

GitHub name: If you selected "Environment", enter your environment name (e.g., "production")

Name: A descriptive name for this credential

The system will generate a Subject identifier for you, which will look something like: repo:organization/repository:environment:environment-name

6. Important: Make sure the Audience field is set to api://AzureADTokenExchange

7. Click Add


Step 4: Create the GitHub Workflow

Now, let's create a GitHub workflow that uses OIDC to authenticate to Microsoft Graph:

name: Connect to Entra using OIDC

on:
  workflow_dispatch:

permissions:
  id-token: write # Required for OIDC
  contents: read

jobs:
  connect-entra:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Install Microsoft Entra PowerShell module
        shell: pwsh
        run: |
          Install-Module -Name Microsoft.Entra -Repository PSGallery -Scope CurrentUser -Force -AllowClobber

      - name: Get OIDC token and authenticate to Microsoft Graph
        shell: pwsh
        env:
          TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
        run: |
          try {
            # Request an OIDC token with the specific audience
            $oidcRequestUrl = "$env:ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange"
            Write-Host "Requesting token from: $oidcRequestUrl"
            
            $oidcResponse = Invoke-RestMethod -Uri $oidcRequestUrl -Headers @{Authorization = "Bearer $env:ACTIONS_ID_TOKEN_REQUEST_TOKEN"}
            $oidcToken = $oidcResponse.value
            
            # Exchange OIDC token for Graph access token
            $tokenEndpoint = "https://login.microsoftonline.com/$env:TENANT_ID/oauth2/v2.0/token"
            $tokenParams = @{
              client_id = $env:CLIENT_ID
              client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
              client_assertion = $oidcToken
              scope = "https://graph.microsoft.com/.default"
              grant_type = "client_credentials"
            }
            
            Write-Host "Requesting access token from: $tokenEndpoint"
            $tokenResponse = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $tokenParams -ContentType "application/x-www-form-urlencoded"
            Write-Host "Successfully obtained access token"
            
            # Convert access token to SecureString
            $secureAccessToken = ConvertTo-SecureString $tokenResponse.access_token -AsPlainText -Force
            
            # Connect to Microsoft Graph using the token as SecureString
            Connect-Entra -AccessToken $secureAccessToken
            
            # Test the connection with a query
            Get-EntraGroup -Top 5
          }
          catch {
            Write-Error "Authentication failed: $_"
            Write-Host "Error details: $($_.Exception | Format-List -Force | Out-String)"
            exit 1
          }        

Step 5: Set Up Repository Secrets

Add the following secrets to your GitHub repository:

  • AZURE_TENANT_ID: Your Directory (tenant) ID
  • AZURE_CLIENT_ID: Your Application (client) ID


How It Works

Let's break down what's happening in this workflow:

  1. Token Request: GitHub Actions generates an OIDC token with the audience api://AzureADTokenExchange
  2. Token Exchange: We exchange this OIDC token for a Microsoft Graph access token
  3. SecureString Conversion: We convert the access token to a SecureString for use with PowerShell cmdlets
  4. Graph Connection: We connect to Microsoft Graph and perform operations


Common Issues and Troubleshooting

  1. Invalid Audience: If you see an error like AADSTS700212: No matching federated identity record found, ensure your federated credential is configured with the correct audience (api://AzureADTokenExchange) and that your workflow is requesting a token with this audience.
  2. Permission Issues: Make sure you've granted admin consent for all required permissions.
  3. Secure String Conversion: If using PowerShell cmdlets, remember that you need to convert the access token to a SecureString using ConvertTo-SecureString.


Conclusion

Using OIDC with GitHub Actions and Microsoft Graph provides a secure, modern approach to service-to-service authentication without managing credentials. This passwordless approach reduces your attack surface and simplifies credential management.

Once set up, you can use this authentication method to automate various Microsoft Graph operations - from user management to reporting and beyond - all without storing any sensitive credentials in your GitHub repository.

Happy secure automating!


To view or add a comment, sign in

Others also viewed

Explore content categories