Skip to content

Use OpenID Connect for Login.#197

Merged
hendrikmennen merged 5 commits intomainfrom
feature/AuthOverOpenIdConnect
Mar 5, 2026
Merged

Use OpenID Connect for Login.#197
hendrikmennen merged 5 commits intomainfrom
feature/AuthOverOpenIdConnect

Conversation

@NPK111
Copy link
Copy Markdown
Contributor

@NPK111 NPK111 commented Feb 25, 2026

No description provided.

Copy link
Copy Markdown

Copilot AI left a 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 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);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
JwtSecurityToken? jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwt);
JwtSecurityToken jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwt);

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +20
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (DataContext is AuthenticateCloudViewModel viewModel)
{
await viewModel.LoginWithBrowserAsync(this);
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to 21
Loaded += OnLoaded;
}

private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (DataContext is AuthenticateCloudViewModel viewModel)
{
await viewModel.LoginWithBrowserAsync(this);
}
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
Loaded += OnLoaded;
}
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (DataContext is AuthenticateCloudViewModel viewModel)
{
await viewModel.LoginWithBrowserAsync(this);
}
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
private string? _codeVerifier;
private string? _state;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
throw new Exception("Token or refresh token not found");
if (response.IsSuccessful && !string.IsNullOrWhiteSpace(response.Content))
{
JsonNode data = JsonSerializer.Deserialize<JsonNode>(response.Content)!;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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");

Copilot uses AI. Check for mistakes.
Comment on lines 109 to 112
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);
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
public AuthenticateCloudView()
{
InitializeComponent();
Loaded += OnLoaded;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

var restClient =
new RestClient(_httpService.HttpClient, new RestClientOptions("https://cloud.one-ware.com"));
RestClient restClient = new (_httpService.HttpClient);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Suggested change
RestClient restClient = new (_httpService.HttpClient);
RestClient restClient = _httpService.GetRestClient();

Copilot uses AI. Check for mistakes.
@NPK111 NPK111 force-pushed the feature/AuthOverOpenIdConnect branch from acc6989 to 4328bee Compare March 4, 2026 16:52
@hendrikmennen hendrikmennen merged commit df251d1 into main Mar 5, 2026
1 check passed
@hendrikmennen hendrikmennen deleted the feature/AuthOverOpenIdConnect branch March 5, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants