https://github.com/webstean/azfunc-powershell
https://github.com/webstean/azfunc-powershell
Last synced: 16 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/webstean/azfunc-powershell
- Owner: webstean
- License: mit
- Created: 2026-02-11T21:23:15.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-02-12T03:22:02.000Z (4 months ago)
- Last Synced: 2026-02-12T05:50:51.510Z (4 months ago)
- Language: PowerShell
- Size: 74.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Azure Function - PowerShell with Entra ID Authentication
[](https://github.com/webstean/azfunc-powershell/actions/workflows/deploy.yml)
This 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.
## Features
- ✅ **Health Endpoint**: Public /api/health endpoint for monitoring and service availability checks
- ✅ **Entra ID Authentication**: Requires valid access tokens with proper scopes/roles
- ✅ **recordh.create Scope/Role**: Custom permission for SharePoint site creation
- ✅ **OBO Flow Support**: Enables delegated access for user-context operations
- ✅ **PnP PowerShell Integration**: Full support for SharePoint operations via PnP.PowerShell module
- ✅ **Extensible Architecture**: Easy to add more functions to the same Function App
- ✅ **CI/CD with GitHub Actions**: Automated deployment using OIDC authentication (no stored credentials)
## Project Structure
```
azfunc-powershell/
├── host.json # Azure Functions runtime configuration
├── profile.ps1 # PowerShell initialization script
├── requirements.psd1 # PowerShell module dependencies (Az, PnP.PowerShell)
├── local.settings.json # Local development settings (not committed)
├── health/ # HTTP-triggered function for health checks
│ ├── function.json # Function binding configuration
│ └── run.ps1 # Health check implementation
└── recordh/ # HTTP-triggered function for SharePoint site creation
├── function.json # Function binding configuration
└── run.ps1 # Function implementation
├── .github/
│ ├── workflows/
│ │ └── deploy.yml # GitHub Actions deployment workflow
│ └── SECRETS.md # Required secrets configuration guide
├── DEPLOYMENT.md # Comprehensive deployment guide
```
## Prerequisites
- [Azure Functions Core Tools](https://docs.microsoft.com/azure/azure-functions/functions-run-local) v4.x
- [PowerShell](https://docs.microsoft.com/powershell/scripting/install/installing-powershell) 7.2 or later
- [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli) (for deployment)
- An Azure subscription
- Microsoft Entra ID (Azure AD) tenant
## Entra ID App Registration
To use this function, you must register an application in Microsoft Entra ID:
### 1. Register the Application
```bash
az login
az ad app create --display-name "RecordH-API" --sign-in-audience AzureADMyOrg
```
Note the **Application (client) ID** and **Directory (tenant) ID** from the output.
### 2. Define App Roles (for Application Permissions)
Add an application role for service-to-service authentication:
```json
{
"allowedMemberTypes": ["Application"],
"description": "Allows creation of SharePoint sites via recordh API",
"displayName": "recordh.create",
"id": "",
"isEnabled": true,
"value": "recordh.create"
}
```
### 3. Define API Scopes (for Delegated Permissions)
Expose an API scope for user delegation:
1. Go to **Azure Portal** → **Entra ID** → **App registrations** → Your app
2. Navigate to **Expose an API**
3. Add a scope:
- **Scope name**: `recordh.create`
- **Who can consent**: Admins and users
- **Admin consent display name**: Create SharePoint sites
- **Admin consent description**: Allows the application to create SharePoint sites on behalf of the user
### 4. Create a Client Secret (Required for OBO Flow)
```bash
az ad app credential reset --id
```
Save the **client secret** securely - you'll need it for OBO flows.
### 5. Grant API Permissions
For the function to work with SharePoint, grant these Microsoft Graph permissions:
- `Sites.FullControl.All` (Application or Delegated)
For OBO scenarios, ensure your client applications are authorized to request tokens on behalf of users.
## Configuration
### Local Development
1. Copy `local.settings.json` template and update with your values:
```json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "powershell",
"FUNCTIONS_WORKER_RUNTIME_VERSION": "7.2",
"ENTRA_CLIENT_ID": "",
"ENTRA_TENANT_ID": "",
"ENTRA_AUDIENCE": "api://",
"ENTRA_CLIENT_SECRET": "",
"REQUIRED_SCOPE": "recordh.create"
}
}
```
### Azure Deployment
When deploying to Azure, set these application settings:
```bash
az functionapp config appsettings set \
--name \
--resource-group \
--settings \
ENTRA_CLIENT_ID="" \
ENTRA_TENANT_ID="" \
ENTRA_AUDIENCE="api://" \
ENTRA_CLIENT_SECRET="" \
REQUIRED_SCOPE="recordh.create"
```
**Security Note**: Use Azure Key Vault references for sensitive values in production:
```
ENTRA_CLIENT_SECRET="@Microsoft.KeyVault(SecretUri=https://.vault.azure.net/secrets/)"
```
## Local Development
1. Install dependencies:
```bash
# Start Azure Functions Core Tools (will install PowerShell modules automatically)
func start
```
2. Test the endpoint locally:
```bash
curl -X POST http://localhost:7071/api/recordh \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"siteTitle": "My New Site", "siteAlias": "my-new-site"}'
```
## API Reference
### GET /api/health
Health check endpoint for monitoring service availability.
**Authentication**: Not required (public endpoint)
**Request Headers**: None required
**Response (200 OK)**:
```json
{
"status": "healthy",
"service": "azfunc-powershell",
"timestamp": "2024-02-11T22:00:00.000Z",
"version": "1.0.0"
}
```
**Use Cases**:
- Azure Load Balancer health probes
- Azure Application Gateway backend health checks
- Kubernetes liveness/readiness probes
- External monitoring services (Pingdom, StatusPage, etc.)
- CI/CD deployment validation
---
### POST /api/recordh
Creates a new SharePoint site.
**HTTP Method**: POST only. Requests with other HTTP methods (GET, PUT, DELETE, etc.) will receive a 405 Method Not Allowed error with guidance.
**Authentication**: Required (Bearer token with `recordh.create` scope/role)
**Request Headers**:
- `Authorization`: Bearer
- `Content-Type`: application/json
**Request Body**:
```json
{
"siteTitle": "Project Alpha",
"siteAlias": "project-alpha",
"tenantUrl": "https://contoso.sharepoint.com"
}
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "SharePoint site creation initiated",
"siteTitle": "Project Alpha",
"accessType": "Delegated",
"requestedBy": "user@contoso.com",
"timestamp": "2024-02-11T21:23:00.000Z"
}
```
**Error Responses**:
- `405 Method Not Allowed`: Non-POST method used (only POST is supported)
```json
{
"error": "Method Not Allowed",
"message": "This endpoint only supports POST requests. Please use POST method to access this API.",
"allowedMethods": ["POST"],
"receivedMethod": "GET"
}
```
- `401 Unauthorized`: Missing or invalid token, missing required scope/role
- `400 Bad Request`: Invalid request body or missing required parameters
- `500 Internal Server Error`: Site creation failed
## Authentication Types
### Application Authentication (Service-to-Service)
Used when a service needs to create sites without user context:
```bash
# Get access token using client credentials flow
curl -X POST https://login.microsoftonline.com//oauth2/v2.0/token \
-d "client_id=" \
-d "client_secret=" \
-d "scope=api:///.default" \
-d "grant_type=client_credentials"
```
### Delegated Authentication (On-Behalf-Of User)
Used when acting on behalf of a signed-in user:
1. User authenticates to your app
2. Your app gets a token with `recordh.create` scope
3. Function validates the user's token
4. Function uses OBO to get SharePoint token with user's permissions
## OBO Flow Implementation
The function includes built-in support for On-Behalf-Of flows:
```powershell
# Automatically handles OBO for delegated access
$oboToken = Get-OBOToken -UserToken $authHeader -TargetResource "https://graph.microsoft.com"
```
This allows the function to:
- Preserve user context when accessing SharePoint
- Respect user's actual permissions
- Maintain audit trails with correct user attribution
## Deployment
### Automated Deployment with GitHub Actions (Recommended)
This repository includes a GitHub Actions workflow that uses OIDC (OpenID Connect) authentication to securely deploy to Azure without storing credentials.
#### Prerequisites
1. **Azure Resources**: Create the required Azure resources:
```bash
# Create resource group
az group create --name --location
# Create storage account
az storage account create \
--name \
--resource-group \
--location \
--sku Standard_LRS
# Create Function App
az functionapp create \
--name \
--resource-group \
--consumption-plan-location \
--runtime powershell \
--runtime-version 7.2 \
--functions-version 4 \
--storage-account
```
2. **Configure OIDC for GitHub Actions**: Set up federated identity credentials to allow GitHub Actions to authenticate to Azure without secrets:
```bash
# Get your GitHub repository information
GITHUB_ORG=""
GITHUB_REPO=""
# Create an Azure AD application for GitHub Actions
APP_ID=$(az ad app create \
--display-name "GitHub-Actions-${GITHUB_REPO}" \
--query appId -o tsv)
# Create a service principal
az ad sp create --id $APP_ID
# Get the service principal object ID
SP_OBJECT_ID=$(az ad sp show --id $APP_ID --query id -o tsv)
# Assign Contributor role to the service principal for the resource group
az role assignment create \
--assignee $APP_ID \
--role Contributor \
--scope /subscriptions//resourceGroups/
# Create federated identity credential for the main branch
az ad app federated-credential create \
--id $APP_ID \
--parameters '{
"name": "github-federated-credential",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:'"${GITHUB_ORG}/${GITHUB_REPO}"':ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
# (Optional) Create federated credential for pull requests
az ad app federated-credential create \
--id $APP_ID \
--parameters '{
"name": "github-pr-credential",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:'"${GITHUB_ORG}/${GITHUB_REPO}"':pull_request",
"audiences": ["api://AzureADTokenExchange"]
}'
```
3. **Configure GitHub Secrets**: Add the following secrets to your GitHub repository (Settings → Secrets and variables → Actions):
```
AZURE_CLIENT_ID=
AZURE_TENANT_ID=
AZURE_SUBSCRIPTION_ID=
AZURE_FUNCTIONAPP_NAME=
AZURE_RESOURCE_GROUP=
# Entra ID configuration for the function
ENTRA_CLIENT_ID=
ENTRA_TENANT_ID=
ENTRA_AUDIENCE=api://
ENTRA_CLIENT_SECRET=
REQUIRED_SCOPE=recordh.create
```
4. **Create GitHub Environment** (Optional but recommended):
- Go to repository Settings → Environments
- Create a `production` environment
- Add protection rules (require reviewers, branch restrictions, etc.)
- Add the secrets to this environment
#### Triggering Deployments
**Automatic deployment on push to main**:
```bash
git push origin main
```
**Manual deployment via GitHub UI**:
1. Go to Actions tab in your GitHub repository
2. Select "Deploy Azure Function" workflow
3. Click "Run workflow"
4. Choose the environment (production/staging)
5. Click "Run workflow"
**Manual deployment via GitHub CLI**:
```bash
gh workflow run deploy.yml --ref main -f environment=production
```
#### Monitoring Deployments
View deployment status:
- Navigate to the **Actions** tab in your GitHub repository
- Click on the workflow run to see detailed logs
- Each step shows execution status and output
### Manual Deployment with Azure Functions Core Tools
For local testing or one-time deployments:
1. Deploy the function code:
```bash
func azure functionapp publish
```
2. Configure application settings:
```bash
az functionapp config appsettings set \
--name \
--resource-group \
--settings \
ENTRA_CLIENT_ID="" \
ENTRA_TENANT_ID="" \
ENTRA_AUDIENCE="api://" \
ENTRA_CLIENT_SECRET="" \
REQUIRED_SCOPE="recordh.create"
```
## Adding More Functions
This Function App is designed to be extensible. To add more functions:
1. Create a new folder (e.g., `myfunction/`)
2. Add `function.json` with binding configuration
3. Add `run.ps1` with function logic
4. Update authentication logic as needed
Example:
```bash
mkdir myfunction
# Create function.json and run.ps1
```
## Security Considerations
- ✅ All endpoints require authentication (authLevel: anonymous + manual token validation)
- ✅ Tokens are validated for audience, tenant, expiration, and required permissions
- ✅ Supports both application roles and delegated scopes
- ✅ OBO flow preserves user context for audit and permissions
- ✅ Use Azure Key Vault for storing secrets in production
- ✅ Enable Application Insights for monitoring and logging
### Important Security Notes
**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:
- The function is deployed behind Azure API Management or Azure App Service Authentication/EasyAuth
- The function is used in internal/private scenarios where the token source is trusted
For external-facing APIs or additional security hardening, consider:
- Enabling Azure App Service Authentication (EasyAuth) to handle full token validation
- Implementing full JWT signature verification using Azure AD's JWKS endpoint
- Using a PowerShell JWT library that validates signatures
**Production Deployment Best Practices**:
- Always use HTTPS endpoints (enforced by Azure Functions)
- Store secrets in Azure Key Vault with Key Vault references
- Use Managed Identity instead of client secrets where possible
- Enable Azure Monitor and Application Insights for security monitoring
- Implement rate limiting to prevent abuse
- Regularly update PowerShell modules for security patches
**GitHub Actions Security**:
- ✅ Uses OIDC authentication (no long-lived credentials stored in GitHub)
- ✅ Federated identity credentials are scoped to specific repository and branches
- ✅ Secrets are stored in GitHub Secrets (encrypted at rest)
- ✅ Use GitHub Environments for additional protection (required reviewers, branch restrictions)
- ✅ Workflow has minimal permissions (`id-token: write`, `contents: read`)
- ⚠️ Regularly rotate Azure service principal credentials
- ⚠️ Review federated credential subjects to prevent unauthorized access
## Troubleshooting
### Token Validation Failures
- Verify `ENTRA_AUDIENCE` matches your app's identifier URI
- Ensure the token includes the required scope or role
- Check token expiration time
### Module Loading Issues
- Ensure `requirements.psd1` is properly formatted
- Check that managed dependencies are enabled in `host.json`
- Review function app logs for module installation errors
### OBO Flow Failures
- Verify `ENTRA_CLIENT_SECRET` is configured
- Ensure the client app is authorized for OBO
- Check that required API permissions are granted
### GitHub Actions Deployment Failures
**OIDC Authentication Issues**:
- Verify federated identity credential subject matches your repository
- Check that `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_SUBSCRIPTION_ID` secrets are set correctly
- Ensure the service principal has Contributor role on the resource group
- Verify the federated credential issuer is `https://token.actions.githubusercontent.com`
**Deployment Package Issues**:
- Ensure all required files (host.json, profile.ps1, requirements.psd1, function folders) are included
- Check GitHub Actions logs for file copy or zip errors
- Verify the deployment package size is within Azure limits
**Function App Configuration Issues**:
- Confirm all required secrets are set in GitHub (ENTRA_CLIENT_ID, ENTRA_TENANT_ID, etc.)
- Verify the Function App name and resource group are correct
- Check that the Function App is configured for PowerShell 7.2 runtime
## Resources
- [Azure Functions PowerShell Developer Guide](https://docs.microsoft.com/azure/azure-functions/functions-reference-powershell)
- [PnP PowerShell Documentation](https://pnp.github.io/powershell/)
- [Microsoft Entra ID Authentication](https://docs.microsoft.com/azure/active-directory/develop/)
- [On-Behalf-Of Flow](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow)
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.