secure_session_manager
A lightweight, secure, and highly scalable session management package for Flutter apps with zero performance overhead when optional features are disabled.
Features
- ๐ Secure Storage: Uses
flutter_secure_storagefor persisting tokens. - ๐ Performance: In-memory caching for sub-millisecond token access.
- ๐ Atomic Refresh: Automatic token refresh with a mutex to prevent multiple simultaneous refresh calls.
- ๐ฆ Request Queueing: Queues pending requests during an active token refresh.
- โณ Idle Timeout: Optional, event-driven idle detection (no polling).
- ๐ฑ App Lifecycle: Automatically validates session on app resume.
- ๐ Interceptors: Pre-built
Dioandhttpinterceptors for automatic token injection and 401 handling. - ๐งฉ JWT Support: Automatic expiry detection from JWT payloads.
- ๐ฆ Flexible Storage: Use
SecureStorageProvider(default) orSharedPreferencesProvider. - ๐งช Testable: Designed with dependency injection for easy unit testing.
- ๐ Zero Overhead: Optional features are only instantiated if enabled.
- ๐ก๏ธ Infinite Loop Prevention: Smart retry markers to prevent refresh loops.
- ๐ก Rich Event System: Comprehensive event streams for auth state changes.
- ๐ Safe Multipart Replay: Proper file stream handling for retried multipart requests.
- ๐ฏ Refreshing State: Track when token refresh is in progress.
Installation
Add to your pubspec.yaml:
dependencies:
secure_session_manager: ^1.2.0
Quick Start
1. Implement TokenProvider
Implement this interface to define how your app refreshes its authentication tokens.
class MyTokenProvider implements TokenProvider {
@override
Future<SessionToken> refreshToken(SessionToken currentToken) async {
// Call your API to refresh the token
final response = await http.post(
Uri.parse('https://your-api.com/refresh'),
body: {'refresh_token': currentToken.refreshToken},
);
final data = jsonDecode(response.body);
return SessionToken(
accessToken: data['access_token'],
refreshToken: data['refresh_token'],
expiresAt: DateTime.now().add(Duration(hours: 1)),
);
}
@override
Future<void> revokeToken(SessionToken token) async {
// Optional: Call your API to revoke the token on logout
await http.post(
Uri.parse('https://your-api.com/revoke'),
body: {'token': token.refreshToken},
);
}
}
2. Initialize SessionManager
Initialize the singleton at the start of your app.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SessionManager.instance.initialize(
tokenProvider: MyTokenProvider(),
// Optional: Configure idle timeout
idleTimeout: Duration(minutes: 15),
// Optional: Enable lifecycle observer (default: true)
enableLifecycleObserver: true,
);
runApp(MyApp());
}
3. Networking Interceptors
The package provides built-in interceptors for Dio and http that automatically:
- Inject the
Authorization: Bearer <token>header. - Intercept
401 Unauthorizederrors. - Trigger a token refresh and replay the failed request seamlessly.
For Dio
final dio = Dio();
dio.interceptors.add(SecureSessionDioInterceptor(dio));
For http package
final client = SecureSessionHttpInterceptor(http.Client());
// Use 'client' instead of 'http' for your requests
4. JWT Automatic Expiry
If your accessToken is a JWT, the package can automatically extract the exp claim.
final token = SessionToken(accessToken: "your.jwt.token");
print(token.expiresAt); // Automatically parsed if not provided
5. Manual Session Control
// Save session on login
await SessionManager.instance.setSession(token);
// Get current token (auto-refreshes if expired)
final token = await SessionManager.instance.getAccessToken();
// Get full session details
final session = await SessionManager.instance.getSession();
// Logout / Clear session
await SessionManager.instance.logout();
Advanced Features
Idle Detection
Option 1: Using IdleListener widget (Recommended)
await SessionManager.instance.initialize(
tokenProvider: MyTokenProvider(),
idleTimeout: Duration(minutes: 15),
);
// Wrap your app with IdleListener
return IdleListener(
child: MaterialApp(...),
);
Option 2: Manual setup
return Listener(
onPointerDown: (_) => SessionManager.instance.touch(),
behavior: HitTestBehavior.translucent,
child: MaterialApp(...),
);
Listen to Events
// All events combined
SessionManager.instance.onEvent.listen((event) {
print('Event: $event');
});
// Individual event streams
SessionManager.instance.onLogin.listen((token) => print('User logged in'));
SessionManager.instance.onLogout.listen((_) => print('User logged out'));
SessionManager.instance.onSessionExpired.listen((_) => print('Session expired'));
SessionManager.instance.onTokenRefreshed.listen((token) => print('Token refreshed'));
SessionManager.instance.onTokenRefreshStarted.listen((_) => print('Refresh started'));
SessionManager.instance.onTokenRefreshFailed.listen((error) => print('Refresh failed: $error'));
// Auth state changes
SessionManager.instance.authStateChanges.listen((state) {
switch (state) {
case AuthState.authenticated:
print('User is authenticated');
break;
case AuthState.unauthenticated:
print('User is not authenticated');
break;
case AuthState.refreshing:
print('Token is being refreshed');
break;
}
});
Cache Invalidation
For multi-isolate scenarios or external storage updates:
// Invalidate cache and re-read from storage
final freshToken = await SessionManager.instance.invalidateCache();
// Or with getSession
final freshSession = await SessionManager.instance.getSession(invalidateCache: true);
Reset SessionManager
For testing or complete reset:
await SessionManager.instance.reset();
// Now you can initialize it again with new configuration
Performance-First Design
- Lazy Initialization: Components like
IdleControllerandLifecycleObserverare only created if configured. - Mutex Locking: Ensures that even if 100 concurrent requests trigger a refresh, only one network call is made.
- In-Memory Cache: Tokens are cached in RAM after the first read from storage.
- No Polling: Idle detection is event-driven, no background polling.
Security Best Practices
- Always use SecureStorageProvider for production apps
- Never use SharedPreferencesProvider for sensitive tokens in production
- Implement token revocation in your TokenProvider
- Set appropriate idle timeouts for sensitive applications
- Always handle token refresh failures gracefully in your UI
Migration Guide
From 1.1.0 to 1.2.0
- Breaking Changes: None - fully backward compatible
- New Features:
- Added
IdleListenerwidget for easy idle detection setup - Added
TokenRefreshStartedEventandTokenRefreshFailedEvent - Added
AuthState.refreshingstate - Added
invalidateCache()method - Added
reset()method - Added
getSession(invalidateCache:)parameter - Added
hasCachedTokengetter - Added comprehensive exception types
- Added
Example
Check out the full example app in the repository.
โค๏ธ Maintained By
GreenLogix โ Flutter, Laravel & AI Development Agency
๐ https://greelogix.com
๐ฉ hello@greelogix.com
๐ฆ Other Open-Source Flutter Packages by GreenLogix
Check out our other packages like Launchify, Best Form Validator, and Flutter Telescope.
License
MIT