Conversation
There was a problem hiding this comment.
Pull request overview
This pull request migrates the authentication system from a custom username/password login to OpenID Connect (OIDC) with PKCE (Proof Key for Code Exchange) for enhanced security. The changes integrate with a Keycloak identity provider and implement the OAuth 2.0 authorization code flow with PKCE.
Changes:
- Implemented OpenID Connect authentication flow with PKCE security extension
- Removed username/password login UI in favor of browser-based OAuth2 authentication
- Updated token refresh mechanism to use Keycloak's OIDC token endpoint
- Changed JWT claim type from "nameid" to "sub" to align with OIDC standards
- Added automatic login trigger when authentication window loads
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| src/OneWare.CloudIntegration/Views/AuthenticateCloudView.axaml.cs | Added Loaded event handler to automatically trigger browser-based login when window opens |
| src/OneWare.CloudIntegration/Views/AuthenticateCloudView.axaml | Removed username/password login UI elements, simplified to show only browser login option with waiting state |
| src/OneWare.CloudIntegration/ViewModels/AuthenticateCloudViewModel.cs | Removed success log message after browser authentication completes |
| src/OneWare.CloudIntegration/Services/OneWareCloudLoginService.cs | Implemented OIDC/PKCE authentication flow, updated token refresh to use Keycloak endpoints, changed JWT claim extraction to use "sub" claim |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwt); | ||
| var userId = jwtToken.Claims.FirstOrDefault(x => x.Type == "nameid")?.Value ?? null; | ||
| JwtSecurityToken? jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwt); |
There was a problem hiding this comment.
The nullable annotation on JwtSecurityToken is unnecessary here since ReadJwtToken will throw an exception if the JWT is invalid, not return null. The nullable annotation should be removed for clarity.
| JwtSecurityToken? jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwt); | |
| JwtSecurityToken jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwt); |
| private async void OnLoaded(object? sender, RoutedEventArgs e) | ||
| { | ||
| if (DataContext is AuthenticateCloudViewModel viewModel) | ||
| { | ||
| await viewModel.LoginWithBrowserAsync(this); | ||
| } |
There was a problem hiding this comment.
The async void event handler lacks error handling. If LoginWithBrowserAsync throws an exception, it will crash the application since async void methods cannot propagate exceptions. Add a try-catch block to handle potential exceptions and display an error message to the user or log the error appropriately.
| Loaded += OnLoaded; | ||
| } | ||
|
|
||
| private async void OnLoaded(object? sender, RoutedEventArgs e) | ||
| { | ||
| if (DataContext is AuthenticateCloudViewModel viewModel) | ||
| { | ||
| await viewModel.LoginWithBrowserAsync(this); | ||
| } | ||
| } |
There was a problem hiding this comment.
Triggering the login flow automatically when the window loads may not be the desired behavior. This prevents users from seeing the UI or understanding what's happening before authentication begins. Consider whether this automatic trigger is intentional or if users should have the option to manually initiate the login by clicking the "Retry Login" button first.
| Loaded += OnLoaded; | |
| } | |
| private async void OnLoaded(object? sender, RoutedEventArgs e) | |
| { | |
| if (DataContext is AuthenticateCloudViewModel viewModel) | |
| { | |
| await viewModel.LoginWithBrowserAsync(this); | |
| } | |
| } | |
| } |
| private string? _codeVerifier; | ||
| private string? _state; |
There was a problem hiding this comment.
Using instance-level fields for PKCE state and code_verifier creates a potential security issue if multiple login attempts occur concurrently. If a second login is initiated before the first completes, the state and code_verifier values will be overwritten, causing the first login attempt to fail validation. Consider using a dictionary keyed by state to support concurrent login attempts, or add proper locking to prevent concurrent logins.
| throw new Exception("Token or refresh token not found"); | ||
| if (response.IsSuccessful && !string.IsNullOrWhiteSpace(response.Content)) | ||
| { | ||
| JsonNode data = JsonSerializer.Deserialize<JsonNode>(response.Content)!; |
There was a problem hiding this comment.
Deserializing without null checking could throw a NullReferenceException if the response content is valid JSON but the deserialization returns null. Consider adding a null check after deserialization before accessing properties with the null-forgiving operator.
| JsonNode data = JsonSerializer.Deserialize<JsonNode>(response.Content)!; | |
| JsonNode? data = JsonSerializer.Deserialize<JsonNode>(response.Content); | |
| if (data is null) | |
| throw new Exception("Failed to deserialize token response"); |
| ContainerLocator.Current.Resolve<IWindowService>().ActivateMainWindow(); | ||
| ContainerLocator.Current.Resolve<IMainDockService>() | ||
| .Show(ContainerLocator.Current.Resolve<IOutputService>(), DockShowLocation.Bottom); | ||
| ContainerLocator.Current.Resolve<ILogger>() | ||
| .Log("Successfully logged in to OneWare Cloud via browser authentication.", true, Brushes.Lime); | ||
| } |
There was a problem hiding this comment.
The success log message that was previously displayed after successful browser authentication has been removed. This means users will no longer receive visual confirmation in the logs that their login was successful. Consider adding this feedback back, or ensure that success is communicated to users through another means (such as a UI notification or status update).
| public AuthenticateCloudView() | ||
| { | ||
| InitializeComponent(); | ||
| Loaded += OnLoaded; |
There was a problem hiding this comment.
The Loaded event handler is subscribed but never unsubscribed, which can cause a memory leak. The event handler should be unsubscribed when the window is closed or disposed. Consider unsubscribing in an overridden OnUnloaded method or by using weak event patterns.
|
|
||
| var restClient = | ||
| new RestClient(_httpService.HttpClient, new RestClientOptions("https://cloud.one-ware.com")); | ||
| RestClient restClient = new (_httpService.HttpClient); |
There was a problem hiding this comment.
The RestClient is now created without specifying a base URL, which will cause the feedback API requests to fail. The RestRequest uses relative paths like "/api/feedback/anonymous" and "/api/feedback" which require a base URL. The original code correctly specified "https://cloud.one-ware.com" as the base URL. This should be restored or obtained from settings using GetRestClient().
| RestClient restClient = new (_httpService.HttpClient); | |
| RestClient restClient = _httpService.GetRestClient(); |
acc6989 to
4328bee
Compare
No description provided.