The Brief
Create a mobile app for company employees to view holiday entitlement and book holiday requests.
Requirements:
- Works on both Android and Apple phones.
- Does not require user accounts to be registered in our company Acitve Directory.
- App should provide self-service registration and authenticate with minimal interaction required from HR and/or IT.
Technologies
- Progressive Web App
- Azure Static Web Apps
- Azure DevOps
- Entra External ID (aka Microsoft Entra ID for customers)
The first requirement is covered with the use of a PWA. I can use my existing expertise in web development & web platform technologies to provide a native-app experience on both of the dominant mobile phone platforms.
Requirement two and three is covered by utilising Microsoft Entra External ID. A customer identity access management solution that allows users to register and login using their own personal email address.
Steps
Create project in Azure DevOps
- Create project in Azure DevOps
- Repos => Initialize main branch with a README or gitignore => Select VisualStudio .gitignore and click Initialize
Entra ID Tenant - Create SWA
- Create SWA in main Entra ID tenant
- Plan Type = Standard
- Deployment details
- Source = Azure DevOps
- Build Details = Custom
- App location = “/src”
- Api location = “/api”
- Output location = ""
- Select Repo created in Azure DevOps above
When the cretion procedure is complete, an Azure Static Web Apps workflow YAML file is automatically created in the Repo.
Create Customer Tenant
- Create Entra tenant in Azure Documentation Which type of users will you manage in this tenant? = Customer
Ensure you are in your company's main Microsoft Entra ID tenant when performing this step. When writing this post, I selected to create a tenant whilst already in a Customer tenant I had created earlier. The options for the "Select a tenant type" had the "Azure AD B2C" option disabled, meaning there must be different permissions at play in the background.
"If you're creating a customer tenant for the first time, you have the option to create a trial tenant that doesn't require an Azure subscription."
Register an app in the Customer Tenant
- App registrations
- Supported account types = Accounts in this organizational directory only (MyCompanyEmployee only - Single tenant)
- Redirect URIs
- Platform = Web
- URI = https://«SWA-URL-created-above»azurestaticapps.net/.auth/login/aad/callback
- Implicit grant and hybrid flows => ID tokens (used for implicit and hybrid flows)
- Register
- API Permissions => Add a permission => Microsoft Graph => Delegated Permissions
- OpenId permissions:
- offline_access
- openid
- profile
- Click the “Grant admin consent for ” button
- OpenId permissions:
- Sidebar => Manage => Certificates & secrets (you will need the “Value” of the newly created secret later on).
Create User Flow
To enable user flow creation, you first need to enable the feature.
- External Identities => External collaboration settings => “Enable guest self-service sign up via user flows”
- External Identities => User flows => New user flow
- Identity Providers = Email one-time passcode
- User attributes = Given Name, Surname, Display Name
- Create
- Click to access new user flow
- Applications
- Add application => select app created above
Configure SWA in Entra ID Tenant
- Go to SWA resource
- Configuration
- Add two Application Settings, ClientSecret and ClientID, both values taken from the App Registration in the Customer Tenant.
Coding the SWA
- Browse to your SWA project in Azure DevOps => Repos
- Clone project to VS Code
- Install/Enable the Azure Static Web Apps extension
- Install/Enable the Azure Functions extension
- Add the ./api folder
- F1 => Azure Static Web Apps: Create HTTP Function => If prompted for a folder for your function, browse to the api folder created above.
- Install SWA CLI
- npm install -D @azure/static-web-apps-cli (accept helpful tip from VS Code to add node_modules folder to .gitignore file)
- npx swa init
- Create the config file
- “The recommended location for the staticwebapp.config.json is in the folder set as the app_location in the workflow file”
- Create an index.html file in the ./src folder.
Oryx could not find a 'build' or 'build:azure' script in the package configuration. Please add one of these commands to your package configuration file (i.e. package.json).
Alternatively, you can add the app_build_command to the build/deploy section of your deployment configuration file. For example, app_build_command: 'npm run docs:build'
Add skip_app_build: true
Failed to find a default file in the app artifacts folder (/). Valid default files: index.html,Index.html.
Change app_location from "/" to "./src", the actual folder that the index.html file is in.
The Azure Static Web Apps workflow YAML file:
name: Azure Static Web Apps CI/CD
pr:
branches:
include:
- main
trigger:
branches:
include:
- main
jobs:
- job: build_and_deploy_job
displayName: Build and Deploy Job
condition: or(eq(variables['Build.Reason'], 'Manual'),or(eq(variables['Build.Reason'], 'PullRequest'),eq(variables['Build.Reason'], 'IndividualCI')))
pool:
vmImage: ubuntu-latest
variables:
- group: Azure-Static-Web-Apps-random-words-123456789-variable-group
steps:
- checkout: self
submodules: true
- task: AzureStaticWebApp@0
inputs:
azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN_RANDOM_WORDS_123456789)
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "/src" # App source code path
api_location: "/api" # Api source code path - optional
output_location: "" # Built app content directory - optional
skip_app_build: true
###### End of Repository/Build Configurations ######
Authentication
##Updated 09/02/2024 with resolution of this issue
The Static Web Apps config json file needs to be updated with the settings to implement Entra External ID authentication.
{
"routes": [
{
"route": "/*",
"allowedRoles": [
"authenticated"
]
},
{
"route": "/logout",
"redirect": "/.auth/logout"
},
{
"route": "/me",
"redirect": "/.auth/me"
}
],
"responseOverrides": {
"401": {
"statusCode": 302,
"redirect": "/.auth/login/aad"
}
},
"trailingSlash": "auto",
"platform": {
"apiRuntime": "dotnet-isolated:8.0"
},
"auth": {
"identityProviders": {
"azureActiveDirectory": {
"userDetailsClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"registration": {
"openIdIssuer": "https://{TenantID}.ciamlogin.com/{TenantID}/v2.0",
"clientIdSettingName": "ClientID",
"clientSecretSettingName": "ClientSecret"
}
}
}
}
}
Browsing to the SWA URL, I am presented with a basic login screen.