Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c2994db
feat: EntraID authentication
Lucki2g Dec 6, 2025
759d4d5
chore: can remove server serialization. Not used clientside
Lucki2g Dec 6, 2025
275c2d0
fix: always redirect to loign
Lucki2g Dec 6, 2025
f6b7bbb
chore: Microsoft icon for AD login
Lucki2g Dec 6, 2025
226faae
feat: use normal grant flow instead of baked in, in the app service.
Lucki2g Dec 15, 2025
930a4c7
fix: save session when logged in with entra
Lucki2g Dec 15, 2025
88d4035
fix: clear Entra cookies and session on logout, like with password au…
Lucki2g Dec 15, 2025
e3721e3
chore: swapped to correct env variables in auth config and pipelines
Lucki2g Dec 15, 2025
373a46d
feat: UI indication for slower Entra login
Lucki2g Dec 15, 2025
ec1f11b
feat: Entra security group required authentication
Lucki2g Dec 15, 2025
4a60b3e
feat: carousel item for home page.
Lucki2g Dec 15, 2025
e77783a
fix: npm package version bump to remove critial and high vulnerabilit…
Lucki2g Dec 15, 2025
6eef484
feat: rate limit and brute force password protection.
Lucki2g Dec 15, 2025
68efb5a
feat: security headers:
Lucki2g Dec 15, 2025
6facf7b
chore: carousel images also move with the text on home page.
Lucki2g Dec 15, 2025
f21be88
chore: readme update
Lucki2g Dec 20, 2025
4a29b83
fix: coalesce the booleans in pipeline
Lucki2g Dec 20, 2025
bb78a32
chore: changed naming in pipelines for less confusion
Lucki2g Dec 20, 2025
5ebfa31
fix: coalsace other optional params
Lucki2g Dec 20, 2025
4c11593
fix: changed boolean to string variables in pipelines, as boolean kep…
Lucki2g Dec 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 261 additions & 0 deletions Infrastructure/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ param adoProjectName string = ''

@description('Azure DevOps repository name for diagram storage')
param adoRepositoryName string = ''

@description('Enable EntraID authentication via Easy Auth')
param enableEntraIdAuth bool = false

@description('Azure AD App Registration Client ID')
param entraIdClientId string = ''

@description('Azure AD Tenant ID (defaults to subscription tenant)')
param entraIdTenantId string = subscription().tenantId

@description('Comma-separated list of Azure AD Group Object IDs allowed to access (empty = all tenant users)')
param entraIdAllowedGroups string = ''

@description('Disable password authentication (EntraID only)')
param disablePasswordAuth bool = false
```

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

## Deployment

Expand Down Expand Up @@ -141,6 +159,249 @@ The Azure Pipeline (`azure-pipelines-deploy-jobs.yml`) deploys using:
-adoRepositoryName $(AdoRepositoryName)
```

## EntraID Authentication Setup

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.

**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".

### Authentication Modes

Three authentication modes are supported:

1. **Password Only** (default): Traditional password-based login
2. **EntraID Only**: Microsoft SSO authentication via OpenID Connect, password login disabled
3. **Dual Mode**: Users can choose between password or Microsoft SSO

### EntraID Prerequisites

Before enabling EntraID authentication:

1. **Azure AD App Registration**
2. **User access to Azure AD tenant**
3. **Optional**: Azure AD security groups for access control

### Step 1: Create Azure AD App Registration

