Skip to content

Comments

Flutter windows desktop support#656

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

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

Conversation

@NandanPrabhu
Copy link
Contributor

@NandanPrabhu NandanPrabhu commented Sep 8, 2025

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

📋 Changes

This PR adds native Windows desktop support to the auth0_flutter SDK, enabling Auth0 Universal Login on Flutter Windows apps using the OAuth 2.0 Authorization Code Flow with PKCE. The implementation is a C++ Flutter plugin that integrates with the existing platform-interface layer without modifying the mobile (iOS/Android) code paths.


New: WindowsWebAuthentication class

A dedicated Windows authentication class exposed via Auth0.windowsWebAuthentication(). Unlike the mobile WebAuthentication class, this:

  • Requires redirectUrl explicitly (no platform default exists on Windows)
  • Does not auto-store credentials in CredentialsManager (no Keychain/Keystore on Windows)
  • Exposes a parameters map for Windows-specific configuration
final auth0 = Auth0('DOMAIN', 'CLIENT_ID');

// Simple: Auth0 redirects directly to the app via custom scheme
final credentials = await auth0.windowsWebAuthentication().login(
  redirectUrl: 'auth0flutter://callback',
);

// Intermediary server: HTTPS endpoint forwards to auth0flutter://callback
final credentials = await auth0.windowsWebAuthentication().login(
  redirectUrl: 'https://your-server.com/callback',
  parameters: {'authTimeoutSeconds': '300'},
);

authTimeoutSeconds (default '180'): How long the plugin polls for the OAuth callback before returning USER_CANCELLED. Increase for slow MFA flows; decrease for fast failure in tests.

Regardless of what redirectUrl is registered with Auth0, the Windows plugin always wakes the app by listening on the auth0flutter://callback custom scheme. When using an intermediary server, the server must forward the callback to auth0flutter://callback?code=…&state=….


New: Windows C++ plugin (auth0_flutter/windows/)

Component Purpose
login_web_auth_request_handler.cpp Orchestrates the full OAuth 2.0 + PKCE login flow
logout_web_auth_request_handler.cpp Builds and opens the Auth0 logout URL
oauth_helpers.cpp PKCE code verifier/challenge generation; auth0flutter:// callback polling
auth0_client.cpp HTTP token exchange via cpprestsdk
id_token_validator.cpp OpenID Connect ID token validation
id_token_signature_validator.cpp RS256 signature verification via OpenSSL
jwt_util.cpp JWT header/payload decoding
token_decoder.cpp Maps token exchange response → Credentials struct
user_profile.cpp / user_identity.cpp OIDC claims → UserProfile struct
time_util.cpp ISO 8601 / RFC 3339 timestamp parsing
url_utils.cpp RFC 3986 URL parsing and query parameter extraction
windows_utils.cpp WideToUtf8, BringFlutterWindowToFront

