feat: Begin support for OIDC login

This commit is contained in:
2026-01-09 12:17:00 -05:00
parent 5fdc02d2fc
commit d09fddf960
9 changed files with 596 additions and 1 deletions

View File

@@ -0,0 +1,109 @@
# Microsoft Entra OpenID Connect - Quick Start
## What Was Implemented
The Login flow now supports Microsoft Entra ID (Azure AD) authentication via OpenID Connect using Supabase's Azure OAuth provider.
## Files Modified
1. **[Login.tsx](management-dashboard-web-app/src/components/Login.tsx)**: Added Microsoft login button and OAuth flow
2. **[MicrosoftIcon.tsx](management-dashboard-web-app/src/components/MicrosoftIcon.tsx)**: Created Microsoft logo component
3. **[vite-env.d.ts](management-dashboard-web-app/src/vite-env.d.ts)**: Added TypeScript type for new environment variable
4. **[.env.example](management-dashboard-web-app/.env.example)**: Added Microsoft login configuration
## How It Works
### User Experience
1. User visits login page and sees two options:
- Traditional email/password login (existing)
- "Sign in with Microsoft" button (new)
2. Clicking Microsoft button redirects to Microsoft login
3. User authenticates with Microsoft credentials
4. Microsoft redirects back to app with authenticated session
### Technical Flow
```
User clicks "Sign in with Microsoft"
Supabase signInWithOAuth() with provider: 'azure'
Redirect to Microsoft Entra login page
User authenticates with Microsoft
Microsoft redirects to Supabase callback URL
Supabase validates and creates session
User redirected back to application (authenticated)
```
## Configuration Required
### 1. Azure Portal Setup
- Register application in Microsoft Entra ID
- Configure redirect URI: `https://<supabase-ref>.supabase.co/auth/v1/callback`
- Generate client ID and client secret
- Set API permissions (openid, profile, email)
### 2. Supabase Configuration
Navigate to Authentication > Providers > Azure and configure:
- **Azure Client ID**: From Azure app registration
- **Azure Secret**: From Azure client secrets
- **Azure Tenant**: Use `common` for multi-tenant or specific tenant ID
### 3. Application Environment
Set in `.env` file:
```bash
VITE_ENABLE_MICROSOFT_LOGIN=true
```
## Testing
### Enable Microsoft Login
```bash
# In .env file
VITE_ENABLE_MICROSOFT_LOGIN=true
```
### Disable Microsoft Login
```bash
# In .env file
VITE_ENABLE_MICROSOFT_LOGIN=false
# Or simply remove the variable
```
## Features
**Hybrid Authentication**: Supports both email/password and Microsoft login
**Feature Flag**: Microsoft login can be enabled/disabled via environment variable
**Dark Mode Support**: Microsoft button matches the existing theme
**Error Handling**: Displays authentication errors to users
**Loading States**: Shows loading indicator during authentication
**Type Safety**: Full TypeScript support with proper types
## Next Steps
1. **Complete Azure Setup**: Follow [MICROSOFT_ENTRA_SETUP.md](./MICROSOFT_ENTRA_SETUP.md) for detailed configuration
2. **Configure Supabase**: Enable Azure provider in Supabase dashboard
3. **Test Authentication**: Verify the complete login flow
4. **Deploy**: Update production environment variables
## Documentation
- **Full Setup Guide**: [MICROSOFT_ENTRA_SETUP.md](./MICROSOFT_ENTRA_SETUP.md) - Complete Azure and Supabase configuration
- **Supabase Docs**: https://supabase.com/docs/guides/auth/social-login/auth-azure
- **Microsoft Identity Platform**: https://docs.microsoft.com/azure/active-directory/develop/
## Support
For issues or questions:
- Check [MICROSOFT_ENTRA_SETUP.md](./MICROSOFT_ENTRA_SETUP.md) troubleshooting section
- Review Supabase Auth logs
- Check Azure sign-in logs in Azure Portal
- Verify redirect URIs match exactly
---
**Implementation Date**: January 9, 2026
**Status**: Ready for configuration and testing

View File

