Provides reusable GitHub Actions for controlling access to Azure Static Web Apps (SWA) based on GitHub repository permissions.
When hosting documentation on SWA that is associated with a GitHub repository, you can achieve access control such as "only users with read permission to the repository can view the content." This Action automatically syncs GitHub repository permissions (admin/maintain/write/triage/read) to SWA custom roles, and notifies target users with invitation links via GitHub Discussions.
- Overview
- Features
- Prerequisites
- Getting Started
- Creating Azure Resources
- Configuration
- Troubleshooting
- Documentation
- License
When publishing documentation related to a GitHub repository on SWA, the following challenges exist:
- SWA authentication/authorization is not linked to GitHub repository permissions
- SWA role assignments must be manually updated when repository permissions change
- Managing invitation link expiration is cumbersome
This repository provides the following two reusable GitHub Actions:
| Action | Description |
|---|---|
| swa-github-role-sync | Syncs users with GitHub repository permissions to SWA custom roles and notifies them via Discussion with invitation links |
| swa-github-discussion-cleanup | Automatically deletes expired invitation Discussions |
- 1:1 mapping between GitHub permissions and SWA roles (5 levels)
admin→github-adminmaintain→github-maintainwrite→github-writetriage→github-triageread→github-read
- Configurable minimum permission level for sync targets via
minimum-permission(default:write) - Duplicate invitation suppression through diff detection
- Duplicate Discussion prevention: Skips creating new invite Discussions when an open Discussion containing both
swa-nameand@{login}already exists in the same category - Automatic creation of invitation Discussions for each user
- Sync result summary output to
GITHUB_STEP_SUMMARY
- Automatic deletion of expired Discussions based on creation date
- カテゴリー一致のみの削除対象判定
- Immediate deletion mode for manual execution
- On Windows, use WSL (Windows Subsystem for Linux). Setup scripts are written in Bash.
- Azure CLI (
az) and GitHub CLI (gh) must be installed and logged in. - Enable Discussions on the target repository and prepare a category for posting invitation notifications (e.g.,
Announcements). - Create a GitHub App (
Administration: read,Discussions: read & write, andMembers: readif needed). Note the App ID and private key. - The workflow must have
permissionsconfigured forcontents: read/discussions: write/id-token: write.
If Azure resources (Static Web App, managed identity, etc.) already exist, start from the Secrets configuration. If you need to create Azure resources, refer to the "Creating Azure Resources" section first.
Register the following Secrets in your repository or Organization:
| Secret | Description |
|---|---|
AZURE_CLIENT_ID |
Client ID for Azure OIDC authentication |
AZURE_TENANT_ID |
Azure tenant ID |
AZURE_SUBSCRIPTION_ID |
Azure subscription ID |
AZURE_STATIC_WEB_APPS_API_TOKEN |
SWA deployment token |
ROLE_SYNC_APP_ID |
GitHub App ID |
ROLE_SYNC_APP_PRIVATE_KEY |
GitHub App private key |
- Create a "New GitHub App" from GitHub's "Developer settings > GitHub Apps".
- Grant
Administration (Read-only)andDiscussions (Read & write)under Repository permissions. If using Organization member information, grantMembers (Read-only)under Organization permissions. - Install the App on the target Organization/repository, obtain the App ID and Private key (
.pem), and register them in Secrets.
Manually register Secrets for the GitHub App:
gh secret set ROLE_SYNC_APP_ID --body "123456"
gh secret set ROLE_SYNC_APP_PRIVATE_KEY < role-sync-app.private-key.pemFor Organization Secrets, add --org <ORG> and optionally limit visibility with --repos.
To enable role-based access control on SWA, configure routes in staticwebapp.config.json.
{
"routes": [
{
"route": "/*",
"allowedRoles": ["github-admin", "github-maintain", "github-write"]
}
],
"responseOverrides": {
"401": {
"redirect": "/.auth/login/github",
"statusCode": 302
}
}
}Example of separating admin-only areas from general user areas:
{
"routes": [
{
"route": "/admin/*",
"allowedRoles": ["github-admin"]
},
{
"route": "/internal/*",
"allowedRoles": ["github-admin", "github-maintain", "github-write"]
},
{
"route": "/*",
"allowedRoles": [
"github-admin",
"github-maintain",
"github-write",
"github-triage",
"github-read"
]
}
],
"responseOverrides": {
"401": {
"redirect": "/.auth/login/github",
"statusCode": 302
},
"403": {
"rewrite": "/403.html",
"statusCode": 403
}
}
}In this example:
/admin/*: Accessible only bygithub-adminrole/internal/*: Accessible bygithub-admin,github-maintain,github-writeroles/*: Accessible by all synced roles
Specify the roles to allow in allowedRoles according to your minimum-permission and role-for-* settings. For details, see the Azure Static Web Apps route configuration documentation.
Create .github/workflows/role-sync.yml. Change swa-name, swa-resource-group, and discussion-category-name to match your environment.
name: Sync SWA roles
on:
workflow_dispatch:
schedule:
- cron: '0 3 * * 1'
jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: read
discussions: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sync SWA role assignments
uses: nuitsjp/swa-github-role-sync@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
swa-name: my-swa-app
swa-resource-group: my-swa-rg
discussion-category-name: AnnouncementsSetting cleanup-mode to immediate enables immediate deletion during manual execution. For scheduled runs, maintaining expiration is safer.
name: Cleanup invite discussions
on:
schedule:
- cron: '0 4 * * 1'
workflow_dispatch:
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
discussions: write
steps:
- uses: nuitsjp/swa-github-discussion-cleanup@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
discussion-category-name: Announcements
expiration-hours: 168
cleanup-mode: ${{ github.event_name == 'workflow_dispatch' && 'immediate' || 'expiration' }}# Run role sync manually
gh workflow run role-sync.yml --ref main
# Check execution results
gh run watch --exit-status
# Run invitation Discussion cleanup manually
gh workflow run cleanup-invite-discussions.yml --ref main- The number of invitations and updates are displayed in
GITHUB_STEP_SUMMARY. Check via GitHub Web UI orgh run view --log. - Invitation Discussions are created in the specified category with invitation URLs in the body.
- The cleanup workflow outputs the number of deleted items.
If Azure resources are not yet created, follow these steps to create a resource group, Static Web App, managed identity, and configure OIDC login from GitHub Actions.
Run the following command to create all necessary resources at once:
curl -fsSL https://raw.githubusercontent.com/nuitsjp/swa-github-role-sync-ops/main/scripts/setup-azure-resources.sh | bash -s -- <owner> <repository>Example:
curl -fsSL https://raw.githubusercontent.com/nuitsjp/swa-github-role-sync-ops/main/scripts/setup-azure-resources.sh | bash -s -- nuitsjp swa-github-role-sync-opsOutput:
=== Azure Resource Setup for swa-github-role-sync-ops ===
Resource Group: rg-swa-github-role-sync-ops-prod
Static Web App: stapp-swa-github-role-sync-ops-prod
Managed Identity: id-swa-github-role-sync-ops-prod
GitHub Repo: nuitsjp/swa-github-role-sync-ops
[1/6] Creating Resource Group...
Created: rg-swa-github-role-sync-ops-prod
[2/6] Creating Static Web App...
Created: stapp-swa-github-role-sync-ops-prod
Hostname: white-pond-06cee3400.3.azurestaticapps.net
[3/6] Creating Managed Identity...
Created: id-swa-github-role-sync-ops-prod
Client ID: a58912f6-5e94-4cf7-a68f-84a8f8537781
[4/6] Creating Federated Credential...
Created: fc-github-actions-main
[5/6] Assigning RBAC role...
Assigned Contributor role to id-swa-github-role-sync-ops-prod
[6/6] Registering GitHub Secrets...
AZURE_CLIENT_ID: a58912f6-5e94-4cf7-a68f-84a8f8537781
AZURE_TENANT_ID: fe689afa-3572-4db9-8e8a-0f81d5a9d253
AZURE_SUBSCRIPTION_ID: fc7753ed-2e69-4202-bb66-86ff5798b8d5
=== Setup Complete ===
SWA URL: https://white-pond-06cee3400.3.azurestaticapps.net
The script automatically executes the following:
- Create resource group (
rg-<repository>-prod) - Create Static Web App (
stapp-<repository>-prod, Standard SKU) - Create managed identity (
id-<repository>-prod) - Create OIDC federated credential
- Assign Contributor role
- Register GitHub Secrets (
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_SUBSCRIPTION_ID)
Based on the Azure Cloud Adoption Framework's resource abbreviation guidance, the following naming conventions are used:
| Resource Type | Prefix | Example |
|---|---|---|
| Resource Group | rg |
rg-swa-github-role-sync-ops-prod |
| Static Web App | stapp |
stapp-swa-github-role-sync-ops-prod |
| Managed Identity | id |
id-swa-github-role-sync-ops-prod |
Use minimum-permission to specify the minimum permission level for sync targets:
minimum-permission |
Sync Targets |
|---|---|
read |
read, triage, write, maintain, admin |
triage |
triage, write, maintain, admin |
write |
write, maintain, admin (default) |
maintain |
maintain, admin |
admin |
admin only |
- uses: nuitsjp/swa-github-role-sync@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
swa-name: my-swa-app
swa-resource-group: my-swa-rg
discussion-category-name: Announcements
minimum-permission: read # Sync all users with read or aboveYou can configure SWA role names corresponding to each GitHub permission:
| Parameter | Default | Description |
|---|---|---|
role-for-admin |
github-admin |
SWA role for admin permission |
role-for-maintain |
github-maintain |
SWA role for maintain permission |
role-for-write |
github-write |
SWA role for write permission |
role-for-triage |
github-triage |
SWA role for triage permission |
role-for-read |
github-read |
SWA role for read permission |
- Sync based on another repository's permissions
Specify
owner/repointarget-repoand provide a PAT with access to the target repository ingithub-token. - Changing templates
テンプレートは
{login}、{role}、{inviteUrl}、{swaName}、{repo}、{date}などのプレースホルダーを利用可能である。Discussion掃除側はタイトルテンプレートを参照しないため設定不要である。 - Invitation link expiration
Changing
invitation-expiration-hours(default 168 hours) requires matching theexpiration-hoursin the cleanup workflow. - Using custom domain
Specifying a domain like
https://example.cominswa-domaingenerates invitation URLs with that domain.
Discussion category "..." not found: Category name is incorrect or Discussions are disabled. Check your settings.Resource not accessible by integration: Insufficientgithub-tokenpermissions. Check thepermissionsblock and token scopes.- Sync completes with 0 invitations: If already synced, it completes without changes. Note that differences in
role-prefixexclude items from diff detection.
| Document | Contents |
|---|---|
| docs/developer-guide.md | Development environment setup, testing, and release procedures |
| swa-github-role-sync | Detailed documentation for Role Sync Action |
| swa-github-discussion-cleanup | Detailed documentation for Discussion Cleanup Action |
MIT License - See the LICENSE file in each repository for details.