Skip to content

Comments

Flutter windows desktop support#656

Open
NandanPrabhu wants to merge 72 commits intomainfrom
SDK-6071
Open

Flutter windows desktop support#656
NandanPrabhu wants to merge 72 commits intomainfrom
SDK-6071

Conversation

@NandanPrabhu
Copy link
Contributor

@NandanPrabhu NandanPrabhu commented Sep 8, 2025

Changes

This PR consists of changes with respect to adding flutter windows desktop support.

Feature implemented PKCE flow end to end
Right now the flutter channels supported are webauthlogin and webauthlogout
CredentialsManager would be supported in another PR
Dependency management for windows is done via CMakeLists.txt file. Under the hood flutter provides C++ application as wrapper over windows

@NandanPrabhu NandanPrabhu marked this pull request as ready for review January 8, 2026 07:37
@NandanPrabhu NandanPrabhu requested a review from a team as a code owner January 8, 2026 07:37
@NandanPrabhu NandanPrabhu changed the title Windows desktop support Flutter windows desktop support Jan 8, 2026
NandanPrabhu and others added 4 commits January 8, 2026 22:39
Copy link

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

Copilot reviewed 55 out of 62 changed files in this pull request and generated 24 comments.

Files not reviewed (4)
  • .idea/.gitignore: Language not supported
  • .idea/auth0-flutter.iml: Language not supported
  • .idea/modules.xml: Language not supported
  • .idea/vcs.xml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 133 to 135
