{"id":50565727,"url":"https://github.com/webstean/azfunc-powershell","last_synced_at":"2026-06-04T14:30:42.968Z","repository":{"id":337901358,"uuid":"1155746625","full_name":"webstean/azfunc-powershell","owner":"webstean","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-12T03:22:02.000Z","size":76,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-12T05:50:51.510Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PowerShell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webstean.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-11T21:23:15.000Z","updated_at":"2026-02-12T03:22:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/webstean/azfunc-powershell","commit_stats":null,"previous_names":["webstean/azfunc-powershell"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/webstean/azfunc-powershell","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webstean%2Fazfunc-powershell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webstean%2Fazfunc-powershell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webstean%2Fazfunc-powershell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webstean%2Fazfunc-powershell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webstean","download_url":"https://codeload.github.com/webstean/azfunc-powershell/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webstean%2Fazfunc-powershell/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33910136,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T14:30:42.846Z","updated_at":"2026-06-04T14:30:42.957Z","avatar_url":"https://github.com/webstean.png","language":"PowerShell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Azure Function - PowerShell with Entra ID Authentication\n\n[![Deploy Azure Function](https://github.com/webstean/azfunc-powershell/actions/workflows/deploy.yml/badge.svg)](https://github.com/webstean/azfunc-powershell/actions/workflows/deploy.yml)\n\nThis repository contains an Azure Function App built with PowerShell that provides a secure API endpoint for creating SharePoint sites. The function is protected with Microsoft Entra ID (formerly Azure AD) authentication and supports both application and delegated access patterns, including On-Behalf-Of (OBO) flows.\n\n## Features\n\n- ✅ **Health Endpoint**: Public /api/health endpoint for monitoring and service availability checks\n- ✅ **Entra ID Authentication**: Requires valid access tokens with proper scopes/roles\n- ✅ **recordh.create Scope/Role**: Custom permission for SharePoint site creation\n- ✅ **OBO Flow Support**: Enables delegated access for user-context operations\n- ✅ **PnP PowerShell Integration**: Full support for SharePoint operations via PnP.PowerShell module\n- ✅ **Extensible Architecture**: Easy to add more functions to the same Function App\n- ✅ **CI/CD with GitHub Actions**: Automated deployment using OIDC authentication (no stored credentials)\n\n## Project Structure\n\n```\nazfunc-powershell/\n├── host.json                 # Azure Functions runtime configuration\n├── profile.ps1              # PowerShell initialization script\n├── requirements.psd1        # PowerShell module dependencies (Az, PnP.PowerShell)\n├── local.settings.json      # Local development settings (not committed)\n├── health/                  # HTTP-triggered function for health checks\n│   ├── function.json        # Function binding configuration\n│   └── run.ps1              # Health check implementation\n└── recordh/                 # HTTP-triggered function for SharePoint site creation\n    ├── function.json        # Function binding configuration\n    └── run.ps1              # Function implementation\n├── .github/\n│   ├── workflows/\n│   │   └── deploy.yml         # GitHub Actions deployment workflow\n│   └── SECRETS.md             # Required secrets configuration guide\n├── DEPLOYMENT.md              # Comprehensive deployment guide\n```\n\n## Prerequisites\n\n- [Azure Functions Core Tools](https://docs.microsoft.com/azure/azure-functions/functions-run-local) v4.x\n- [PowerShell](https://docs.microsoft.com/powershell/scripting/install/installing-powershell) 7.2 or later\n- [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli) (for deployment)\n- An Azure subscription\n- Microsoft Entra ID (Azure AD) tenant\n\n## Entra ID App Registration\n\nTo use this function, you must register an application in Microsoft Entra ID:\n\n### 1. Register the Application\n\n```bash\naz login\naz ad app create --display-name \"RecordH-API\" --sign-in-audience AzureADMyOrg\n```\n\nNote the **Application (client) ID** and **Directory (tenant) ID** from the output.\n\n### 2. Define App Roles (for Application Permissions)\n\nAdd an application role for service-to-service authentication:\n\n```json\n{\n  \"allowedMemberTypes\": [\"Application\"],\n  \"description\": \"Allows creation of SharePoint sites via recordh API\",\n  \"displayName\": \"recordh.create\",\n  \"id\": \"\u003cgenerate-a-guid\u003e\",\n  \"isEnabled\": true,\n  \"value\": \"recordh.create\"\n}\n```\n\n### 3. Define API Scopes (for Delegated Permissions)\n\nExpose an API scope for user delegation:\n\n1. Go to **Azure Portal** → **Entra ID** → **App registrations** → Your app\n2. Navigate to **Expose an API**\n3. Add a scope:\n   - **Scope name**: `recordh.create`\n   - **Who can consent**: Admins and users\n   - **Admin consent display name**: Create SharePoint sites\n   - **Admin consent description**: Allows the application to create SharePoint sites on behalf of the user\n\n### 4. Create a Client Secret (Required for OBO Flow)\n\n```bash\naz ad app credential reset --id \u003capplication-id\u003e\n```\n\nSave the **client secret** securely - you'll need it for OBO flows.\n\n### 5. Grant API Permissions\n\nFor the function to work with SharePoint, grant these Microsoft Graph permissions:\n- `Sites.FullControl.All` (Application or Delegated)\n\nFor OBO scenarios, ensure your client applications are authorized to request tokens on behalf of users.\n\n## Configuration\n\n### Local Development\n\n1. Copy `local.settings.json` template and update with your values:\n\n```json\n{\n  \"IsEncrypted\": false,\n  \"Values\": {\n    \"AzureWebJobsStorage\": \"UseDevelopmentStorage=true\",\n    \"FUNCTIONS_WORKER_RUNTIME\": \"powershell\",\n    \"FUNCTIONS_WORKER_RUNTIME_VERSION\": \"7.2\",\n    \"ENTRA_CLIENT_ID\": \"\u003cyour-app-client-id\u003e\",\n    \"ENTRA_TENANT_ID\": \"\u003cyour-tenant-id\u003e\",\n    \"ENTRA_AUDIENCE\": \"api://\u003cyour-app-client-id\u003e\",\n    \"ENTRA_CLIENT_SECRET\": \"\u003cyour-client-secret\u003e\",\n    \"REQUIRED_SCOPE\": \"recordh.create\"\n  }\n}\n```\n\n### Azure Deployment\n\nWhen deploying to Azure, set these application settings:\n\n```bash\naz functionapp config appsettings set \\\n  --name \u003cfunction-app-name\u003e \\\n  --resource-group \u003cresource-group\u003e \\\n  --settings \\\n    ENTRA_CLIENT_ID=\"\u003cyour-app-client-id\u003e\" \\\n    ENTRA_TENANT_ID=\"\u003cyour-tenant-id\u003e\" \\\n    ENTRA_AUDIENCE=\"api://\u003cyour-app-client-id\u003e\" \\\n    ENTRA_CLIENT_SECRET=\"\u003cyour-client-secret\u003e\" \\\n    REQUIRED_SCOPE=\"recordh.create\"\n```\n\n**Security Note**: Use Azure Key Vault references for sensitive values in production:\n```\nENTRA_CLIENT_SECRET=\"@Microsoft.KeyVault(SecretUri=https://\u003cvault\u003e.vault.azure.net/secrets/\u003csecret\u003e)\"\n```\n\n## Local Development\n\n1. Install dependencies:\n\n```bash\n# Start Azure Functions Core Tools (will install PowerShell modules automatically)\nfunc start\n```\n\n2. Test the endpoint locally:\n\n```bash\ncurl -X POST http://localhost:7071/api/recordh \\\n  -H \"Authorization: Bearer \u003cyour-token\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"siteTitle\": \"My New Site\", \"siteAlias\": \"my-new-site\"}'\n```\n\n## API Reference\n\n### GET /api/health\n\nHealth check endpoint for monitoring service availability.\n\n**Authentication**: Not required (public endpoint)\n\n**Request Headers**: None required\n\n**Response (200 OK)**:\n```json\n{\n  \"status\": \"healthy\",\n  \"service\": \"azfunc-powershell\",\n  \"timestamp\": \"2024-02-11T22:00:00.000Z\",\n  \"version\": \"1.0.0\"\n}\n```\n\n**Use Cases**:\n- Azure Load Balancer health probes\n- Azure Application Gateway backend health checks\n- Kubernetes liveness/readiness probes\n- External monitoring services (Pingdom, StatusPage, etc.)\n- CI/CD deployment validation\n\n---\n\n### POST /api/recordh\n\nCreates a new SharePoint site.\n\n**HTTP Method**: POST only. Requests with other HTTP methods (GET, PUT, DELETE, etc.) will receive a 405 Method Not Allowed error with guidance.\n\n**Authentication**: Required (Bearer token with `recordh.create` scope/role)\n\n**Request Headers**:\n- `Authorization`: Bearer \u003caccess_token\u003e\n- `Content-Type`: application/json\n\n**Request Body**:\n```json\n{\n  \"siteTitle\": \"Project Alpha\",\n  \"siteAlias\": \"project-alpha\",\n  \"tenantUrl\": \"https://contoso.sharepoint.com\"\n}\n```\n\n**Response (200 OK)**:\n```json\n{\n  \"success\": true,\n  \"message\": \"SharePoint site creation initiated\",\n  \"siteTitle\": \"Project Alpha\",\n  \"accessType\": \"Delegated\",\n  \"requestedBy\": \"user@contoso.com\",\n  \"timestamp\": \"2024-02-11T21:23:00.000Z\"\n}\n```\n\n**Error Responses**:\n- `405 Method Not Allowed`: Non-POST method used (only POST is supported)\n  ```json\n  {\n    \"error\": \"Method Not Allowed\",\n    \"message\": \"This endpoint only supports POST requests. Please use POST method to access this API.\",\n    \"allowedMethods\": [\"POST\"],\n    \"receivedMethod\": \"GET\"\n  }\n  ```\n- `401 Unauthorized`: Missing or invalid token, missing required scope/role\n- `400 Bad Request`: Invalid request body or missing required parameters\n- `500 Internal Server Error`: Site creation failed\n\n## Authentication Types\n\n### Application Authentication (Service-to-Service)\n\nUsed when a service needs to create sites without user context:\n\n```bash\n# Get access token using client credentials flow\ncurl -X POST https://login.microsoftonline.com/\u003ctenant-id\u003e/oauth2/v2.0/token \\\n  -d \"client_id=\u003cclient-id\u003e\" \\\n  -d \"client_secret=\u003cclient-secret\u003e\" \\\n  -d \"scope=api://\u003cclient-id\u003e/.default\" \\\n  -d \"grant_type=client_credentials\"\n```\n\n### Delegated Authentication (On-Behalf-Of User)\n\nUsed when acting on behalf of a signed-in user:\n\n1. User authenticates to your app\n2. Your app gets a token with `recordh.create` scope\n3. Function validates the user's token\n4. Function uses OBO to get SharePoint token with user's permissions\n\n## OBO Flow Implementation\n\nThe function includes built-in support for On-Behalf-Of flows:\n\n```powershell\n# Automatically handles OBO for delegated access\n$oboToken = Get-OBOToken -UserToken $authHeader -TargetResource \"https://graph.microsoft.com\"\n```\n\nThis allows the function to:\n- Preserve user context when accessing SharePoint\n- Respect user's actual permissions\n- Maintain audit trails with correct user attribution\n\n## Deployment\n\n### Automated Deployment with GitHub Actions (Recommended)\n\nThis repository includes a GitHub Actions workflow that uses OIDC (OpenID Connect) authentication to securely deploy to Azure without storing credentials.\n\n#### Prerequisites\n\n1. **Azure Resources**: Create the required Azure resources:\n\n```bash\n# Create resource group\naz group create --name \u003cresource-group\u003e --location \u003cregion\u003e\n\n# Create storage account\naz storage account create \\\n  --name \u003cstorage-account\u003e \\\n  --resource-group \u003cresource-group\u003e \\\n  --location \u003cregion\u003e \\\n  --sku Standard_LRS\n\n# Create Function App\naz functionapp create \\\n  --name \u003cfunction-app-name\u003e \\\n  --resource-group \u003cresource-group\u003e \\\n  --consumption-plan-location \u003cregion\u003e \\\n  --runtime powershell \\\n  --runtime-version 7.2 \\\n  --functions-version 4 \\\n  --storage-account \u003cstorage-account\u003e\n```\n\n2. **Configure OIDC for GitHub Actions**: Set up federated identity credentials to allow GitHub Actions to authenticate to Azure without secrets:\n\n```bash\n# Get your GitHub repository information\nGITHUB_ORG=\"\u003cyour-github-org\u003e\"\nGITHUB_REPO=\"\u003cyour-github-repo\u003e\"\n\n# Create an Azure AD application for GitHub Actions\nAPP_ID=$(az ad app create \\\n  --display-name \"GitHub-Actions-${GITHUB_REPO}\" \\\n  --query appId -o tsv)\n\n# Create a service principal\naz ad sp create --id $APP_ID\n\n# Get the service principal object ID\nSP_OBJECT_ID=$(az ad sp show --id $APP_ID --query id -o tsv)\n\n# Assign Contributor role to the service principal for the resource group\naz role assignment create \\\n  --assignee $APP_ID \\\n  --role Contributor \\\n  --scope /subscriptions/\u003csubscription-id\u003e/resourceGroups/\u003cresource-group\u003e\n\n# Create federated identity credential for the main branch\naz ad app federated-credential create \\\n  --id $APP_ID \\\n  --parameters '{\n    \"name\": \"github-federated-credential\",\n    \"issuer\": \"https://token.actions.githubusercontent.com\",\n    \"subject\": \"repo:'\"${GITHUB_ORG}/${GITHUB_REPO}\"':ref:refs/heads/main\",\n    \"audiences\": [\"api://AzureADTokenExchange\"]\n  }'\n\n# (Optional) Create federated credential for pull requests\naz ad app federated-credential create \\\n  --id $APP_ID \\\n  --parameters '{\n    \"name\": \"github-pr-credential\",\n    \"issuer\": \"https://token.actions.githubusercontent.com\",\n    \"subject\": \"repo:'\"${GITHUB_ORG}/${GITHUB_REPO}\"':pull_request\",\n    \"audiences\": [\"api://AzureADTokenExchange\"]\n  }'\n```\n\n3. **Configure GitHub Secrets**: Add the following secrets to your GitHub repository (Settings → Secrets and variables → Actions):\n\n```\nAZURE_CLIENT_ID=\u003capplication-client-id\u003e\nAZURE_TENANT_ID=\u003cyour-tenant-id\u003e\nAZURE_SUBSCRIPTION_ID=\u003cyour-subscription-id\u003e\nAZURE_FUNCTIONAPP_NAME=\u003cfunction-app-name\u003e\nAZURE_RESOURCE_GROUP=\u003cresource-group\u003e\n\n# Entra ID configuration for the function\nENTRA_CLIENT_ID=\u003cyour-entra-app-client-id\u003e\nENTRA_TENANT_ID=\u003cyour-tenant-id\u003e\nENTRA_AUDIENCE=api://\u003cyour-entra-app-client-id\u003e\nENTRA_CLIENT_SECRET=\u003cyour-entra-client-secret\u003e\nREQUIRED_SCOPE=recordh.create\n```\n\n4. **Create GitHub Environment** (Optional but recommended):\n   - Go to repository Settings → Environments\n   - Create a `production` environment\n   - Add protection rules (require reviewers, branch restrictions, etc.)\n   - Add the secrets to this environment\n\n#### Triggering Deployments\n\n**Automatic deployment on push to main**:\n```bash\ngit push origin main\n```\n\n**Manual deployment via GitHub UI**:\n1. Go to Actions tab in your GitHub repository\n2. Select \"Deploy Azure Function\" workflow\n3. Click \"Run workflow\"\n4. Choose the environment (production/staging)\n5. Click \"Run workflow\"\n\n**Manual deployment via GitHub CLI**:\n```bash\ngh workflow run deploy.yml --ref main -f environment=production\n```\n\n#### Monitoring Deployments\n\nView deployment status:\n- Navigate to the **Actions** tab in your GitHub repository\n- Click on the workflow run to see detailed logs\n- Each step shows execution status and output\n\n### Manual Deployment with Azure Functions Core Tools\n\nFor local testing or one-time deployments:\n\n1. Deploy the function code:\n\n```bash\nfunc azure functionapp publish \u003cfunction-app-name\u003e\n```\n\n2. Configure application settings:\n\n```bash\naz functionapp config appsettings set \\\n  --name \u003cfunction-app-name\u003e \\\n  --resource-group \u003cresource-group\u003e \\\n  --settings \\\n    ENTRA_CLIENT_ID=\"\u003cyour-app-client-id\u003e\" \\\n    ENTRA_TENANT_ID=\"\u003cyour-tenant-id\u003e\" \\\n    ENTRA_AUDIENCE=\"api://\u003cyour-app-client-id\u003e\" \\\n    ENTRA_CLIENT_SECRET=\"\u003cyour-client-secret\u003e\" \\\n    REQUIRED_SCOPE=\"recordh.create\"\n```\n\n## Adding More Functions\n\nThis Function App is designed to be extensible. To add more functions:\n\n1. Create a new folder (e.g., `myfunction/`)\n2. Add `function.json` with binding configuration\n3. Add `run.ps1` with function logic\n4. Update authentication logic as needed\n\nExample:\n```bash\nmkdir myfunction\n# Create function.json and run.ps1\n```\n\n## Security Considerations\n\n- ✅ All endpoints require authentication (authLevel: anonymous + manual token validation)\n- ✅ Tokens are validated for audience, tenant, expiration, and required permissions\n- ✅ Supports both application roles and delegated scopes\n- ✅ OBO flow preserves user context for audit and permissions\n- ✅ Use Azure Key Vault for storing secrets in production\n- ✅ Enable Application Insights for monitoring and logging\n\n### Important Security Notes\n\n**JWT Signature Verification**: The current implementation validates JWT token claims (audience, tenant, expiration, scopes/roles) but does not perform cryptographic signature verification. This design is appropriate when:\n- The function is deployed behind Azure API Management or Azure App Service Authentication/EasyAuth\n- The function is used in internal/private scenarios where the token source is trusted\n\nFor external-facing APIs or additional security hardening, consider:\n- Enabling Azure App Service Authentication (EasyAuth) to handle full token validation\n- Implementing full JWT signature verification using Azure AD's JWKS endpoint\n- Using a PowerShell JWT library that validates signatures\n\n**Production Deployment Best Practices**:\n- Always use HTTPS endpoints (enforced by Azure Functions)\n- Store secrets in Azure Key Vault with Key Vault references\n- Use Managed Identity instead of client secrets where possible\n- Enable Azure Monitor and Application Insights for security monitoring\n- Implement rate limiting to prevent abuse\n- Regularly update PowerShell modules for security patches\n\n**GitHub Actions Security**:\n- ✅ Uses OIDC authentication (no long-lived credentials stored in GitHub)\n- ✅ Federated identity credentials are scoped to specific repository and branches\n- ✅ Secrets are stored in GitHub Secrets (encrypted at rest)\n- ✅ Use GitHub Environments for additional protection (required reviewers, branch restrictions)\n- ✅ Workflow has minimal permissions (`id-token: write`, `contents: read`)\n- ⚠️ Regularly rotate Azure service principal credentials\n- ⚠️ Review federated credential subjects to prevent unauthorized access\n\n## Troubleshooting\n\n### Token Validation Failures\n\n- Verify `ENTRA_AUDIENCE` matches your app's identifier URI\n- Ensure the token includes the required scope or role\n- Check token expiration time\n\n### Module Loading Issues\n\n- Ensure `requirements.psd1` is properly formatted\n- Check that managed dependencies are enabled in `host.json`\n- Review function app logs for module installation errors\n\n### OBO Flow Failures\n\n- Verify `ENTRA_CLIENT_SECRET` is configured\n- Ensure the client app is authorized for OBO\n- Check that required API permissions are granted\n\n### GitHub Actions Deployment Failures\n\n**OIDC Authentication Issues**:\n- Verify federated identity credential subject matches your repository\n- Check that `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_SUBSCRIPTION_ID` secrets are set correctly\n- Ensure the service principal has Contributor role on the resource group\n- Verify the federated credential issuer is `https://token.actions.githubusercontent.com`\n\n**Deployment Package Issues**:\n- Ensure all required files (host.json, profile.ps1, requirements.psd1, function folders) are included\n- Check GitHub Actions logs for file copy or zip errors\n- Verify the deployment package size is within Azure limits\n\n**Function App Configuration Issues**:\n- Confirm all required secrets are set in GitHub (ENTRA_CLIENT_ID, ENTRA_TENANT_ID, etc.)\n- Verify the Function App name and resource group are correct\n- Check that the Function App is configured for PowerShell 7.2 runtime\n\n## Resources\n\n- [Azure Functions PowerShell Developer Guide](https://docs.microsoft.com/azure/azure-functions/functions-reference-powershell)\n- [PnP PowerShell Documentation](https://pnp.github.io/powershell/)\n- [Microsoft Entra ID Authentication](https://docs.microsoft.com/azure/active-directory/develop/)\n- [On-Behalf-Of Flow](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow)\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebstean%2Fazfunc-powershell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebstean%2Fazfunc-powershell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebstean%2Fazfunc-powershell/lists"}