Skip to content

Commit 1f60380

Browse files
authored
Merge pull request #79 from delegateas/authentication/230-security-update
Entra ID Authentication, Password Bruteforce Protection & Package Upgrades
2 parents 3481c24 + 4c11593 commit 1f60380

26 files changed

Lines changed: 1472 additions & 192 deletions

Infrastructure/CLAUDE.md

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ param adoProjectName string = ''
6464
6565
@description('Azure DevOps repository name for diagram storage')
6666
param adoRepositoryName string = ''
67+
68+
@description('Enable EntraID authentication via Easy Auth')
69+
param enableEntraIdAuth bool = false
70+
71+
@description('Azure AD App Registration Client ID')
72+
param entraIdClientId string = ''
73+
74+
@description('Azure AD Tenant ID (defaults to subscription tenant)')
75+
param entraIdTenantId string = subscription().tenantId
76+
77+
@description('Comma-separated list of Azure AD Group Object IDs allowed to access (empty = all tenant users)')
78+
param entraIdAllowedGroups string = ''
79+
80+
@description('Disable password authentication (EntraID only)')
81+
param disablePasswordAuth bool = false
6782
```
6883

6984
### Resource Naming Convention
@@ -86,6 +101,9 @@ The template configures these environment variables for the Website:
86101
| `ADO_ORGANIZATION_URL` | Parameter | Azure DevOps org URL |
87102
| `ADO_PROJECT_NAME` | Parameter | ADO project name |
88103
| `ADO_REPOSITORY_NAME` | Parameter | Diagram storage repo |
104+
| `ENABLE_ENTRAID_AUTH` | Parameter | Enable EntraID auth |
105+
| `ENTRAID_ALLOWED_GROUPS` | Parameter | Group-based access control |
106+
| `DISABLE_PASSWORD_AUTH` | Parameter | Disable password login |
89107

90108
## Deployment
91109

@@ -141,6 +159,249 @@ The Azure Pipeline (`azure-pipelines-deploy-jobs.yml`) deploys using:
141159
-adoRepositoryName $(AdoRepositoryName)
142160
```
143161
162+
## EntraID Authentication Setup
163+
164+
The application supports **optional** Microsoft EntraID (Azure AD) authentication using **OpenID Connect** (via NextAuth.js). This provides enterprise single sign-on (SSO) with your organization's Microsoft accounts.
165+
166+
**Important**: This implementation uses standard OpenID Connect flow, NOT Azure App Service Easy Auth. Users can always access the login page - authentication only occurs when they click "Sign in with Microsoft".
167+
168+
### Authentication Modes
169+
170+
Three authentication modes are supported:
171+
172+
1. **Password Only** (default): Traditional password-based login
173+
2. **EntraID Only**: Microsoft SSO authentication via OpenID Connect, password login disabled
174+
3. **Dual Mode**: Users can choose between password or Microsoft SSO
175+
176+
### EntraID Prerequisites
177+
178+
Before enabling EntraID authentication:
179+
180+
1. **Azure AD App Registration**
181+
2. **User access to Azure AD tenant**
182+
3. **Optional**: Azure AD security groups for access control
183+
184+
### Step 1: Create Azure AD App Registration
185+
186+
1. Navigate to [Azure Portal](https://portal.azure.com) → **Azure Active Directory** → **App registrations**
187+
2. Click **New registration**
188+
3. Configure:
189+
- **Name**: `Data Model Viewer - {environment}` (e.g., `Data Model Viewer - Production`)
190+
- **Supported account types**: `Accounts in this organizational directory only (Single tenant)`
191+
- **Redirect URI**:
192+
- Platform: `Web`
193+
- URI: `https://wa-{solutionId}.azurewebsites.net/api/auth/callback/microsoft-entra-id`
194+
- Replace `{solutionId}` with your actual solution ID
195+
4. Click **Register**
196+
5. Note the **Application (client) ID** and **Directory (tenant) ID** from the Overview page
197+
198+
### Step 2: Create Client Secret
199+
200+
**CRITICAL**: Azure App Service Easy Auth requires a client secret to avoid using deprecated implicit grant flow.
201+
202+
1. In your App Registration, go to **Certificates & secrets**
203+
2. Click **Client secrets** → **New client secret**
204+
3. Enter:
205+
- **Description**: `App Service SSO`
206+
- **Expires**: Choose appropriate duration (e.g., 24 months)
207+
4. Click **Add**
208+
5. **IMPORTANT**: Copy the **Value** (not the Secret ID) immediately - it won't be shown again
209+
6. Save this value securely - you'll use it in the deployment
210+
211+
**Alternative (Preview)**: Azure supports using a managed identity with federated credentials instead of a client secret. This approach is currently in preview and requires additional setup. For production deployments, the client secret approach is recommended. See Microsoft's documentation on [using a managed identity instead of a secret](https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad?tabs=workforce-configuration%2Cworkforce-tenant#use-a-managed-identity-instead-of-a-secret-preview) if interested.
212+
213+
### Step 3: Configure App Registration API Permissions
214+
215+
1. In your App Registration, go to **API permissions**
216+
2. Click **Add a permission** → **Microsoft Graph** → **Delegated permissions**
217+
3. Add these permissions:
218+
- `User.Read` (required - basic user profile)
219+
- `Group.Read.All` (optional - required for group-based access control)
220+
4. Click **Add permissions**
221+
5. **Grant admin consent** if required by your organization
222+
223+
### Step 4: Configure Token Claims (Optional - for Group-Based Access)
224+
225+
To enable group-based access control:
226+
227+
1. Go to **Token configuration** in your App Registration
228+
2. Click **Add groups claim**
229+
3. Select **Security groups**
230+
4. Check both **ID** and **Access tokens**
231+
5. Click **Add**
232+
233+
### Step 5: Get Security Group Object IDs (Optional)
234+
235+
If restricting access to specific groups:
236+
237+
1. Navigate to **Azure Active Directory** → **Groups**
238+
2. Find the group(s) that should have access
239+
3. Click on each group and copy the **Object ID**
240+
4. Prepare comma-separated list: `abc123-...,def456-...,ghi789-...`
241+
242+
### Step 6: Deploy with EntraID Enabled
243+
244+
#### Option A: Enable on New Deployment
245+
246+
```bash
247+
az deployment group create \
248+
--resource-group rg-datamodelviewer \
249+
--template-file main.bicep \
250+
--parameters solutionId=myorg-dmv \
251+
websitePassword='SecurePassword123!' \
252+
sessionSecret='<32-byte-random-string>' \
253+
enableEntraIdAuth=true \
254+
entraIdClientId='<your-client-id>' \
255+
entraIdClientSecret='<your-client-secret>' \
256+
entraIdTenantId='<your-tenant-id>' \
257+
entraIdAllowedGroups='<group-id-1>,<group-id-2>' \
258+
disablePasswordAuth=false \
259+
adoOrganizationUrl='https://dev.azure.com/myorg' \
260+
adoProjectName='MyProject' \
261+
adoRepositoryName='DataModelViewer'
262+
```
263+
264+
#### Option B: Update Existing Deployment
265+
266+
```bash
267+
az deployment group create \
268+
--resource-group rg-datamodelviewer \
269+
--template-file main.bicep \
270+
--parameters @previous-parameters.json \
271+
enableEntraIdAuth=true \
272+
entraIdClientId='<your-client-id>' \
273+
entraIdClientSecret='<your-client-secret>' \
274+
entraIdTenantId='<your-tenant-id>'
275+
```
276+
277+
### EntraID Parameter Reference
278+
279+
| Parameter | Required | Default | Description |
280+
|-----------|----------|---------|-------------|
281+
| `enableEntraIdAuth` | No | `false` | Enables EntraID authentication via Easy Auth |
282+
| `entraIdClientId` | Yes if enabled | `''` | Application (client) ID from App Registration |
283+
| `entraIdClientSecret` | Yes if enabled | `''` | Client secret value from App Registration |
284+
| `entraIdTenantId` | No | Subscription tenant | Directory (tenant) ID for your Azure AD |
285+
| `entraIdAllowedGroups` | No | `''` | Comma-separated group Object IDs. Empty = all tenant users |
286+
| `disablePasswordAuth` | No | `false` | Set to `true` for EntraID-only mode |
287+
288+
### Authentication Mode Examples
289+
290+
#### Example 1: Dual Mode (Password + EntraID)
291+
```bash
292+
--parameters enableEntraIdAuth=true \
293+
entraIdClientId='abc123...' \
294+
entraIdClientSecret='secret123...' \
295+
disablePasswordAuth=false
296+
```
297+
- Users see both "Sign in with Microsoft" and password form
298+
- Existing password auth continues to work
299+
- Ideal for gradual migration
300+
301+
#### Example 2: EntraID Only
302+
```bash
303+
--parameters enableEntraIdAuth=true \
304+
entraIdClientId='abc123...' \
305+
entraIdClientSecret='secret123...' \
306+
disablePasswordAuth=true
307+
```
308+
- Only "Sign in with Microsoft" button shown
309+
- Password login disabled
310+
- Full enterprise SSO
311+
312+
#### Example 3: EntraID with Group Restrictions
313+
```bash
314+
--parameters enableEntraIdAuth=true \
315+
entraIdClientId='abc123...' \
316+
entraIdClientSecret='secret123...' \
317+
entraIdAllowedGroups='group-id-1,group-id-2'
318+
```
319+
- Only users in specified security groups can access
320+
- Returns 403 Forbidden for unauthorized users
321+
322+
### How EntraID Authentication Works
323+
324+
1. **User Access**: User navigates to `https://wa-{solutionId}.azurewebsites.net/`
325+
2. **Easy Auth Intercepts**: App Service Easy Auth detects unauthenticated request
326+
3. **Redirect to Microsoft**: User redirected to `login.microsoftonline.com`
327+
4. **User Signs In**: User authenticates with Microsoft account
328+
5. **Token Exchange**: Microsoft returns ID token to Easy Auth
329+
6. **Header Injection**: Easy Auth validates token and injects `X-MS-CLIENT-PRINCIPAL` header
330+
7. **Application Access**: Middleware parses header, creates session, grants access
331+
332+
### Troubleshooting EntraID Authentication
333+
334+
#### Users Get "AADSTS700054: response_type 'id_token' is not enabled" Error
335+
336+
**Problem**: No client secret configured - Easy Auth is falling back to deprecated implicit grant flow
337+
338+
**Solution**:
339+
1. Create a client secret in your App Registration:
340+
- Go to Azure Portal → Azure Active Directory → App registrations
341+
- Select your Data Model Viewer app registration
342+
- Go to **Certificates & secrets** → **Client secrets** → **New client secret**
343+
- Copy the secret **Value** (not Secret ID)
344+
2. Redeploy with the client secret parameter:
345+
```bash
346+
az deployment group create \
347+
--resource-group rg-datamodelviewer \
348+
--template-file main.bicep \
349+
--parameters @previous-parameters.json \
350+
entraIdClientSecret='<paste-your-client-secret-here>'
351+
```
352+
3. Wait 2-3 minutes for App Service to restart
353+
4. Try logging in again
354+
355+
**Note**: Easy Auth requires a client secret to avoid using the deprecated OAuth 2.0 implicit grant flow
356+
357+
#### Users Get "Redirect URI Mismatch" Error
358+
359+
**Problem**: App Registration redirect URI doesn't match deployed URL
360+
361+
**Solution**:
362+
1. Check App Registration → Authentication → Redirect URIs
363+
2. Ensure it matches: `https://wa-{solutionId}.azurewebsites.net/.auth/login/aad/callback`
364+
3. Verify HTTPS (not HTTP)
365+
4. No trailing slash
366+
367+
#### Users Get "AADSTS50020: User account does not exist" Error
368+
369+
**Problem**: User's account is not in the specified tenant
370+
371+
**Solution**:
372+
1. Verify user belongs to correct Azure AD tenant
373+
2. Check App Registration is "Single tenant" type
374+
3. Ensure user account is not external/guest (or add multi-tenant support)
375+
376+
#### Users Get 403 Forbidden After Login
377+
378+
**Problem**: User not in allowed security groups
379+
380+
**Solution**:
381+
1. Check `entraIdAllowedGroups` parameter includes user's group
382+
2. Verify group claim is configured in token configuration
383+
3. Check API permission `Group.Read.All` is granted
384+
4. Wait 5-10 minutes for group membership cache to refresh
385+
386+
#### EntraID Login Doesn't Work Locally
387+
388+
**Expected Behavior**: Easy Auth only works on Azure App Service
389+
390+
**Solution**:
391+
- Use password authentication for local development
392+
- Set `ENABLE_ENTRAID_AUTH=false` in `.env.local`
393+
- Test EntraID in deployed dev environment
394+
395+
#### Can't Find App Service Managed Identity in Azure AD
396+
397+
**Problem**: Looking for wrong object
398+
399+
**Solution**:
400+
- Managed Identity is for **backend services** (Dataverse, ADO)
401+
- **EntraID/Easy Auth** is for **user authentication**
402+
- These are separate authentication mechanisms
403+
- Don't add users to Managed Identity
404+
144405
## Post-Deployment Configuration
145406

146407
### 1. Configure Startup Command

Infrastructure/main.bicep

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ param adoOrganizationUrl string = ''
88
param adoProjectName string = ''
99
param adoRepositoryName string = ''
1010

11+
@description('Enable EntraID authentication')
12+
param enableEntraIdAuth bool = false
13+
14+
@description('Azure AD App Registration Client ID')
15+
param entraIdClientId string = ''
16+
17+
@description('Azure AD App Registration Client Secret')
18+
@secure()
19+
param entraIdClientSecret string = ''
20+
21+
@description('Azure AD Tenant ID (defaults to subscription tenant)')
22+
param entraIdTenantId string = subscription().tenantId
23+
24+
@description('Comma-separated list of Azure AD Group Object IDs allowed to access (empty = all tenant users)')
25+
param entraIdAllowedGroups string = ''
26+
27+
@description('Disable password authentication')
28+
param disablePasswordAuth bool = false
29+
1130
var location = resourceGroup().location
1231

1332
@description('Create an App Service Plan')
@@ -45,6 +64,14 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = {
4564
name: 'WebsiteSessionSecret'
4665
value: sessionSecret
4766
}
67+
{
68+
name: 'AUTH_SECRET'
69+
value: sessionSecret
70+
}
71+
{
72+
name: 'NEXTAUTH_URL'
73+
value: 'https://wa-${solutionId}.azurewebsites.net'
74+
}
4875
{
4976
name: 'WEBSITE_NODE_DEFAULT_VERSION'
5077
value: '~20'
@@ -61,6 +88,30 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = {
6188
name: 'ADO_REPOSITORY_NAME'
6289
value: adoRepositoryName
6390
}
91+
{
92+
name: 'ENABLE_ENTRAID_AUTH'
93+
value: string(enableEntraIdAuth)
94+
}
95+
{
96+
name: 'AZURE_AD_CLIENT_ID'
97+
value: entraIdClientId
98+
}
99+
{
100+
name: 'AZURE_AD_CLIENT_SECRET'
101+
value: entraIdClientSecret
102+
}
103+
{
104+
name: 'AZURE_AD_TENANT_ID'
105+
value: entraIdTenantId
106+
}
107+
{
108+
name: 'ENTRAID_ALLOWED_GROUPS'
109+
value: entraIdAllowedGroups
110+
}
111+
{
112+
name: 'DISABLE_PASSWORD_AUTH'
113+
value: string(disablePasswordAuth)
114+
}
64115
]
65116
}
66117
}

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
</a>
99
</p>
1010

11+
> [!NOTE]
12+
> The README is slowly being moved to [Git Wiki](https://github.com/delegateas/DataModelViewer/wiki). Newer features are documented in the wiki whilst older ones are documented in this Readme.
13+
1114
# 👋 Introduction
1215

1316
<p className="text-gray-700 mb-4">

0 commit comments

Comments
 (0)