Social authentication in Flutter Desktop apps

Bart Selwesiuk
6 min readJan 25, 2023

--

At Buzz, we use Flutter as the foundation for our tech stack. It’s the optimal framework for us, for many reasons:

  • It has a powerful UI toolkit that allows us to build complex views and animations easily.
  • It is cross-platform which allows us to develop for multiple platforms such as MacOS and Windows at the same time.
  • It has excellent developer experience.

In this Flutter Craft blog series we’ll be sharing how we’re using Flutter at Buzz and the challenges that come with it on the Desktop platforms. This blog post has been co-authored by Rafael Ortiz Zableh and Bart Selwesiuk.

Social authentication

Social authentication is a way for users to log in to your application using their existing social accounts such as Google, Facebook, Twitter. This type of sign-in is considered to be more secure than managing your own authentication and the login process is generally faster and more convenient for users, as they do not need to create and remember a new set of credentials.

However, when it comes to social authentication in Flutter Desktop applications, the current support for popular authentication methods like Google and Facebook is limited. The google_sign_in package, for example, does not currently support Google authentication in Flutter Desktop, and the flutter_facebook_auth package offers limited support for Facebook authentication on MacOS, but no support for Windows.

One approach to building social authentication in Flutter Desktop is using the desktop_webview_auth package, which enables Firebase, Google and Twitter authentication on desktop platforms via webviews. However, the webview approach comes with some limitations, mainly, the user needs to log in to their social platform, even though they may already be logged in in their preferred (default) browser.

That’s why we decided to go with a more user-friendly approach. In this case, we open the sign-in flow in the user’s default browser so that they can choose the desired account from the list of already logged in accounts (e.g. Google accounts), which makes the authentication process more intuitive for the user.

In the example below, we are going to build Google authentication in Flutter Desktop which works with the user’s default browser instead of using a webview.

Create a redirect web page

Most social platforms rely on OAuth authentication to handle social sign-in and redirect URIs are a key part of that flow. As we’ll be using using the browser to launch authentication URIs, we need to create a web page to handle the redirect flow.

An example of a redirect web page.

To start, you need to create a simple HTML file, such as the one above. You can serve it locally to test the sign-in flow. The link to this page will be the redirect URI for your authentication. In the next steps, we will update it to handle the auth flow.

For production use, your redirect web page needs to be hosted online, but don’t worry, you can use tools like Github Pages, Netlify, Gitlab Pages, Vercel, or Firebase Hosting to host it for free.

Next, you will need to create a Google OAuth Client ID by navigating to the Google APIs Console → Credentials → Create Credentials → OAuth client ID. Choose “Web application” as your application type and provide the redirect URI that you have configured in the previous step.

Set up your Flutter app for the authentication process

Now, we will define a model for building the Google authentication URL.

class GoogleSignInArgs {
const GoogleSignInArgs({
required this.clientId,
required this.redirectUri,
required this.scope,
required this.responseType,
required this.accessType,
required this.prompt,
required this.state,
});

/// The OAuth client id of your Google app.
///
/// See https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid.
final String clientId;

// The authentication scopes.
///
/// See https://developers.google.com/identity/protocols/oauth2/scopes.
final String scope;

/// The authentication response types (e.g. token, id_token, code).
final String responseType;

/// A list of prompts to present the user.
final String prompt;

/// Cryptographic nonce used to prevent replay attacks.
///
/// It may be required when using an id_token as a response type.
/// The response from Google should include the same nonce inside the id_token.
final String nonce;

/// The URL where the user will be redirected after
/// completing the authentication in the browser.
final String redirectUri;
}

A nonce may be required in the authentication flow. Here’s an example of how to generate a random cryptographically secure nonce.

String generateNonce({int length = 32}) {
const characters =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();

return List.generate(
length,
(_) => characters[random.nextInt(characters.length)],
).join();
}

Next, we will be using the url_launcher package to launch the Google authentication URL in the user's default browser. This can be done by using the launch method to open the URL.