Authentication flow:

  1. Generate PKCE code_verifier (32 cryptographically random bytes via RAND_bytes) and code_challenge (SHA-256 via OpenSSL, base64-URL encoded)
  2. Generate a random state value for CSRF protection
  3. Build the Auth0 /authorize URL with all parameters RFC 3986-encoded
  4. Open the URL in the system default browser via ShellExecuteA
  5. Poll PLUGIN_STARTUP_URL environment variable (set by Windows when the app is launched via the auth0flutter:// custom scheme) every 200 ms until the callback arrives or the timeout expires
  6. Validate state to prevent CSRF; extract code
  7. Exchange code + code_verifier for tokens via POST to /oauth/token
  8. Validate the ID token (issuer, audience, expiry, auth_time, nonce, RS256 signature)
  9. Bring the Flutter window back to the foreground and return credentials

Key design decisions:

  • The app always listens on auth0flutter://callback (kDefaultRedirectUri). The redirectUrl sent to Auth0 may differ (e.g. an HTTPS intermediary server URL); that server is responsible for forwarding to auth0flutter://callback?code=…&state=….
  • Internal plugin parameters (authTimeoutSeconds) are consumed before building the authorize URL and are not appended to it.
  • The authentication flow runs on a background std::thread to avoid blocking the Flutter UI thread.
  • openid scope is always enforced even when not explicitly passed, as required by OpenID Connect.
  • All URL parameters are RFC 3986 percent-encoded to prevent injection and handle special characters in values such as redirect URIs and scopes.

New: vcpkg.json dependency manifest

Manages C++ dependencies via vcpkg, integrating automatically with CMake through the vcpkg toolchain file set by Flutter during flutter build windows:

Library Purpose
cpprestsdk Async HTTP client and JSON for token exchange
openssl RAND_bytes (PKCE entropy), SHA-256 (code challenge), RS256 signature verification, TLS
boost-system / boost-date-time / boost-regex Transitive cpprestsdk dependencies

New: Unit tests (Google Test, auth0_flutter/windows/test/)

Test file Coverage
oauth_helpers_test.cpp Base64-URL encoding, code verifier/challenge generation, callback timeout behaviour
id_token_validator_test.cpp Issuer, audience, expiry, auth_time, nonce, leeway validation
jwt_util_test.cpp JWT splitting, header/payload decoding
time_util_test.cpp ISO 8601 and RFC 3339 timestamp parsing
token_decoder_test.cpp Token response → Credentials mapping
url_utils_test.cpp URL parsing, query string extraction, RFC 3986 encoding
user_identity_test.cpp Identity claims extraction
user_profile_test.cpp User profile claims mapping
windows_utils_test.cpp WideToUtf8 wide-to-UTF-8 conversion

Tests are compiled as a separate auth0_flutter_tests executable and registered with CTest, enabled via -DAUTH0_FLUTTER_ENABLE_TESTS=ON.


New: CI pipeline (.github/workflows/main.yml)

Added a windows-tests job that installs vcpkg dependencies, builds the test executable with CMake, and runs all C++ unit tests via CTest on windows-latest.


Changed: Auth0 class (lib/auth0_flutter.dart)

  • Added windowsWebAuthentication({String? scheme}) factory method
  • Exported WindowsWebAuthentication from the library

Changed: web_authentication_test.dart

  • Removed incorrect assertions that expected Windows-specific parameters (appCallbackUrl, authTimeoutSeconds) to appear in mobile WebAuthentication calls — the mobile class passes parameters through unchanged
  • Fixed test 'passes custom authTimeoutSeconds to parameters''passes custom parameters to platform': the original test called .login() with no parameters but asserted a '300' timeout value; the body now actually passes and verifies explicit custom parameters

Changed: README

  • Added Windows setup section documenting vcpkg prerequisite, auth0flutter:// custom URL scheme registration, Allowed Callback URL configuration, and both redirect patterns (direct vs. intermediary server)

📎 References


🎯 Testing

Automated — C++ unit tests (Windows)

cd auth0_flutter/windows
cmake -B build -S . \
  -DCMAKE_TOOLCHAIN_FILE=<vcpkg-root>/scripts/buildsystems/vcpkg.cmake \
  -DAUTH0_FLUTTER_ENABLE_TESTS=ON
cmake --build build
cd build && ctest --output-on-failure

All 9 test suites pass.

Automated — Flutter unit tests (any platform)

cd auth0_flutter
flutter test test/mobile/web_authentication_test.dart
# 34/34 tests pass

Manual — end-to-end on Windows

Prerequisites:

  1. Register auth0flutter as a custom URL scheme pointing to your app executable (via installer or registry)
  2. Add auth0flutter://callback to Allowed Callback URLs in the Auth0 dashboard
cd auth0_flutter/example
flutter run -d windows
  • Tap Log in → system default browser opens Auth0 Universal Login
  • Authenticate; browser redirects to auth0flutter://callback?code=…&state=…
  • Windows launches the app via the registered custom scheme; the Flutter window comes to the foreground and displays the returned credentials

To test the intermediary server pattern, point redirectUrl at an HTTPS endpoint that reads the code and state query parameters and responds with a redirect to auth0flutter://callback?code=…&state=….

// - 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 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed appCallbackUrl altogether. redirect url will solve the purpose we need for waking up the app on redirection from browser

'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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done. added auth0flutter://callback as default redirectUrl

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added both claim and signature validation as well

* @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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done added validation of signature as well id_token_signature_validator.cpp file

}

// 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

* main: (79 commits)
  build(deps): bump aws-actions/configure-aws-credentials from 5.1.1 to 6.0.0 in /.github/actions/rl-scanner (#744)
  chore:  updated the RL wrapper installation path (#746)
  Release af-v2.0.0-beta.3 (#743)
  Release afpi-v2.0.0-beta.3 (#742)
  Release afpi-v2.0.0-beta.3 (#741)
  build(deps): bump ruby/setup-ruby from 1.286.0 to 1.288.0 in /.github/actions/setup-darwin (#740)
  CredentialsManager user info/ID token contents accessible via flutter SDK (#607)
  CI cleanup
  Avoid running always failing smoke tests for iOS
  make PR runs  with release workflow that triggers flutter analyze
  udpate pubspec
  udpate podspecs
  Release af-v2.0.0-beta.2
  pubspec update for beta release
  Release afpi-v2.0.0-beta.2
  fix flutter analyse errors
  build(deps): bump ruby/setup-ruby from 1.285.0 to 1.286.0 in /.github/actions/setup-darwin (#728)
  build(deps-dev): bump lodash from 4.17.21 to 4.17.23 in /appium-test (#729)
  chore:Fixed errors from flutter analyze (#727)
  feat: Add allowedBrowsers parameter to logout API [SDK-724] (#726)
  ...
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.

5 participants