if (!Platform.isWindows) {
await _credentialsManager?.storeCredentials(credentials);
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Platform.isWindows is used to gate credential storage/clearing, but dart:io isn't available on web. While this file is under src/mobile, it can still be analyzed as part of the package on web builds. Consider using kIsWeb guard or conditional imports so this remains analyzer-safe for web consumers.

Copilot uses AI. Check for mistakes.
Comment on lines 107 to 111
// Merge custom callback parameters into the parameters map for Windows
final mergedParameters = <String, String>{
...parameters,
'actualCallbackUrl': 'auth0flutter://callback',
};
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

In login(), actualCallbackUrl is merged into parameters unconditionally. That means iOS/Android/macOS will also send actualCallbackUrl=auth0flutter://callback to Auth0 as a custom authorize query parameter, which is not part of the public API and could break Rules/Actions or be rejected by some tenants. Please only inject this parameter on Windows (or keep it internal to the Windows implementation rather than passing it via parameters).

Suggested change
// Merge custom callback parameters into the parameters map for Windows
final mergedParameters = <String, String>{
...parameters,
'actualCallbackUrl': 'auth0flutter://callback',
};
// Merge custom callback parameters into the parameters map only on Windows
final Map<String, String> mergedParameters = Platform.isWindows
? <String, String>{
...parameters,
'actualCallbackUrl': 'auth0flutter://callback',
}
: parameters;

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be avoided if we use different class to handle Desktop callback while ensuring this doesn't cause issue with the existing code for mobile and web

Copy link
Contributor Author

Choose a reason for hiding this comment

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

have created a new windows_web_authentication class for the same

Comment on lines +193 to +198
auto parametersIt = arguments->find(flutter::EncodableValue("parameters"));
if (parametersIt == arguments->end())
{
result->Error("bad_args", "Missing 'parameters' key");
return;
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

parameters is treated as required (Missing 'parameters' key), but on the Dart side parameters defaults to {} and in general this option is typically optional. To keep API behavior consistent and avoid breaking callers using the platform interface directly, treat missing parameters as an empty map instead of returning an error.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree. Parameters are optional and shouldn't return error if not provided

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. will address the same.

Comment on lines +18 to +21
EXPECT_EQ(creds.accessToken, "test_access_token");
EXPECT_EQ(creds.tokenType, "Bearer");
EXPECT_FALSE(creds.idToken.empty()); // Empty, not uninitialized
EXPECT_FALSE(creds.refreshToken.has_value());
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

In the minimal token response (no id_token), Credentials.idToken remains the default empty string. This assertion currently expects it to be non-empty, so the test will fail. Please change it to EXPECT_TRUE(creds.idToken.empty()) (or update DecodeTokenResponse to set a non-empty sentinel, though that would be unusual).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
#include <gtest/gtest.h>

#include "oauth_helpers.h"
#include <regex>
#include <openssl/sha.h>

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This test uses std::set but doesn't include <set>. It may fail to compile depending on indirect includes. Please add #include <set>.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +8
#include "jwt_util.h"

#include <algorithm>
#include <sstream>
#include <vector>
#include <windows.h>
#include <wincrypt.h>

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

jwt_util.cpp throws std::runtime_error but doesn't include <stdexcept>. This may fail to compile depending on transitive includes. Please add #include <stdexcept>.

Copilot uses AI. Check for mistakes.
Comment on lines 1 to 20
#include "auth0_client.h"

#include <cpprest/http_client.h>
#include <cpprest/json.h>

#include "token_decoder.h"
using namespace web;
using namespace web::http;
using namespace web::http::client;

static std::string GetJsonString(
const web::json::value &json,
const utility::string_t &key)
{
if (json.has_field(key) && json.at(key).is_string())
{
return utility::conversions::to_utf8string(json.at(key).as_string());
}
return {};
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

auth0_client.cpp throws std::runtime_error but doesn't include <stdexcept>. This may fail to compile depending on transitive includes. Please add the missing header.

Copilot uses AI. Check for mistakes.
std::string state = generateCodeVerifier(); // Reuse code verifier generation for random state

DebugPrint("codeVerifier = " + codeVerifier);
DebugPrint("codeChallenge = " + codeChallenge);

Choose a reason for hiding this comment

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

We should not log / print sensitive information.
Can you fix the following logs.

  DebugPrint("codeVerifier = " + codeVerifier);
  DebugPrint("codeChallenge = " + codeChallenge);
  DebugPrint("state = " + state);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes @nandan-bhat Debug print will removed at this point. Once beta is out. We could have better logging

Copy link
Contributor

Choose a reason for hiding this comment

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

we should remove it before check in

// Extract redirect URI (default: "auth0flutter://callback")
// Default to localhost HTTP callback for better user experience
// This displays a friendly "redirecting" page instead of leaving browser stuck
std::string redirectUri = "http://localhost:8080/callback";

Choose a reason for hiding this comment

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

Are we using http://localhost:8080/callback as fallback redirect URI. Is this something we followed in other mobile SDKs ?

Copy link
Contributor

@pmathew92 pmathew92 Feb 12, 2026

Choose a reason for hiding this comment

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

No, this is not something we follow in other mobile SDKs. The redirect URI are constructed using the scheme and domain for mobile.
In this case we have the default auth0flutter://callback and the intermediary redirect url if provided. So the http://localhost:8080/callback might not be required
@nandan-bhat @frederikprijck

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in case of flutter default redirect uri wont be present. We will throw an error if redirect uri is not provided by the user on flutter side

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you want to throw an error here ? redirect_uri is optional. If not provided by the user, we pass the auth0flutter://callback to the authorize URL. If they want to have an intermediate page, they can pass the redirect_uri with that URI

using flutter::EncodableList;

static bool IsCustomClaim(const std::string& key) {
return key.rfind("https://", 0) == 0;

Choose a reason for hiding this comment

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

Should we allow http:// ?

Copy link
Member

Choose a reason for hiding this comment

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

Should we reverse the check, and detect standard claims from a fixed list?

const auto *val = std::get_if<std::string>(&kv.second);
if (key && val)
{
additionalParams[*key] = *val;

Choose a reason for hiding this comment

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

Can you confirm if you are including actualCallbackUrl as-well here. If yes, it will show up in the payload (query params) of /authorize call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no @nandan-bhat we are not doing that. Actual callback url is the flutter app activation callback. So we are not passing it as part of authorize url

// Extract the actual callback URL to listen for (may differ from redirectUri sent to Auth0)
// This is used when an intermediary server receives the Auth0 redirect and forwards to the app
std::string actualCallbackUrl = redirectUri;
auto callbackUrlIt = parametersMap->find(flutter::EncodableValue("actualCallbackUrl"));
Copy link
Contributor

@pmathew92 pmathew92 Feb 12, 2026

Choose a reason for hiding this comment

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

actualCallbackUrl is not required. We have a single redirect_uri which should be passed by the customer if they wish to have the intermediary workaround. Else the default redirect uri auth0flutter://callback would be used

{
// Handle any errors during the authentication flow
result->Error("auth_failed", e.what());
}
Copy link
Contributor

@pmathew92 pmathew92 Feb 12, 2026

Choose a reason for hiding this comment

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

Are we handling the scenario if someone closes the authorization browser window between the flow ? Would that return a error like Browser closed to the application from the SDK ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes they will recieve timeout error

- Android: `SCHEME://YOUR_DOMAIN/android/YOUR_PACKAGE_NAME/callback`
- iOS: `https://YOUR_DOMAIN/ios/YOUR_BUNDLE_ID/callback,YOUR_BUNDLE_ID://YOUR_DOMAIN/ios/YOUR_BUNDLE_ID/callback`
- macOS: `https://YOUR_DOMAIN/macos/YOUR_BUNDLE_ID/callback,YOUR_BUNDLE_ID://YOUR_DOMAIN/macos/YOUR_BUNDLE_ID/callback`
- Windows: `https://YOUR_HOSTED_DOMAIN/callback` (or your custom callback URL on your intermediary server)
Copy link
Contributor

@pmathew92 pmathew92 Feb 12, 2026

Choose a reason for hiding this comment

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

Should we mention the callback url here as this is a workaround we suggest for users. This should be mentioned in the example code . For windows by default we have the flutterapp://calback uri defined

…entage url encoding to login and logout urls - id token validation - presence of openid scope - code refactoring
// - appCallbackUrl: Change if using custom scheme or intermediary server
// - authTimeoutSeconds: Change if users need more/less time to authenticate
final Map<String, String> parameters = const {
'appCallbackUrl': 'auth0flutter://callback',
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the appCallbackUrl parameter at all. User can pass their intermediary url as part of the redirect_uri if required

Choose a reason for hiding this comment

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

I have a strong opinion about adding too many options to a public SDK. Once we add something like appCallbackUrl, customers will start using it. After that, we can’t remove it without a breaking change. And that also means we’re committing to maintaining it long-term.

I’m more aligned with what @pmathew92 suggested (using only redirect_uri). From my understanding, the developer can configure it like this:

redirect_uri: 'https://customers-domain.com/handle-redirect?target=flutterapp://callback'

Auth0 will then append ?code=34r43r.... and redirect the user to:

https://customers-domain.com/handle-redirect?target=flutterapp://callback?code=34r43r....

The intermediate page can extract both target and code, show a meaningful message to the user, and then redirect to:

flutterapp://callback?code=34r43r....

We can document a simple reference implementation of this intermediate page so it’s easy for customers to set up.

In this example we use target as the parameter name, but customers can choose any name they prefer. Because of this, I don’t see a strong need to introduce another public option like appCallbackUrl.

@frederikprijck, do you have any strong thoughts on this ?

'email',
'offline_access',
},
required final String redirectUrl,
Copy link
Contributor

Choose a reason for hiding this comment

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

redirectUrl can have the default value auth0flutter://callback

return {parts[0], parts[1], parts[2]};
}

web::json::value DecodeJwtPayload(const std::string &token)

Choose a reason for hiding this comment

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

This is just decoding the payload. It's still not verifying the JWS signature.

* @brief Implementation of ID Token validation
*/

#include "id_token_validator.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

This class is only validating the claims . We are not validating signature which could also be tempered. Please check with security guidelines or else we should get the public key from JWKS and validate the signature too

}

// Run authentication flow in background thread to avoid blocking UI
std::thread([
Copy link
Contributor

Choose a reason for hiding this comment

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

The entire login flow runs on a std::thread that is immediately .detach()ed. If the Flutter engine shuts down while the thread is running (e.g., app closes during authentication), accessing result is undefined behavior and will crash. There is also no way to cancel the authentication from the Dart side.

Current Code:
std::thread(result = std::move(result), ... mutable {
// ...
}).detach();

I would recommend to design a safe, non fire and forget solution using cpprestsdk’s pplx::task instead of detaching a thread. Check this : https://microsoft.github.io/cpprestsdk/classpplx_1_1cancellation__token__registration.html
This will:

  1. Avoid undefined behavior if the Flutter engine shuts down. (no crashes)
  2. Give structured async task management (no fire-and-forget)


late Auth0 auth0;
late WebAuthentication webAuth;
late WindowsWebAuthentication windowsWebAuth;
Copy link
Contributor

Choose a reason for hiding this comment

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

WindowsWebAuthentication is a parallel class rather than a platform extension. WindowsWebAuthentication class exposed via auth0.windowsWebAuthentication(). This is existing platofrm where auth0.webAuthentication() works cross-platform.

from dx point of view custoemrs will now need platform conditional code like below from now on

  if (Platform.isWindows) {
    await auth0.windowsWebAuthentication().login(redirectUrl: '...');
  } else {
    await auth0.webAuthentication().login();
  }

The separate class is fine as an internal implementation detail, but it should not be the public API That must be consistent

{

// Local URL encoding helper (kept here per design requirement)
static std::string UrlEncode(const std::string &str)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

logout has same code

*/
bool IsNetworkError() const
{
return statusCode_ == 0 || statusCode_ >= 500;
Copy link
Contributor

Choose a reason for hiding this comment

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

5xx eg 503 these are server errors canot be treated as network error. we can refer android / ios SDK for this

std::string state = generateCodeVerifier(); // Reuse code verifier generation for random state

DebugPrint("codeVerifier = " + codeVerifier);
DebugPrint("codeChallenge = " + codeChallenge);
Copy link
Contributor

Choose a reason for hiding this comment

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

we should remove it before check in

{
authUrl << "&audience=" << UrlEncode(audience);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

IdTokenValidationConfig has a nonce but we are not adding capability to add nonce here to authUrl. it is important to mitigate replay attacks . please add taht along witjh organisationid and other params

/// this class uses the Windows native implementation to perform interactions
/// with Universal Login.
///
/// It is not intended for you to instantiate this class yourself, as an
Copy link
Contributor

Choose a reason for hiding this comment

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

https://github.com/auth0/auth0-flutter/pull/656/changes#diff-b81bf8156bb640d7c5c9ce8bca261802f01cb6141aea422de2ddf21c99fba804R46

We are saying here it is not intended for you to instantiate this class yourself. but this is not enforce we still have a public constructor

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.

6 participants