final signInArgs = GoogleSignInArgs(
// The OAuth client id of your Google app.
clientId: 'your-client-id',
// The URI to your redirect web page.
redirectUri: 'your-redirect-uri',
// Basic scopes to retrieve the user's email and profile details.
scope: [
'<https://www.googleapis.com/auth/userinfo.email>',
'<https://www.googleapis.com/auth/userinfo.profile>',
].join(' '),
responseType: 'token id_token',
// Prompts the user for consent and to select an account.
prompt: 'select_account consent',
// Random secure nonce to be checked after the sign-in flow completes.
nonce: generateNonce(),
);

final authUri = Uri(
scheme: 'https',
host: 'accounts.google.com',
path: '/o/oauth2/v2/auth',
queryParameters: {
'scope': signInArgs.scope,
'response_type': signInArgs.responseType,
'redirect_uri': signInArgs.redirectUri,
'client_id': signInArgs.clientId,
'nonce': signInArgs.nonce,
'prompt': signInArgs.prompt,
},
);

await launch(authUri);

Handle the redirect with app links

At this step, the authentication takes you to the redirect URI but does not navigate to your Flutter Desktop app yet. To redirect back to the app after a successful authentication, we will set up app links. This can be done on both Windows and MacOS by using the app_links package. This package allows you to handle deep links in your Flutter app, so that when the user is redirected back to your app after authentication, the sign-in process can be completed. Don’t forget to follow the setup of this package to register your URL scheme. Once the package is configured, we can listen for incoming app links in the following way.

AppLinks().uriLinkStream().listen((uri) async {
if (uri.scheme != 'example-scheme') return;
if (uri.path != 'app.example.com') return;

final authenticationIdToken = uri.queryParameters['id_token'];
final authenticationAccessToken = uri.queryParameters['access_token'];

// Authentication completed, you may use the access token to
// access user-specific data from Google.
//
// At this step, you may want to verify that the nonce
// from the id token matches the one you generated previously.
//
// Example:
// Signing-in with Firebase Auth credentials using the retrieved
// id and access tokens.
final credential = GoogleAuthProvider.credential(
idToken: authenticationIdToken,
accessToken: authenticationAccessToken,
);

await _auth.signInWithCredential(credential);
});

The last part is configuring the redirect web page to navigate to your Flutter Desktop app after a successful authentication. When redirected from Google, the web page will include authentication credentials in the URI parameters. Include the following JavaScript code in your redirect web page to navigate to your Flutter Desktop app as soon as the user opens the page.

function findUrlParameter(parameterName) {
let result = null;
const searchParams = new URLSearchParams(location.search);
if (searchParams.has(parameterName)) {
result = searchParams.get(parameterName);
}
return result;
}

function redirectToDesktop() {
const idToken = findUrlParameter("id_token");
const accessToken = findUrlParameter("access_token");
const appLinkScheme = "example-scheme";
const appLinkAuthority = "app.example.com";
const appLinkUrl = `${appLinkScheme}://${appLinkAuthority}/google-auth?id_token=${idToken}&access_token=${accessToken}`;
setTimeout(() => {
window.location.href = url;
}, 100);
}

redirectToDesktop();

That’s all! 🎉

To recap, in this approach your Flutter Desktop app launches a Google authentication URL which then navigates you to your redirect web page. The redirect web page retrieves authentication parameters from the URI and passes them to your app (by using app links) where the social authentication may be completed.

Implementations details (e.g. authentication parameters) may vary when authenticating with other social platforms, but the authentication flow would remain the same.

In conclusion, while social authentication in Flutter Desktop applications is currently limited, there are approaches that can be taken to authenticate using popular platforms like Google and Facebook. Webviews are fairly straightforward to use, but come with the limitation of having to login to the social platform each time you authenticate. On the other hand, building a solution with url_launcher and app_links packages makes the authentication process more intuitive for the user, but may be a bit more complex to implement.

--

--