device_restricted_auth 0.1.0 copy "device_restricted_auth: ^0.1.0" to clipboard
device_restricted_auth: ^0.1.0 copied to clipboard

Device-based login restriction for Flutter apps with Firebase (Android + Windows Desktop)

Device Restricted Auth #

A Flutter package that enforces device-based login restrictions using Firebase Authentication and Firestore. Perfect for apps that need to limit account access to specific devices.

What is Device Restricted Auth? #

This package permanently binds user accounts to specific devices, preventing account sharing across multiple devices. Once a user signs up or logs in from a device, that account becomes permanently linked to that device's hardware ID.

Why Use This Package? #

Use Cases:

  • Streaming Apps: Prevent password sharing (like Netflix's device limits)
  • Premium Apps: Enforce "1 device per license" policies
  • Enterprise Apps: Restrict corporate accounts to company-owned devices
  • Educational Apps: Ensure students use their own devices for exams
  • Private Tools: Limit access to authorized devices only

Key Benefits:

  • Hardware-level device identification (not spoofable by users)
  • Server-side enforcement via Firestore security rules
  • Automatic device binding on first login
  • Clear error messages for device mismatches

Features #

  • Permanent Device Binding: Each account is bound to 1 Android + 1 Desktop device
  • Hardware-Level Security: Uses platform-specific hardware identifiers
  • Firebase Integration: Works seamlessly with Firebase Auth and Firestore
  • Cross-Platform: Android and Windows Desktop support
  • Policy-Based: Configurable device binding policies
  • Type-Safe: Full Dart type safety with custom exceptions

Supported Platforms #

Platform Support Device ID Source
Android ✅ Yes androidId from device_info_plus
Windows ✅ Yes Windows device ID from device_info_plus
iOS ❌ No Not implemented
macOS ❌ No Not implemented
Linux ❌ No Not implemented
Web ❌ No Not supported (no hardware ID)

Installation #

Add this to your pubspec.yaml:

dependencies:
  device_restricted_auth: ^0.1.0
  firebase_core: ^3.8.0
  firebase_auth: ^5.3.3
  cloud_firestore: ^5.5.0

Then run:

flutter pub get

Firebase Setup #

1. Initialize Firebase #

Make sure Firebase is initialized in your app:

import 'package:firebase_core/firebase_core.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

2. Firestore Collections #

This package requires two Firestore collections:

users/{userId} - User profile data:

{
  "email": "user@example.com",
  "username": "username",
  "name": "User Name",
  "createdAt": Timestamp
}

user_devices/{userId} - Device bindings:

{
  "android": {
    "deviceId": "abc123...",
    "boundAt": Timestamp,
    "lastActive": Timestamp,
    "isPermanent": true
  },
  "desktop": {
    "deviceId": "xyz789...",
    "boundAt": Timestamp,
    "lastActive": Timestamp,
    "isPermanent": true
  }
}

3. Firestore Security Rules (Important!) #

Add these rules to enforce device restrictions server-side:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // User devices collection
    match /user_devices/{userId} {
      allow read: if request.auth != null && request.auth.uid == userId;
      allow write: if request.auth != null && request.auth.uid == userId;
    }

    // Users collection
    match /users/{userId} {
      allow read: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null && request.auth.uid == userId;
      allow update: if request.auth != null && request.auth.uid == userId;
    }
  }
}

Usage #

Step 1: Initialize the Coordinator #

Create the coordinator with platform-specific device ID provider:

import 'dart:io';
import 'package:device_restricted_auth/device_restricted_auth.dart';

// Choose provider based on platform
final deviceIdProvider = Platform.isAndroid
    ? AndroidDeviceIdProvider()
    : WindowsDeviceIdProvider();

// Create repository
final deviceRepository = FirestoreDeviceRepository();

// Create coordinator
final coordinator = DeviceRestrictionCoordinator(
  deviceRepository: deviceRepository,
  deviceIdProvider: deviceIdProvider,
);

Step 2: During User Signup #

import 'package:firebase_auth/firebase_auth.dart';

Future<void> signUp(String email, String password) async {
  try {
    // 1. Check if device is available for new account
    await coordinator.verifyDeviceForSignup();

    // 2. Create Firebase account
    final credential = await FirebaseAuth.instance
        .createUserWithEmailAndPassword(
      email: email,
      password: password,
    );

    // 3. Initialize device document in Firestore
    await coordinator.initializeDeviceDocument(credential.user!.uid);

    // 4. Bind current device to this account
    await coordinator.verifyAndBindDevice(credential.user!.uid);

    print('✅ Signup successful! Device bound.');

  } on DeviceAlreadyBoundException catch (e) {
    // Device is already registered to another account
    print('Error: ${e.message}');
    // Show error to user: "This device is already linked to another account"

  } catch (e) {
    print('Signup failed: $e');
  }
}

