flutter_secure_auth 1.0.0
flutter_secure_auth: ^1.0.0 copied to clipboard
A secure, lightweight authentication package for Flutter (REST + OAuth2 PKCE + token refresh) with secure local storage.
flutter_secure_auth 🔒 #
A secure, lightweight, and offline-resilient authentication client for Flutter applications. It seamlessly handles OAuth2 PKCE authorization flows, REST sign-ins, secure token storage, and automatic credential refreshes with full multi-platform support (including WebAssembly/WASM).
✨ Key Features #
- 🔔 Reactive Auth State:
authStateChangesstream for building reactive UIs withStreamBuilder— just like Firebase Auth. - 🌐 Drop-in HTTP Interceptor:
AuthInterceptorauto-injectsAuthorizationheaders and handles401retries transparently. - 🏭 Quick Setup Factory:
AuthService.create('https://api.example.com')— zero boilerplate for standard OAuth2 APIs. - 🔐 RFC 7636 OAuth2 PKCE Compliance: Safe code exchange using strictly compliant unreserved characters.
- ⚡ 100% WASM Compatible: Fully migrated web storage to
package:web/dart:js_interopfor next-gen Flutter Web builds. - 🔌 Offline Resiliency: Smart refresh scheduling. Won't prematurely wipe credentials during network loss or 5xx server issues — only logs out on definitive auth errors (400/401/403).
- 🩹 Self-Healing Storage: Corrupt secure storage inputs are silently discarded and healed, returning a clean login state rather than crashing.
- 🏷️ Typed Error Codes:
AuthErrorCodeenum makes catch blocks readable and avoids magic number comparisons. - 📋
copyWith()on Tokens: Update specific token fields without boilerplate. - 🔒 Biometric / Guard Hook:
readGuardcallback onTokenStoragefor plugging in Face ID / Touch ID before token retrieval.
🚀 Installation #
Add the package to your pubspec.yaml:
dependencies:
flutter_secure_auth: ^1.1.0
Then run:
flutter pub get
🧩 Usage Guide #
1. Initialize the Auth Service #
Option A — Quick factory (recommended for standard REST APIs):
import 'package:flutter_secure_auth/flutter_secure_auth.dart';
// Automatically builds tokenEndpoint and revokeEndpoint from a base URL
final authService = AuthService.create('https://api.yourdomain.com');
Option B — Full manual configuration:
final authService = AuthService(
tokenEndpoint: Uri.parse('https://api.yourdomain.com/oauth/token'),
revokeEndpoint: Uri.parse('https://api.yourdomain.com/oauth/revoke'),
);
2. Reactive Auth State (New in v1.1.0) #
Listen to authStateChanges to rebuild your UI whenever the session changes:
StreamBuilder<AuthState>(
stream: authService.authStateChanges,
builder: (context, snapshot) {
final state = snapshot.data ?? const AuthState.unauthenticated();
if (state.isAuthenticated) {
return HomeScreen();
}
return LoginScreen();
},
);
Or check auth status programmatically:
if (await authService.isAuthenticated) {
navigateTo(HomeScreen());
}
3. Sign In with Password #
try {
final tokens = await authService.signInWithPassword(
endpoint: Uri.parse('https://api.yourdomain.com/auth/login'),
username: 'user@example.com',
password: 'securePassword123',
);
print('Signed in! Token expires in: ${tokens.timeToExpiry.inMinutes} minutes');
} on AuthException catch (e) {
if (e.errorCode == AuthErrorCode.unauthorized) {
showSnackBar('Invalid credentials. Please try again.');
} else if (e.errorCode == AuthErrorCode.serverError) {
showSnackBar('Server error. Please try later.');
}
}
4. OAuth2 PKCE Flow #
Generate a cryptographically secure verifier/challenge pair (conforming to RFC 7636) and initiate the authorization flow:
// 1. Create the PKCE pair
final pkce = createPkcePair(length: 64);
// 2. Build your authorization URL
final authUrl = Uri.parse('https://auth.yourdomain.com/authorize').replace(
queryParameters: {
'response_type': 'code',
'client_id': 'your-client-id',
'redirect_uri': 'com.yourapp:/oauthredirect',
'scope': 'openid profile offline_access',
'code_challenge': pkce.codeChallenge,
'code_challenge_method': 'S256',
'state': pkce.state,
},
);
// 3. After redirect & code capture, perform secure exchange:
final tokens = await authService.exchangeAuthorizationCode(
code: 'returned_auth_code_from_redirect',
codeVerifier: pkce.codeVerifier,
redirectUri: Uri.parse('com.yourapp:/oauthredirect'),
);
5. Send Authorized HTTP Requests (New: AuthInterceptor) #
Option A — Drop-in HTTP client (recommended):
import 'package:flutter_secure_auth/flutter_secure_auth.dart';
// Wrap your client once — all requests are automatically authorized
final client = AuthInterceptor(authService: authService);
// Authorization: Bearer <token> is injected automatically
// If token is expired it refreshes first; if 401 is received it retries once
final response = await client.get(
Uri.parse('https://api.yourdomain.com/profile'),
);
print(response.body);
// Close when done (e.g., in dispose())
client.close();
Option B — Manual per-request injection:
import 'package:http/http.dart' as http;
final rawRequest = http.Request('GET', Uri.parse('https://api.yourdomain.com/profile'));
final authorizedRequest = await authService.authorizedRequest(rawRequest);
final response = await authorizedRequest.send();
6. Typed Error Handling (New in v1.1.0) #
} on AuthException catch (e) {
switch (e.errorCode) {
case AuthErrorCode.unauthorized:
navigateToLogin();
case AuthErrorCode.forbidden:
showError('You do not have permission to perform this action.');
case AuthErrorCode.serverError:
showRetryButton();
case AuthErrorCode.networkError:
showOfflineBanner();
default:
showError(e.message);
}
}
7. Token copyWith() (New in v1.1.0) #
// Update a specific field without recreating the whole object
final refreshed = tokens.copyWith(
accessToken: 'newToken',
expiry: DateTime.now().add(const Duration(hours: 1)),
);
8. Biometric Guard on Token Retrieval #
Plug in Face ID, Touch ID, or any custom security check before tokens are read:
import 'package:local_auth/local_auth.dart';
final storage = TokenStorage(
readGuard: () async {
final localAuth = LocalAuthentication();
return await localAuth.authenticate(
localizedReason: 'Authenticate to access your account',
);
},
);
final authService = AuthService(
tokenEndpoint: Uri.parse('https://api.yourdomain.com/oauth/token'),
storage: storage,
);
9. Sign Out #
// Revokes refresh token server-side (if revokeEndpoint is set) and wipes local storage
await authService.signOut(revokeServerSide: true);
10. Dispose #
@override
void dispose() {
authService.dispose(); // cancels timers and closes authStateChanges stream
super.dispose();
}
🎮 Interactive Example App #
The package includes a full demo app illustrating:
- Reactive auth state with
StreamBuilder<AuthState> - Sign in / sign out flows
AuthInterceptorfor authorized API requests- PKCE code generation
- Typed error handling with
AuthErrorCode
To run the demo:
cd example
flutter run
📄 License #
This project is licensed under the MIT License - see the LICENSE file for details.