From d09fddf96046f52a2c100f7670bc6c420d8b86f3 Mon Sep 17 00:00:00 2001 From: Hunter Halloran Date: Fri, 9 Jan 2026 12:17:00 -0500 Subject: [PATCH] feat: Begin support for OIDC login --- docs/MICROSOFT_ENTRA_QUICKSTART.md | 109 ++++++ docs/MICROSOFT_ENTRA_SETUP.md | 342 ++++++++++++++++++ management-dashboard-web-app/.env.example | 7 + management-dashboard-web-app/src/App.tsx | 2 +- .../src/components/Login.tsx | 55 +++ .../src/components/MicrosoftIcon.tsx | 11 + .../src/vite-env.d.ts | 1 + package-lock.json | 61 ++++ package.json | 9 + 9 files changed, 596 insertions(+), 1 deletion(-) create mode 100644 docs/MICROSOFT_ENTRA_QUICKSTART.md create mode 100644 docs/MICROSOFT_ENTRA_SETUP.md create mode 100644 management-dashboard-web-app/src/components/MicrosoftIcon.tsx create mode 100644 package-lock.json create mode 100644 package.json diff --git a/docs/MICROSOFT_ENTRA_QUICKSTART.md b/docs/MICROSOFT_ENTRA_QUICKSTART.md new file mode 100644 index 0000000..9b343cf --- /dev/null +++ b/docs/MICROSOFT_ENTRA_QUICKSTART.md @@ -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.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 diff --git a/docs/MICROSOFT_ENTRA_SETUP.md b/docs/MICROSOFT_ENTRA_SETUP.md new file mode 100644 index 0000000..38278bf --- /dev/null +++ b/docs/MICROSOFT_ENTRA_SETUP.md @@ -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://.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://.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://.supabase.co +VITE_SUPABASE_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://.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://.supabase.co +VITE_SUPABASE_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 diff --git a/management-dashboard-web-app/.env.example b/management-dashboard-web-app/.env.example index 91a7ca1..f292910 100755 --- a/management-dashboard-web-app/.env.example +++ b/management-dashboard-web-app/.env.example @@ -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 diff --git a/management-dashboard-web-app/src/App.tsx b/management-dashboard-web-app/src/App.tsx index f7f3786..2cae9fd 100755 --- a/management-dashboard-web-app/src/App.tsx +++ b/management-dashboard-web-app/src/App.tsx @@ -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) diff --git a/management-dashboard-web-app/src/components/Login.tsx b/management-dashboard-web-app/src/components/Login.tsx index 8e4929f..26a89c1 100755 --- a/management-dashboard-web-app/src/components/Login.tsx +++ b/management-dashboard-web-app/src/components/Login.tsx @@ -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(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 (
@@ -108,6 +136,33 @@ export function Login({ onLoginSuccess }: LoginProps) {
+ + {enableMicrosoftLogin && ( + <> +
+
+
+
+
+ + Or continue with + +
+
+ +
+ +
+ + )}
) diff --git a/management-dashboard-web-app/src/components/MicrosoftIcon.tsx b/management-dashboard-web-app/src/components/MicrosoftIcon.tsx new file mode 100644 index 0000000..8676966 --- /dev/null +++ b/management-dashboard-web-app/src/components/MicrosoftIcon.tsx @@ -0,0 +1,11 @@ +export function MicrosoftIcon({ className = "w-5 h-5" }: { className?: string }) { + return ( + + + + + + + + ) +} diff --git a/management-dashboard-web-app/src/vite-env.d.ts b/management-dashboard-web-app/src/vite-env.d.ts index 46479a0..14266d5 100755 --- a/management-dashboard-web-app/src/vite-env.d.ts +++ b/management-dashboard-web-app/src/vite-env.d.ts @@ -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 { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4d8cb8b --- /dev/null +++ b/package-lock.json @@ -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" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7ad4e9b --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.7" + } +}