-
Notifications
You must be signed in to change notification settings - Fork 160
Fix Azure AD authentication in Blazor WASM by passing explicit API scopes during token exchange #722
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…en requests - Updated WasmOidcTokenAccessor to request access tokens with explicit resource scopes - Filter out standard OIDC scopes (openid, profile, email, offline_access) and pass only API scopes - Register OidcOptions in DI container so WasmOidcTokenAccessor can access configured scopes - This ensures Azure AD receives scope parameter during token exchange, fixing AADSTS errors Co-authored-by: sfmskywalker <[email protected]>
- Document Azure AD app registration setup and requirements - Explain single-resource scope limitation (no mixing Graph + custom API scopes) - Add troubleshooting guide for common Azure AD errors (AADSTS28000, AADSTS28003) - Update example to use AddElsaOidcAuthentication instead of AddOidcAuthentication - Document that standard OIDC scopes are automatically filtered Co-authored-by: sfmskywalker <[email protected]>
…copilot/fix-openidconnect-login-issue
- Register OidcOptions as singleton instance instead of using Configure<T> - Remove IOptions<T> dependency from WasmOidcTokenAccessor - Add null check for Scopes array to prevent NullReferenceException - Simplify DI registration pattern Co-authored-by: sfmskywalker <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes Azure AD authentication issues in Blazor WebAssembly by ensuring that API scopes are explicitly passed during token exchange requests. Azure AD requires the scope parameter in token endpoint requests, which wasn't being sent when using the parameterless RequestAccessToken() method.
Key Changes:
- Modified token accessor to filter standard OIDC scopes and request tokens with explicit resource scopes for Azure AD compatibility
- Registered
OidcOptionsin the DI container to make configured scopes accessible to the token accessor service - Added comprehensive Azure AD/Microsoft Entra ID documentation with setup instructions, troubleshooting guide, and configuration examples
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/modules/Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm/Services/WasmOidcTokenAccessor.cs |
Enhanced to filter standard OIDC scopes and request access tokens with explicit resource scopes, ensuring Azure AD receives the required scope parameter during token exchange |
src/modules/Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm/Extensions/ServiceCollectionExtensions.cs |
Added registration of OidcOptions in the DI container so the token accessor can access configured scopes |
src/modules/Elsa.Studio.Authentication.OpenIdConnect/README.md |
Added Azure AD-specific configuration section with app registration setup, scope configuration, troubleshooting guide, and updated examples to use AddElsaOidcAuthentication |
Comments suppressed due to low confidence (1)
src/modules/Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm/Extensions/ServiceCollectionExtensions.cs:48
- The options registration pattern is redundant. Instead of creating an OidcOptions instance, configuring it, and then manually copying each property into services.Configure, you can directly register the configured instance. Consider using:
services.Configure<OidcOptions>(configure);orservices.AddSingleton(Options.Create(options));. The current approach requires maintaining property-by-property copying which is error-prone if new properties are added to OidcOptions.
services.AddSingleton(options);
// Register the token accessor
services.AddScoped<IOidcTokenAccessor, WasmOidcTokenAccessor>();
services.AddScoped<IAuthenticationProvider, OidcAuthProvider>();
services.AddScoped<IFeature, OpenIdConnectBlazorWasmFeature>();
// Configure WASM authentication using the built-in framework.
// Note: Entra ID requires absolute redirect URIs.
services.AddOidcAuthentication(wasmOptions =>
{
wasmOptions.ProviderOptions.Authority = options.Authority;
wasmOptions.ProviderOptions.ClientId = options.ClientId;
wasmOptions.ProviderOptions.ResponseType = options.ResponseType;
...odules/Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm/Services/WasmOidcTokenAccessor.cs
Outdated
Show resolved
Hide resolved
...odules/Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm/Services/WasmOidcTokenAccessor.cs
Outdated
Show resolved
Hide resolved
…m/Services/WasmOidcTokenAccessor.cs Co-authored-by: Copilot <[email protected]>
…m/Services/WasmOidcTokenAccessor.cs Co-authored-by: Copilot <[email protected]>
12dcd61
into
copilot/implement-elsastudio-authentication-oidc
…er abstractions (#721) * Initial plan * Phase 1-3 complete: Core OIDC module with Server and WASM implementations Co-authored-by: sfmskywalker <[email protected]> * Add shared authentication abstractions for multi-provider support Co-authored-by: sfmskywalker <[email protected]> * Add comprehensive authentication architecture documentation Co-authored-by: sfmskywalker <[email protected]> * Final: Add implementation summary for PR review Co-authored-by: sfmskywalker <[email protected]> * Add authentication infrastructure and modules for Elsa Studio, including ElsaAuth and OpenID Connect integration. * Update project references to include new Elsa Studio Authentication modules. * Update `RedirectToLoginUnauthorizedComponentProvider` to support fallback to default Unauthorized component when `IAuthorizationService` is unavailable. * Mark `Elsa.Studio.Login` APIs as obsolete and migrate authentication to `Elsa.Studio.Authentication.ElsaAuth`. Simplify dependencies and refactor JWT parsing for BlazorServer and BlazorWasm modules. * Switch to OpenID Connect authentication, remove legacy login module, and update service registration methods. * Replace `Login` module with OpenID Connect, update authentication pipeline, and revise default `GetClaimsFromUserInfoEndpoint`. * Replace `BearerTokenHttpMessageHandler` with `AuthenticatingApiHttpMessageHandler` and remove obsolete references * Organize solution structure by adding new folders: authentication, localization, workflows, deprecated, samples, and dashboard. Remove obsolete project references. * Refactor authentication: replace legacy services, update OIDC implementation, and restructure PKCE flow * Add `Elsa.Studio.Authentication.ElsaAuth.UI` module to provide Elsa Identity authentication with a login UI and unauthorized redirect behavior. * Migrate `AUTHENTICATION_ARCHITECTURE.md` to `doc/` folder, update project references, and refine namespace imports for authentication module. * Introduce `IAnonymousBackendApiClientProvider` and refactor API client creation to support non-authenticated backend calls. * Add token refresh mechanism for OpenID Connect and Elsa Identity authentication modules. Introduce token refresh coordinators, configuration providers, and support for silent token refresh. Update related services and integrate advanced options for customization. * Add persisted token refresh for OpenID Connect in Blazor Server: implement browser-side pings, background services, and configurable strategies. * Remove persisted token refresh strategy and related services from OpenID Connect configuration. * Refactor OIDC configuration for Blazor WebAssembly: add Azure AD compatibility patches, improve URI handling, and modularize features. * Fix Azure AD authentication in Blazor WASM by passing explicit API scopes during token exchange (#722) * Initial plan * Fix Azure AD authentication by passing explicit API scopes during token requests - Updated WasmOidcTokenAccessor to request access tokens with explicit resource scopes - Filter out standard OIDC scopes (openid, profile, email, offline_access) and pass only API scopes - Register OidcOptions in DI container so WasmOidcTokenAccessor can access configured scopes - This ensures Azure AD receives scope parameter during token exchange, fixing AADSTS errors Co-authored-by: sfmskywalker <[email protected]> * Add Azure AD configuration documentation for Blazor WASM - Document Azure AD app registration setup and requirements - Explain single-resource scope limitation (no mixing Graph + custom API scopes) - Add troubleshooting guide for common Azure AD errors (AADSTS28000, AADSTS28003) - Update example to use AddElsaOidcAuthentication instead of AddOidcAuthentication - Document that standard OIDC scopes are automatically filtered Co-authored-by: sfmskywalker <[email protected]> * Address code review feedback - Register OidcOptions as singleton instance instead of using Configure<T> - Remove IOptions<T> dependency from WasmOidcTokenAccessor - Add null check for Scopes array to prevent NullReferenceException - Simplify DI registration pattern Co-authored-by: sfmskywalker <[email protected]> * Update src/modules/Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm/Services/WasmOidcTokenAccessor.cs Co-authored-by: Copilot <[email protected]> * Update src/modules/Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm/Services/WasmOidcTokenAccessor.cs Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: sfmskywalker <[email protected]> Co-authored-by: Sipke Schoorstra <[email protected]> Co-authored-by: Copilot <[email protected]> * Remove obsolete Azure AD compatibility patches and cleanup related JavaScript and Razor components. * Refactor OpenID Connect callback path handling: use null-coalescing assignments and remove default path values from `OidcOptions`. * Add token purposes and scoped token caching for enhanced authentication configuration * Add scoped access token capabilities and token-purpose configuration Introduce `IScopedAccessTokenProvider`, `IOidcTokenAccessorWithScopes`, and associated models to enable scope-aware token acquisition based on token purposes. Update handlers to support backend API scopes and implement scoped token caching for multi-audience token scenarios. * Refactor authentication modules: simplify scoped token handling, update OIDC providers, and enhance incremental consent support. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: sfmskywalker <[email protected]> Co-authored-by: Sipke Schoorstra <[email protected]> Co-authored-by: Sipke Schoorstra <[email protected]> Co-authored-by: Copilot <[email protected]>
Azure AD requires the
scopeparameter in token endpoint requests, but Blazor WASM'sIAccessTokenProvider.RequestAccessToken()parameterless overload doesn't send it. This caused authentication to succeed but fail authorization, redirecting users to/authentication/login-failed.Changes
WasmOidcTokenAccessor: Request tokens with explicit resource scopes viaRequestAccessToken(new AccessTokenRequestOptions { Scopes = [...] }). Filters standard OIDC scopes (openid, profile, email, offline_access) and passes only API/resource scopes.ServiceCollectionExtensions: RegisterOidcOptionsas singleton for DI injection.README.md: Add Azure AD configuration guide covering single-resource scope limitation, app registration setup, and troubleshooting for AADSTS errors.Example
Azure AD now receives the scope parameter during token exchange, fixing authentication for SignalR connections and API calls.
Original prompt
There seems to be something fundamentally wrong with the Blazor WASM implementartion of the OpenIDConnect library. When I try to login, everything seems to work but I end up being redirected to the https://localhost:7052/authentication/login-failed page with the following error in the console:
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
The auth-interop.js was used for troubleshooting, which produces console output like this:
[auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
content.js:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
MudBlazor.min.js:1 Debugging hotkey: Shift+Cmd+D (when application has focus)
content.js:50 [auth-interop] Intercepted XHR token request to: https://login.microsoftonline.com/f35bcd45-7991-4e24-84f1-e964394501ad/oauth2/v2.0/token
content.js:53 [auth-interop] Original body length: 2488
content.js:58 [auth-interop] Added scope to token request
content.js:59 [auth-interop] Updated body length: 2597
content.js:70 [auth-interop] Captured ID token from response
content.js:30 [auth-interop] Intercepting userinfo request - will return empty response
6login-failed:1 Uncaught (in promise) Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was receivedUnderstand this error
Navigated to https://localhost:7052/authentication/login-failed
VM261:9 [auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
VM261:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
VM248:1 Debugging hotkey: Shift+Alt+D (when application has focus)
VM283:8 [Violation] 'setTimeout' handler took 407ms
VM305:9 [auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
VM305:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
Navigated to https://localhost:7052/
VM368:9 [auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
VM368:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
VM372:1 Debugging hotkey: Shift+Alt+D (when application has focus)
VM405:8 [Violation] 'setTimeout' handler took 412ms
VM439:9 [auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
VM439:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
VM404:3 info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
Navigated to https://localhost:7052/authentication/login
VM494:9 [auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
VM494:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
VM480:1 Debugging hotkey: Shift+Alt+D (when application has focus)
VM523:8 [Violation] 'setTimeout' handler took 408ms
VM550:9 [auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
VM550:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
VM582:9 [auth-interop] Will use scopes (single resource): openid profile offline_access api://dda3270c-997e-413a-9175-36b70134547c/elsa-server-api
VM582:121 [auth-interop] Azure AD compatibility patches initialized (scope + userinfo intercept)
Navigated to https://login.microsoftonline.com/f35bcd45-7991-4e24-84f1-e964394501ad/oauth2/v2.0/authorize?client_id=0078a6b1-54d9-438b-9223-e26f07191949&redirect_uri=https%3A%2F%2Flocalhost%3A7052%2Fauthentication%2Flogin-callback&response_type=code&scope=openid%20profile%20offline_access%20api%3A%2F%2Fdda3270c-997e-413a-9175-36b70134547c%2Felsa-server-api&state=e0b23bc42fa14946bab6256a65b8cb69&code_challenge=th1NwqZM4XtI_1O39A-eLEcsYchUzJJsB8OlPXDyWxA&code_challenge_method=S256&response_mode=query
Navigated to https://localhost:7052/authentication/login-callback?code=1.AXoARc1b85F5JE6E8elkOUUBrbGmeADZVItDkiPibwcZGUl6AAB6AA.BQABBAIAAAADAOz_BQD0_0V2b1N0c0FydGlmYWN0cwIAAAAAAHUryNZPafThvrNIkCpaxcP1t0Uf5iHsC0VWXNDSu4ERMU05_B3zmfMHbFjLBhpgoh2H9_IjKQmVHMID_wf5luD3m9DtCW-7hIh_t0675JSdDX7i3yJ_K_XeT1LQUvpg2zF1D7ghpmhEeZve0vH69ppkYNMeVhMsg7JO58HwOaNOgUKIhIuvNoaCKspqr_yw1fM0FPLfBZ_awP967z-Or_OFuZb4SXsFf1Ly2KLgvkAjwKjzEUGpp63OJVXxrp8-plHdHF-zkfhgoyxHfEcaIJJpTMqytg6WMFZ...
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.