Tutorial: Azure Static Web Apps with Entra ID Authentication from Scratch

This guide walks through setting up a static website on Azure Static Web Apps, authenticated against your organisation's Microsoft Entra ID tenant, deployed via GitHub Actions, with per-branch preview environments. No custom authentication code required.

Contents

  1. Prerequisites
  2. Create the repository
  3. Add static content
  4. Configure authentication
  5. Register an Entra ID application
  6. Create the Static Web App in Azure
  7. Configure application settings
  8. Set up GitHub Actions
  9. Per-branch preview environments
  10. Custom domain (production)
  11. Testing the setup
  12. Troubleshooting
  13. Sources

1. Prerequisites

2. Create the repository

Create a new private repository on GitHub. The structure will look like this:

my-internal-site/
  src/
    index.html
    style.css
  staticwebapp.config.json
  .github/
    workflows/
      deploy.yml
Note: SWA is flexible about directory structure. We use src/ as the app directory, but you can use any folder name or even the repository root.

3. Add static content

Create a minimal page to verify the deployment works.

src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Internal Site</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Internal Site</h1>
    <p>If you can see this, you are authenticated.</p>
    <p><a href="/.auth/logout">Sign out</a></p>
</body>
</html>

src/style.css

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    max-width: 600px;
    margin: 2rem auto;
    padding: 0 1rem;
    color: #333;
}

4. Configure authentication

This is the key file. Create staticwebapp.config.json in the root of the repository (or in your app directory — SWA will find it).

staticwebapp.config.json

{
  "auth": {
    "identityProviders": {
      "customOpenIdConnectProviders": {
        "aad": {
          "registration": {
            "clientIdSettingName": "AAD_CLIENT_ID",
            "clientCredential": {
              "clientSecretSettingName": "AAD_CLIENT_SECRET"
            },
            "openIdConnectConfiguration": {
              "wellKnownOpenIdConfiguration":
                "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0/.well-known/openid-configuration"
            }
          },
          "login": {
            "nameClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
            "scopes": ["openid", "profile", "email"]
          }
        }
      }
    }
  },
  "routes": [
    {
      "route": "/.auth/login/github",
      "statusCode": 404
    },
    {
      "route": "/.auth/login/twitter",
      "statusCode": 404
    },
    {
      "route": "/*",
      "allowedRoles": ["authenticated"]
    }
  ],
  "responseOverrides": {
    "401": {
      "redirect": "/.auth/login/aad",
      "statusCode": 302
    }
  }
}

What this configuration does:

Important: Replace YOUR_TENANT_ID with your actual Entra ID tenant ID (a GUID). You can find it in the Azure Portal under Microsoft Entra ID > Overview > Tenant ID.

5. Register an Entra ID application

You need an app registration so SWA can authenticate users against your tenant.

1 Go to Azure Portal > Microsoft Entra ID > App registrations > New registration.

2 Fill in:

3 After registration, note the Application (client) ID.

4 Go to Certificates & secrets > New client secret. Create a secret, and copy its Value immediately (it is shown only once).

5 (Optional) To restrict access to specific users or groups: go to Enterprise applications, find your app by its object ID, go to Properties, and set “Assignment required?” to Yes. Then assign the users or groups under Users and groups.

6. Create the Static Web App in Azure

Option A: Azure Portal

1 Go to Azure Portal > Create a resource > Static Web App.

2 Fill in:

3 Click Review + Create. Azure will create the SWA and commit a GitHub Actions workflow to your repository.

Option B: Azure CLI

# Create resource group
az group create \
  --name rg-internal-site \
  --location westeurope

# Create the Static Web App
az staticwebapp create \
  --name my-internal-site \
  --resource-group rg-internal-site \
  --source https://github.com/YOUR_ORG/my-internal-site \
  --branch main \
  --app-location "/src" \
  --login-with-github \
  --sku Standard

After creation, note the default hostname (e.g., purple-sand-0ab1c2d3e.azurestaticapps.net).

Don't forget: Go back to your Entra ID app registration and update the Redirect URI to https://<your-default-hostname>/.auth/login/aad/callback.

7. Configure application settings

The staticwebapp.config.json references two setting names: AAD_CLIENT_ID and AAD_CLIENT_SECRET. Set these in the SWA resource.

Via Azure Portal

Go to your Static Web App > Configuration > Application settings > Add:

Via Azure CLI

