device_restricted_auth 0.1.0
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 #
- First Signup: Device is checked if available → Account created → Device bound permanently
- First Login on Android: Account verified → Android device bound permanently
- First Login on Desktop: Account verified → Desktop device bound permanently
- 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 logicAndroidDeviceIdProvider: Retrieves Android device IDsWindowsDeviceIdProvider: Retrieves Windows device IDsFirestoreDeviceRepository: Firestore implementation for device storage
Models #
DeviceBinding: Represents a device bound to a userDeviceMetadata: Additional device informationAuthResult: Result of authentication operationsValidationResult: Result of device validation
Exceptions #
DeviceAlreadyBoundException: Device is registered to another accountDeviceMismatchException: User trying to login from wrong deviceDeviceIdNotFoundException: Cannot retrieve device IDPlatformNotSupportedException: Platform not supported
Contributing #
This package is currently maintained for specific use cases. If you need additional features:
- Fork the repository
- Create a feature branch
- 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.