@@ -0,0 +1,342 @@
# Microsoft Entra (Azure AD) OpenID Connect Setup Guide
## Overview
This guide walks you through configuring Microsoft Entra ID (formerly Azure Active Directory) authentication for the USDA Vision Management Dashboard using Supabase's Azure OAuth provider.
## Prerequisites
- Access to Azure Portal (https://portal.azure.com)
- Admin permissions to register applications in Azure AD
- Access to your Supabase project dashboard
- The USDA Vision application deployed and accessible via URL
## Step 1: Register Application in Microsoft Entra ID
### 1.1 Navigate to Azure Portal
1. Log in to [Azure Portal](https://portal.azure.com)
2. Navigate to **Microsoft Entra ID** (or **Azure Active Directory**)
3. Select **App registrations** from the left sidebar
4. Click **+ New registration**
### 1.2 Configure Application Registration
Fill in the following details:
- **Name**: `USDA Vision Management Dashboard` (or your preferred name)
- **Supported account types**: Choose one of:
- **Single tenant**: Only users in your organization can sign in (most restrictive)
- **Multitenant**: Users in any Azure AD tenant can sign in
- **Multitenant + personal Microsoft accounts**: Broadest support
- **Redirect URI**:
- Platform: **Web**
- URI: `https://<your-supabase-project-ref>.supabase.co/auth/v1/callback`
- Example: `https://abcdefghij.supabase.co/auth/v1/callback`
Click **Register** to create the application.
### 1.3 Note Application (Client) ID
After registration, you'll be taken to the app overview page. Copy and save:
- **Application (client) ID**: This is your `AZURE_CLIENT_ID`
- **Directory (tenant) ID**: This is your `AZURE_TENANT_ID`
## Step 2: Configure Client Secret
### 2.1 Create a Client Secret
1. In your app registration, navigate to **Certificates & secrets**
2. Click **+ New client secret**
3. Add a description: `Supabase Auth`
4. Choose an expiration period (recommendation: 12-24 months)
5. Click **Add**
### 2.2 Save the Secret Value
**IMPORTANT**: Copy the **Value** immediately - it will only be shown once!
- This is your `AZURE_CLIENT_SECRET`
- Store it securely (password manager, secure vault, etc.)
## Step 3: Configure API Permissions
### 3.1 Add Required Permissions
1. Navigate to **API permissions** in your app registration
2. Click **+ Add a permission**
3. Select **Microsoft Graph**
4. Choose **Delegated permissions**
5. Add the following permissions:
- `openid` (Sign users in)
- `profile` (View users' basic profile)
- `email` (View users' email address)
- `User.Read` (Sign in and read user profile)
6. Click **Add permissions**
### 3.2 Grant Admin Consent (Optional but Recommended)
If you have admin privileges:
1. Click **Grant admin consent for [Your Organization]**
2. Confirm the action
This prevents users from seeing a consent prompt on first login.
## Step 4: Configure Authentication Settings
### 4.1 Set Token Configuration
1. Navigate to **Token configuration** in your app registration
2. Click **+ Add optional claim**
3. Choose **ID** token type
4. Add the following claims:
- `email`
- `family_name`
- `given_name`
- `upn` (User Principal Name)
5. Check **Turn on the Microsoft Graph email, profile permission** if prompted
### 4.2 Configure Authentication Flow
1. Navigate to **Authentication** in your app registration
2. Under **Implicit grant and hybrid flows**, ensure:
-**ID tokens** (used for implicit and hybrid flows) is checked
3. Under **Allow public client flows**:
- Select **No** (keep it secure)
## Step 5: Configure Supabase
### 5.1 Navigate to Supabase Auth Settings
1. Log in to your [Supabase Dashboard](https://app.supabase.com)
2. Select your project
3. Navigate to **Authentication** > **Providers**
4. Find **Azure** in the provider list
### 5.2 Enable and Configure Azure Provider
1. Toggle **Enable Sign in with Azure** to ON
2. Fill in the configuration:
- **Azure Client ID**: Paste your Application (client) ID from Step 1.3
- **Azure Secret**: Paste your client secret value from Step 2.2
- **Azure Tenant**: You have two options:
- Use `common` for multi-tenant applications
- Use your specific **Directory (tenant) ID** for single-tenant
- Format: Just the GUID (e.g., `12345678-1234-1234-1234-123456789012`)
3. Click **Save**
### 5.3 Note the Callback URL
Supabase provides the callback URL in the format:
```
https://<your-project-ref>.supabase.co/auth/v1/callback
```
Verify this matches what you configured in Azure (Step 1.2).
## Step 6: Configure Application Environment
### 6.1 Update Environment Variables
In your application's `.env` file, add or update:
```bash
# Supabase Configuration (if not already present)
VITE_SUPABASE_URL=https://<your-project-ref>.supabase.co
VITE_SUPABASE_ANON_KEY=<your-anon-key>
# Enable Microsoft Login
VITE_ENABLE_MICROSOFT_LOGIN=true
```
### 6.2 Restart Development Server
If running locally:
```bash
npm run dev
```
## Step 7: Test the Integration
### 7.1 Test Login Flow
1. Navigate to your application's login page
2. You should see a "Sign in with Microsoft" button
3. Click the button
4. You should be redirected to Microsoft's login page
5. Sign in with your Microsoft account
6. Grant consent if prompted
7. You should be redirected back to your application, logged in
### 7.2 Verify User Data
After successful login, check that:
- User profile information is available
- Email address is correctly populated
- User roles/permissions are properly assigned (if using RBAC)
### 7.3 Common Issues and Troubleshooting
#### Redirect URI Mismatch
**Error**: `AADSTS50011: The redirect URI ... does not match the redirect URIs configured`
**Solution**: Ensure the redirect URI in Azure matches exactly:
```
https://<your-project-ref>.supabase.co/auth/v1/callback
```
#### Invalid Client Secret
**Error**: `Invalid client secret provided`
**Solution**:
- Verify you copied the secret **Value**, not the Secret ID
- Generate a new client secret if the old one expired
#### Missing Permissions
**Error**: User consent required or permission denied
**Solution**:
- Add the required API permissions in Azure (Step 3)
- Grant admin consent if available
#### CORS Errors
**Error**: CORS policy blocking requests
**Solution**:
- Ensure your application URL is properly configured in Supabase
- Check that you're using the correct Supabase URL
## Step 8: Production Deployment
### 8.1 Update Redirect URI for Production
1. Go back to Azure Portal > App registrations
2. Navigate to **Authentication**
3. Add production redirect URI:
```
https://your-production-domain.com/
```
4. Ensure Supabase callback URI is still present
### 8.2 Set Production Environment Variables
Update your production environment with:
```bash
VITE_SUPABASE_URL=https://<your-project-ref>.supabase.co
VITE_SUPABASE_ANON_KEY=<your-production-anon-key>
VITE_ENABLE_MICROSOFT_LOGIN=true
```
### 8.3 Security Best Practices
1. **Rotate Secrets Regularly**: Set calendar reminders before client secret expiration
2. **Use Azure Key Vault**: Store secrets in Azure Key Vault for enhanced security
3. **Monitor Sign-ins**: Use Azure AD sign-in logs to monitor authentication activity
4. **Implement MFA**: Require multi-factor authentication for sensitive accounts
5. **Review Permissions**: Regularly audit API permissions and remove unnecessary ones
## Advanced Configuration
### Multi-Tenant Support
If you want to support users from multiple Azure AD tenants:
1. In Azure App Registration > **Authentication**:
- Set **Supported account types** to **Multitenant**
2. In Supabase Azure provider configuration:
- Set **Azure Tenant** to `common`
### Custom Domain Configuration
If using a custom domain with Supabase:
1. Configure custom domain in Supabase dashboard
2. Update redirect URI in Azure to use custom domain
3. Update application environment variables
### User Attribute Mapping
To map Azure AD attributes to Supabase user metadata:
1. Use Supabase database triggers or functions
2. Extract attributes from JWT token
3. Update user metadata in `auth.users` table
Example attributes available:
- `sub`: User's unique ID
- `email`: Email address
- `name`: Full name
- `given_name`: First name
- `family_name`: Last name
- `preferred_username`: Username
## Integration with RBAC System
To integrate Microsoft Entra authentication with your existing RBAC system:
### Option 1: Manual Role Assignment
After user signs in via Microsoft:
1. Admin assigns roles in the management dashboard
2. Roles stored in your `user_roles` table
3. User gets appropriate permissions on next login
### Option 2: Azure AD Group Mapping
Map Azure AD groups to application roles:
1. Configure group claims in Azure token configuration
2. Read groups from JWT token in Supabase
3. Automatically assign roles based on group membership
### Option 3: Hybrid Approach
Support both Microsoft and email/password login:
- Keep existing email/password authentication
- Add Microsoft as an additional option
- Users can link accounts or use either method
## Monitoring and Maintenance
### Azure AD Monitoring
- **Sign-in logs**: Monitor authentication attempts
- **Audit logs**: Track configuration changes
- **Usage analytics**: Understand authentication patterns
### Supabase Monitoring
- **Auth logs**: View authentication events
- **User activity**: Track active users
- **Error logs**: Identify authentication issues
### Regular Maintenance Tasks
- [ ] Rotate client secrets before expiration (set reminders)
- [ ] Review and update API permissions quarterly
- [ ] Audit user access and remove inactive users
- [ ] Update token configuration as needed
- [ ] Test authentication flow after any infrastructure changes
## Support and Resources
### Microsoft Documentation
- [Microsoft identity platform documentation](https://docs.microsoft.com/azure/active-directory/develop/)
- [Azure AD app registration](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app)
- [OpenID Connect on Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc)
### Supabase Documentation
- [Supabase Auth with Azure](https://supabase.com/docs/guides/auth/social-login/auth-azure)
- [Supabase Auth API reference](https://supabase.com/docs/reference/javascript/auth-signinwithoauth)
### Troubleshooting Resources
- Azure AD Error Codes: https://docs.microsoft.com/azure/active-directory/develop/reference-aadsts-error-codes
- Supabase Discord Community: https://discord.supabase.com
---
**Last Updated**: January 2026
**Maintained By**: USDA Vision System Team

View File

@@ -10,3 +10,10 @@ VITE_VISION_SYSTEM_REMOTE_URL=http://exp-dash:3002/assets/remoteEntry.js?v=$(dat
# API URLs
VITE_VISION_API_URL=http://exp-dash:8000
VITE_MEDIA_API_URL=http://exp-dash:8090
# Supabase Configuration
VITE_SUPABASE_URL=https://your-project-url.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
# Microsoft Entra (Azure AD) OAuth Configuration
VITE_ENABLE_MICROSOFT_LOGIN=true

View File

@@ -14,7 +14,7 @@ function App() {
checkAuthState()
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: string, session: any) => {
console.log('Auth state changed:', event, !!session)
setIsAuthenticated(!!session)
setLoading(false)

View File

@@ -1,5 +1,6 @@
import { useState } from 'react'
import { supabase } from '../lib/supabase'
import { MicrosoftIcon } from './MicrosoftIcon'
interface LoginProps {
onLoginSuccess: () => void
@@ -10,6 +11,7 @@ export function Login({ onLoginSuccess }: LoginProps) {
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const enableMicrosoftLogin = import.meta.env.VITE_ENABLE_MICROSOFT_LOGIN === 'true'
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
@@ -35,6 +37,32 @@ export function Login({ onLoginSuccess }: LoginProps) {
}
}
const handleMicrosoftLogin = async () => {
setLoading(true)
setError(null)
try {
const { error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'email openid profile',
redirectTo: `${window.location.origin}/`,
},
})
if (error) {
setError(error.message)
setLoading(false)
}
// If successful, user will be redirected to Microsoft login
// and then back to the app, so we don't stop loading here
} catch (err) {
setError('An unexpected error occurred during Microsoft login')
console.error('Microsoft login error:', err)
setLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
<div className="max-w-md w-full space-y-8">
@@ -108,6 +136,33 @@ export function Login({ onLoginSuccess }: LoginProps) {
</button>
</div>
</form>
{enableMicrosoftLogin && (
<>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300 dark:border-gray-700" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-gray-50 dark:bg-gray-900 text-gray-500 dark:text-gray-400">
Or continue with
</span>
</div>
</div>
<div>
<button
type="button"
onClick={handleMicrosoftLogin}
disabled={loading}
className="w-full flex items-center justify-center gap-3 py-2 px-4 border border-gray-300 dark:border-gray-700 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
<MicrosoftIcon className="w-5 h-5" />
<span>Sign in with Microsoft</span>
</button>
</div>
</>
)}
</div>
</div>
)

View File

@@ -0,0 +1,11 @@
export function MicrosoftIcon({ className = "w-5 h-5" }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 23 23" xmlns="http://www.w3.org/2000/svg">
<path fill="#f3f3f3" d="M0 0h23v23H0z" />
<path fill="#f35325" d="M1 1h10v10H1z" />
<path fill="#81bc06" d="M12 1h10v10H12z" />
<path fill="#05a6f0" d="M1 12h10v10H1z" />
<path fill="#ffba08" d="M12 12h10v10H12z" />
</svg>
)
}

View File

@@ -4,6 +4,7 @@ interface ImportMetaEnv {
readonly VITE_SUPABASE_URL: string;
readonly VITE_SUPABASE_ANON_KEY: string;
readonly VITE_VISION_API_URL?: string; // optional; defaults to "/api" via vite proxy
readonly VITE_ENABLE_MICROSOFT_LOGIN?: string; // optional; enable Microsoft Entra authentication
}
interface ImportMeta {

61
package-lock.json generated Normal file
View File

@@ -0,0 +1,61 @@
{
"name": "usda-vision",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/react": "^19.2.7"
}
},
"node_modules/@types/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/react": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.3"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
}
}
}

9
package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"dependencies": {
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/react": "^19.2.7"
}
}