From dd5ee09d07924e8e24f85ea168371d79b954e0fc Mon Sep 17 00:00:00 2001 From: michael Date: Thu, 18 Dec 2025 16:59:54 -0600 Subject: [PATCH] Fix JWT signature verification by requesting correct token audience MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change frontend apiTokenRequest scopes from OpenID-only to CLIENT_ID/.default This makes Azure AD issue tokens with audience = app client ID instead of Graph API - Add diagnostic logging in backend to show token claims before verification - Fixes 401 Unauthorized errors on all API calls after login 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/app/services/auth_service.py | 9 +++++++++ frontend/services/authConfig.ts | 13 ++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index 5cc6cb2..8745c6f 100755 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -82,6 +82,15 @@ async def verify_access_token(token: str) -> Optional[dict]: alg = unverified_header.get("alg") logger.info(f"[MSAL Backend] Token header - kid: {kid}, alg: {alg}") + # Log unverified claims to see what we're receiving + try: + unverified_claims = jwt.get_unverified_claims(token) + logger.info(f"[MSAL Backend] Token aud (unverified): {unverified_claims.get('aud')}") + logger.info(f"[MSAL Backend] Token iss (unverified): {unverified_claims.get('iss')}") + logger.info(f"[MSAL Backend] Token azp (unverified): {unverified_claims.get('azp')}") + except Exception as e: + logger.warning(f"[MSAL Backend] Could not decode unverified claims: {e}") + if not kid: logger.warning("[MSAL Backend] No key ID in token header") return None diff --git a/frontend/services/authConfig.ts b/frontend/services/authConfig.ts index 9dd1ebc..41e11e6 100755 --- a/frontend/services/authConfig.ts +++ b/frontend/services/authConfig.ts @@ -4,10 +4,13 @@ */ import { Configuration, LogLevel, PopupRequest } from '@azure/msal-browser'; +// Client ID used for both MSAL config and API token requests +const CLIENT_ID = import.meta.env.VITE_AZURE_CLIENT_ID || ''; + // MSAL configuration - uses PKCE by default for SPAs export const msalConfig: Configuration = { auth: { - clientId: import.meta.env.VITE_AZURE_CLIENT_ID || '', + clientId: CLIENT_ID, authority: `https://login.microsoftonline.com/${import.meta.env.VITE_AZURE_TENANT_ID || 'common'}`, redirectUri: import.meta.env.VITE_AZURE_REDIRECT_URI || window.location.origin, postLogoutRedirectUri: window.location.origin, @@ -41,13 +44,13 @@ export const msalConfig: Configuration = { }, }; -// Scopes for the access token -// Using basic OpenID scopes for authentication +// Scopes for initial login (ID token) export const loginRequest: PopupRequest = { scopes: ['openid', 'profile', 'email'], }; -// Scopes for API calls (same as login for this app) +// Scopes for API calls - request token for OUR app, not Graph +// Using the client ID as scope requests an access token for this app export const apiTokenRequest = { - scopes: ['openid', 'profile', 'email'], + scopes: [`${CLIENT_ID}/.default`], };