Flutter Passwordless Authentication

Pratiksha kadamPratiksha kadam
4 min read

What is Appwrite ?

Appwrite is an end-to-end backend service aiming to simplify web and mobile app development similar to Firebase. As an open-source alternative, Appwrite allows hosting the backend yourself instead of relying on a vendor-controlled platform.

Some key aspects of Appwrite:

Combines NoSQL, SQL databases to balance flexibility and structure.
Includes user management, multi-factor authentication, OAuth integrations, and email-sending capability.
- File storage system handles security, compression, virus scanning, and media metadata extraction.
- Customizable and extensible architecture allows modifying the codebase to meet specific needs.


Setting up Magic URL Authentication in Flutter with Appwrite

Step 1: Create or Use an Existing Flutter Project

In this tutorial we will learn how we can use Magic URL Authentication in our Flutter applications using Appwrite's Flutter SDK.

The same can be done with our Web SDK and Android SDK. As this feature needs to send an email, you must properly setup SMTP service in Appwrite.

flutter create magic_auth_flutter
cd magic_auth_flutter

Step 2: Android Configuration

To configure Android deep linking, open AndroidManifest.xml and add the following under <activity android:name= ".MainActivity">:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="appwrite-callback-auth-[PROJECT_ID]" android:host="magicurl_session" />
</intent-filter>

Replace [PROJECT_ID] with your Appwrite project ID.

Step 3: iOS Configuration

Step 3: iOS Configuration

For iOS, open Info.plist and add the following:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>appwrite-callback-auth-[PROJECT_ID]</string>
        </array>
    </dict>
</array>
<key>FlutterDeepLinkingEnabled</key>
<true/>

Again, replace [PROJECT_ID] with your actual project ID.

Step 4: Set Up an Appwrite Project

Go to Appwrite Cloud or use your self-hosted Appwrite setup. After logging in, create a new project and enable Magic URL authentication from the authentication settings.

Add the necessary platforms (iOS, Android, and Web) and configure the redirect domains.

Step 5: Add Appwrite SDK to Flutter

In your Flutter project, add the Appwrite SDK by running:

flutter pub add appwrite

This will include the latest version of Appwrite in your pubspec.yaml. Ensure the Appwrite version aligns with your server setup if you're using self-hosted Appwrite.

Step 6: Initialize Appwrite in Your Flutter App

In your Flutter app, initialize the Appwrite client. For better structure, create a separate file [appwrite_client.dart] for this:

import 'package:appwrite/appwrite.dart';

Client client = Client();

void initAppwrite() {
  client
    .setEndpoint('https://cloud.appwrite.io/v1') // Your Appwrite API Endpoint
    .setProject('auth-labs'); // Replace with your project ID
}

Call this initialization in your main.dart:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  initAppwrite();
  runApp(MyApp());
}

Step 7: Create the Login Screen with Magic URL

We’ll now implement a simple login screen where users enter their email to receive a Magic URL:

class LoginScreen extends StatelessWidget {
  final _emailController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: 'Email'),
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () => _sendMagicURL(_emailController.text),
              child: Text('Login'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _sendMagicURL(String email) async {
    final account = Account(client);
    try {
      await account.createMagicURLSession(
        email: email,
        url: 'appwrite-callback-auth-[PROJECT_ID]://magicurl_session',
      );
      // Handle success (e.g., show notification)
    } on AppwriteException catch (e) {
      print('Error: ${e.message}');
    }
  }
}

Replace [PROJECT_ID] with your project’s ID and configure the correct URL.

Step 8: Handle the Magic URL Callback

Once the user clicks the Magic URL, Appwrite will redirect them to your app with a user ID and a secret in the query parameters. Set up a route to handle this:

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/magicurl_session',
      builder: (context, state) {
        final userId = state.queryParams['userId']!;
        final secret = state.queryParams['secret']!;
        return MagicUrlSessionPage(userId: userId, secret: secret);
      },
    ),
  ],
);

Step 9: Authenticate the User

Create the MagicUrlSessionPage to complete the authentication:

class MagicUrlSessionPage extends StatelessWidget {
  final String userId;
  final String secret;

  MagicUrlSessionPage({required this.userId, required this.secret});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Authenticating...')),
      body: FutureBuilder(
        future: _authenticateUser(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else {
            return Center(child: Text('Authenticated successfully!'));
          }
        },
      ),
    );
  }

  Future<void> _authenticateUser() async {
    final account = Account(client);
    try {
      await account.updateMagicURLSession(userId: userId, secret: secret);
    } on AppwriteException catch (e) {
      throw Exception('Authentication failed: ${e.message}');
    }
  }
}

🔐 Login with a Magic URL

If you do want to handle the completion of magic URL session and redirection yourself, you need to build a web app that can handle the process. And you need to pass the URL of this web app in the URL parameter while calling createMagicURLSession.

Use the updateMagicURLSession() method and call it with the secret and userId values from the URL's query string.



const urlParams = new URLSearchParams(window.location.search);
const userId = urlParams.get('userId');
const secret = urlParams.get('secret');

let promise = appwrite.account.updateMagicURLSession(userId, secret);

promise.then(function (response) {
    console.log(response); // Success
}, function (error) {
    console.log(error); // Failure
});

If the updateMagicURLSession() succeeded, the user is now logged in.

Note that, Once clicked magic link is null and void.


Conclusion

With Appwrite’s Magic URL authentication, user can implemented a secure, passwordless login mechanism in Flutter. This setup improves user experience while enhancing security by avoiding password-based vulnerabilities. You can now integrate this flow into your apps and provide your users with a quick and secure login experience.

0
Subscribe to my newsletter

Read articles from Pratiksha kadam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pratiksha kadam
Pratiksha kadam