Step 3: During User Login #

Future<void> login(String email, String password) async {
  try {
    // 1. Sign in with Firebase
    final credential = await FirebaseAuth.instance
        .signInWithEmailAndPassword(
      email: email,
      password: password,
    );

    // 2. Verify device binding (binds on first login, validates on subsequent)
    await coordinator.verifyAndBindDevice(credential.user!.uid);

    print('✅ Login successful!');

  } on DeviceMismatchException catch (e) {
    // User is trying to login from a different device
    print('Error: ${e.message}');
    // Show error: "This account is bound to another device"
    await FirebaseAuth.instance.signOut(); // Sign out immediately

  } catch (e) {
    print('Login failed: $e');
  }
}

Error Handling #

The package throws specific exceptions for different scenarios:

DeviceAlreadyBoundException #

Thrown during signup when the device is already registered to another account.

try {
  await coordinator.verifyDeviceForSignup();
} on DeviceAlreadyBoundException catch (e) {
  // Show: "This device is already linked to another account"
  // Action: Ask user to login instead of signup
}

DeviceMismatchException #

Thrown during login when the user tries to access their account from a different device.

try {
  await coordinator.verifyAndBindDevice(userId);
} on DeviceMismatchException catch (e) {
  // Show: "This account is bound to another device"
  // Action: Sign out and prevent access
  await FirebaseAuth.instance.signOut();
}

DeviceIdNotFoundException #

Thrown when the device ID cannot be retrieved from the platform.

try {
  final deviceId = await deviceIdProvider.getDeviceId();
} on DeviceIdNotFoundException catch (e) {
  // Show: "Unable to identify this device"
  // Action: Check device permissions or platform support
}

PlatformNotSupportedException #

Thrown when using a provider on an unsupported platform.

try {
  final provider = AndroidDeviceIdProvider();
  await provider.getDeviceId(); // On iOS/Windows
} on PlatformNotSupportedException catch (e) {
  // Show: "This platform is not supported"
}

How Device Binding Works #

  1. First Signup: Device is checked if available → Account created → Device bound permanently
  2. First Login on Android: Account verified → Android device bound permanently
  3. First Login on Desktop: Account verified → Desktop device bound permanently
  4. Subsequent Logins: Device ID is verified against bound device → Access granted/denied

Important Notes:

  • Each account can have 1 Android device + 1 Desktop device
  • Device bindings are permanent and cannot be changed
  • Users cannot login from a different device once bound
  • Device IDs are hardware-based and survive app reinstalls

Limitations #

  • No Device Replacement: Once bound, devices cannot be changed (by design)
  • Platform-Specific: Only Android and Windows Desktop are supported
  • Requires Firebase: This package is tightly coupled with Firebase services
  • No Web Support: Web browsers don't have reliable hardware IDs
  • Privacy Consideration: Device IDs are stored in Firestore (ensure compliance with privacy laws)

Example App #

See the example directory for a complete working app demonstrating:

  • Firebase initialization
  • Signup with device verification
  • Login with device binding
  • Error handling for all scenarios

API Reference #

Core Classes #

  • DeviceRestrictionCoordinator: Main orchestrator for device restriction logic
  • AndroidDeviceIdProvider: Retrieves Android device IDs
  • WindowsDeviceIdProvider: Retrieves Windows device IDs
  • FirestoreDeviceRepository: Firestore implementation for device storage

Models #

  • DeviceBinding: Represents a device bound to a user
  • DeviceMetadata: Additional device information
  • AuthResult: Result of authentication operations
  • ValidationResult: Result of device validation

Exceptions #

  • DeviceAlreadyBoundException: Device is registered to another account
  • DeviceMismatchException: User trying to login from wrong device
  • DeviceIdNotFoundException: Cannot retrieve device ID
  • PlatformNotSupportedException: Platform not supported

Contributing #

This package is currently maintained for specific use cases. If you need additional features:

  1. Fork the repository
  2. Create a feature branch
  3. Submit a pull request with clear documentation

License #

MIT License - see LICENSE file for details.

Support #

For issues, questions, or feature requests, please visit the GitHub repository.

1
likes
150
points
90
downloads

Publisher

unverified uploader

Weekly Downloads

Device-based login restriction for Flutter apps with Firebase (Android + Windows Desktop)

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

cloud_firestore, device_info_plus, firebase_auth, firebase_core, flutter

More

Packages that depend on device_restricted_auth