az staticwebapp appsettings set \
  --name my-internal-site \
  --resource-group rg-internal-site \
  --setting-names \
    AAD_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
    AAD_CLIENT_SECRET=your-client-secret-value

8. Set up GitHub Actions

When you create the SWA via the portal with GitHub linked, Azure auto-generates a workflow file. If you prefer to manage it manually (or used the CLI), create the following:

.github/workflows/deploy.yml

name: Deploy to Azure Static Web Apps

on:
  push:
    branches:
      - main
      - dev
      - 'feature/**'
  pull_request:
    types: [opened, synchronize, reopened, closed]
    branches:
      - main

jobs:
  build_and_deploy:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy
    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          action: "upload"
          app_location: "/src"
          output_location: ""
          production_branch: "main"

  close_pull_request:
    if: github.event_name == 'pull_request' && github.event.action == 'closed'
    runs-on: ubuntu-latest
    name: Close PR Environment
    steps:
      - name: Close
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
          action: "close"

Key points:

9. Per-branch preview environments

With the workflow above, SWA automatically creates preview environments for non-production branches.

How it works

Branch / Event Environment URL pattern
main Production purple-sand-0ab1c2d3e.azurestaticapps.net
dev Branch preview purple-sand-0ab1c2d3e-dev.westeurope.azurestaticapps.net
feature/xyz Branch preview purple-sand-0ab1c2d3e-feature-xyz.westeurope.azurestaticapps.net
Pull request #42 PR preview purple-sand-0ab1c2d3e-42.westeurope.azurestaticapps.net
Custom domain limitation: Preview environment URLs are auto-generated. You cannot assign custom domains like dev.mysite.com to them. Only the production environment supports custom domains. If custom subdomains per branch are critical, consider deploying separate SWA instances per environment or using Azure Front Door as a routing layer (at additional cost).
Environment limits: The Standard plan supports up to 10 staging environments. When the limit is reached, the oldest environment must be removed before a new one can be created.

Adding the Redirect URI for preview environments

Each preview environment has a different hostname, so the Entra ID callback URL will differ. You need to add each preview environment's callback URL to your app registration's Redirect URIs. You can add a wildcard-style entry or add them as needed:

Alternatively, add all expected preview URLs upfront in the Entra ID app registration.

10. Custom domain (production)

To use your own domain (e.g., internal.yourcompany.com) for the production environment:

1 In the Azure Portal, go to your SWA > Custom domains > Add.

2 Enter your domain name.

3 Azure will ask you to create a CNAME record pointing to your SWA's default hostname, or a TXT record for domain validation (if using an apex domain, you need an ALIAS/ANAME record or Azure DNS).

4 Once validated, Azure automatically provisions a free SSL certificate.

5 Update your Entra ID app registration Redirect URI to use the custom domain:
https://internal.yourcompany.com/.auth/login/aad/callback

11. Testing the setup

  1. Push to main and wait for the GitHub Action to complete.
  2. Open the production URL in a private/incognito browser window.
  3. You should be redirected to the Microsoft login page.
  4. Sign in with an account from your Entra ID tenant — you should see the site.
  5. Try signing in with a personal Microsoft account or an account from a different tenant — it should be rejected.
  6. Visit /.auth/me to see the authenticated user's claims (JSON).
  7. Push to a non-main branch and verify the preview environment is created.

12. Troubleshooting

Redirects to login.microsoftonline.com/common instead of your tenant

This means the wellKnownOpenIdConfiguration URL is wrong or missing. Ensure it contains your specific tenant ID, not common or organizations.

401 after successful login

Check that AAD_CLIENT_ID and AAD_CLIENT_SECRET are set correctly in the SWA application settings. Verify the Redirect URI in the Entra ID app registration matches exactly.

Authentication works in production but not in preview

The preview environment has a different hostname. Add its callback URL to the Entra ID app registration's Redirect URIs.

“AADSTS50011: The reply URL specified in the request does not match”

The Redirect URI in the Entra ID app registration does not match the callback URL. Ensure it is https://<exact-hostname>/.auth/login/aad/callback.

Free plan — custom auth not working

Custom OpenID Connect providers (including tenant-specific Entra ID) require the Standard plan. The free plan only supports the pre-configured providers which allow any Microsoft account.

Final repository structure

my-internal-site/
  .github/
    workflows/
      deploy.yml
  src/
    index.html
    style.css
  staticwebapp.config.json

That is the entire setup — three config files, two content files, and zero lines of authentication code. The Microsoft platform handles login, token validation, session management, and access control.

Sources

Last updated: April 2026