1. Navigate to [Azure Portal](https://portal.azure.com) → **Azure Active Directory** → **App registrations**
2. Click **New registration**
3. Configure:
- **Name**: `Data Model Viewer - {environment}` (e.g., `Data Model Viewer - Production`)
- **Supported account types**: `Accounts in this organizational directory only (Single tenant)`
- **Redirect URI**:
- Platform: `Web`
- URI: `https://wa-{solutionId}.azurewebsites.net/api/auth/callback/microsoft-entra-id`
- Replace `{solutionId}` with your actual solution ID
4. Click **Register**
5. Note the **Application (client) ID** and **Directory (tenant) ID** from the Overview page

### Step 2: Create Client Secret

**CRITICAL**: Azure App Service Easy Auth requires a client secret to avoid using deprecated implicit grant flow.

1. In your App Registration, go to **Certificates & secrets**
2. Click **Client secrets** → **New client secret**
3. Enter:
- **Description**: `App Service SSO`
- **Expires**: Choose appropriate duration (e.g., 24 months)
4. Click **Add**
5. **IMPORTANT**: Copy the **Value** (not the Secret ID) immediately - it won't be shown again
6. Save this value securely - you'll use it in the deployment

**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.

### Step 3: Configure App Registration API Permissions

1. In your App Registration, go to **API permissions**
2. Click **Add a permission** → **Microsoft Graph** → **Delegated permissions**
3. Add these permissions:
- `User.Read` (required - basic user profile)
- `Group.Read.All` (optional - required for group-based access control)
4. Click **Add permissions**
5. **Grant admin consent** if required by your organization

### Step 4: Configure Token Claims (Optional - for Group-Based Access)

To enable group-based access control:

1. Go to **Token configuration** in your App Registration
2. Click **Add groups claim**
3. Select **Security groups**
4. Check both **ID** and **Access tokens**
5. Click **Add**

### Step 5: Get Security Group Object IDs (Optional)

If restricting access to specific groups:

1. Navigate to **Azure Active Directory** → **Groups**
2. Find the group(s) that should have access
3. Click on each group and copy the **Object ID**
4. Prepare comma-separated list: `abc123-...,def456-...,ghi789-...`

### Step 6: Deploy with EntraID Enabled

#### Option A: Enable on New Deployment

```bash
az deployment group create \
--resource-group rg-datamodelviewer \
--template-file main.bicep \
--parameters solutionId=myorg-dmv \
websitePassword='SecurePassword123!' \
sessionSecret='<32-byte-random-string>' \
enableEntraIdAuth=true \
entraIdClientId='<your-client-id>' \
entraIdClientSecret='<your-client-secret>' \
entraIdTenantId='<your-tenant-id>' \
entraIdAllowedGroups='<group-id-1>,<group-id-2>' \
disablePasswordAuth=false \
adoOrganizationUrl='https://dev.azure.com/myorg' \
adoProjectName='MyProject' \
adoRepositoryName='DataModelViewer'
```

#### Option B: Update Existing Deployment

```bash
az deployment group create \
--resource-group rg-datamodelviewer \
--template-file main.bicep \
--parameters @previous-parameters.json \
enableEntraIdAuth=true \
entraIdClientId='<your-client-id>' \
entraIdClientSecret='<your-client-secret>' \
entraIdTenantId='<your-tenant-id>'
```

### EntraID Parameter Reference

| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `enableEntraIdAuth` | No | `false` | Enables EntraID authentication via Easy Auth |
| `entraIdClientId` | Yes if enabled | `''` | Application (client) ID from App Registration |
| `entraIdClientSecret` | Yes if enabled | `''` | Client secret value from App Registration |
| `entraIdTenantId` | No | Subscription tenant | Directory (tenant) ID for your Azure AD |
| `entraIdAllowedGroups` | No | `''` | Comma-separated group Object IDs. Empty = all tenant users |
| `disablePasswordAuth` | No | `false` | Set to `true` for EntraID-only mode |

### Authentication Mode Examples

#### Example 1: Dual Mode (Password + EntraID)
```bash
--parameters enableEntraIdAuth=true \
entraIdClientId='abc123...' \
entraIdClientSecret='secret123...' \
disablePasswordAuth=false
```
- Users see both "Sign in with Microsoft" and password form
- Existing password auth continues to work
- Ideal for gradual migration

#### Example 2: EntraID Only
```bash
--parameters enableEntraIdAuth=true \
entraIdClientId='abc123...' \
entraIdClientSecret='secret123...' \
disablePasswordAuth=true
```
- Only "Sign in with Microsoft" button shown
- Password login disabled
- Full enterprise SSO

#### Example 3: EntraID with Group Restrictions
```bash
--parameters enableEntraIdAuth=true \
entraIdClientId='abc123...' \
entraIdClientSecret='secret123...' \
entraIdAllowedGroups='group-id-1,group-id-2'
```
- Only users in specified security groups can access
- Returns 403 Forbidden for unauthorized users

### How EntraID Authentication Works

1. **User Access**: User navigates to `https://wa-{solutionId}.azurewebsites.net/`
2. **Easy Auth Intercepts**: App Service Easy Auth detects unauthenticated request
3. **Redirect to Microsoft**: User redirected to `login.microsoftonline.com`
4. **User Signs In**: User authenticates with Microsoft account
5. **Token Exchange**: Microsoft returns ID token to Easy Auth
6. **Header Injection**: Easy Auth validates token and injects `X-MS-CLIENT-PRINCIPAL` header
7. **Application Access**: Middleware parses header, creates session, grants access

### Troubleshooting EntraID Authentication

#### Users Get "AADSTS700054: response_type 'id_token' is not enabled" Error

**Problem**: No client secret configured - Easy Auth is falling back to deprecated implicit grant flow

**Solution**:
1. Create a client secret in your App Registration:
- Go to Azure Portal → Azure Active Directory → App registrations
- Select your Data Model Viewer app registration
- Go to **Certificates & secrets** → **Client secrets** → **New client secret**
- Copy the secret **Value** (not Secret ID)
2. Redeploy with the client secret parameter:
```bash
az deployment group create \
--resource-group rg-datamodelviewer \
--template-file main.bicep \
--parameters @previous-parameters.json \
entraIdClientSecret='<paste-your-client-secret-here>'
```
3. Wait 2-3 minutes for App Service to restart
4. Try logging in again

**Note**: Easy Auth requires a client secret to avoid using the deprecated OAuth 2.0 implicit grant flow

#### Users Get "Redirect URI Mismatch" Error

**Problem**: App Registration redirect URI doesn't match deployed URL

**Solution**:
1. Check App Registration → Authentication → Redirect URIs
2. Ensure it matches: `https://wa-{solutionId}.azurewebsites.net/.auth/login/aad/callback`
3. Verify HTTPS (not HTTP)
4. No trailing slash

#### Users Get "AADSTS50020: User account does not exist" Error

**Problem**: User's account is not in the specified tenant

**Solution**:
1. Verify user belongs to correct Azure AD tenant
2. Check App Registration is "Single tenant" type
3. Ensure user account is not external/guest (or add multi-tenant support)

#### Users Get 403 Forbidden After Login

**Problem**: User not in allowed security groups

**Solution**:
1. Check `entraIdAllowedGroups` parameter includes user's group
2. Verify group claim is configured in token configuration
3. Check API permission `Group.Read.All` is granted
4. Wait 5-10 minutes for group membership cache to refresh

#### EntraID Login Doesn't Work Locally

**Expected Behavior**: Easy Auth only works on Azure App Service

**Solution**:
- Use password authentication for local development
- Set `ENABLE_ENTRAID_AUTH=false` in `.env.local`
- Test EntraID in deployed dev environment

#### Can't Find App Service Managed Identity in Azure AD

**Problem**: Looking for wrong object

**Solution**:
- Managed Identity is for **backend services** (Dataverse, ADO)
- **EntraID/Easy Auth** is for **user authentication**
- These are separate authentication mechanisms
- Don't add users to Managed Identity

## Post-Deployment Configuration

### 1. Configure Startup Command
Expand Down
51 changes: 51 additions & 0 deletions Infrastructure/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ param adoOrganizationUrl string = ''
param adoProjectName string = ''
param adoRepositoryName string = ''

@description('Enable EntraID authentication')
param enableEntraIdAuth bool = false

@description('Azure AD App Registration Client ID')
param entraIdClientId string = ''

@description('Azure AD App Registration Client Secret')
@secure()
param entraIdClientSecret string = ''

@description('Azure AD Tenant ID (defaults to subscription tenant)')
param entraIdTenantId string = subscription().tenantId

@description('Comma-separated list of Azure AD Group Object IDs allowed to access (empty = all tenant users)')
param entraIdAllowedGroups string = ''

@description('Disable password authentication')
param disablePasswordAuth bool = false

var location = resourceGroup().location

@description('Create an App Service Plan')
Expand Down Expand Up @@ -45,6 +64,14 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = {
name: 'WebsiteSessionSecret'
value: sessionSecret
}
{
name: 'AUTH_SECRET'
value: sessionSecret
}
{
name: 'NEXTAUTH_URL'
value: 'https://wa-${solutionId}.azurewebsites.net'
}
{
name: 'WEBSITE_NODE_DEFAULT_VERSION'
value: '~20'
Expand All @@ -61,6 +88,30 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = {
name: 'ADO_REPOSITORY_NAME'
value: adoRepositoryName
}
{
name: 'ENABLE_ENTRAID_AUTH'
value: string(enableEntraIdAuth)
}
{
name: 'AZURE_AD_CLIENT_ID'
value: entraIdClientId
}
{
name: 'AZURE_AD_CLIENT_SECRET'
value: entraIdClientSecret
}
{
name: 'AZURE_AD_TENANT_ID'
value: entraIdTenantId
}
{
name: 'ENTRAID_ALLOWED_GROUPS'
value: entraIdAllowedGroups
}
{
name: 'DISABLE_PASSWORD_AUTH'
value: string(disablePasswordAuth)
}
]
}
}
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
</a>
</p>

> [!NOTE]
> 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.

# 👋 Introduction

<p className="text-gray-700 mb-4">
Expand Down
Loading
Loading