DEV Community

Cover image for Secure Login in Flutter Using Enums and Secured Storage
مصعب يوسف
مصعب يوسف

Posted on Originally published at Medium on

Secure Login in Flutter Using Enums and Secured Storage

Secure Login in Flutter Using Enums and Secured Storage

Managing authentication and user roles securely is a fundamental aspect of building reliable Flutter applications. By combining Dart Enums with secure storage solutions like SecuredStorageHandler, you can implement a robust and clean authentication mechanism. This allows you to support role-based access control and safe token handling while ensuring scalability and maintainability.

Required Packages

To follow along with this setup, be sure to include the following essential packages in your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  dio: ^5.0.0
  json_annotation: ^4.8.0
  flutter_secure_storage: ^9.0.0
  jwt_decode: ^0.3.1
  get_it: ^7.6.0
  injectable: ^2.3.0
  auto_route: ^7.8.3
  flutter_dotenv: ^5.1.0
Enter fullscreen mode Exit fullscreen mode

These packages provide HTTP handling, annotation-based serialization, token decoding, secure storage, and dependency injection — all vital for building secure login workflows in Flutter.

After adding these packages, don’t forget to run:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

Why Use Enums for Secure Login?

Enums offer a clean, type-safe way to represent user roles such as admin, customer, or partner. This removes the need to depend on error-prone string comparisons, reducing the likelihood of bugs that could result in unauthorized access or inconsistent behavior.

By centralizing role definitions in a Dart enum, you improve code readability, safety, and consistency throughout your app. IDEs also provide autocomplete and validation support, leading to better developer experience.

Example: RoleType Enum

enum RoleType {
  @JsonValue("master-admin")
  masterAdmin,
  @JsonValue("admin")
  admin,
  @JsonValue("customer")
  customer,
  @JsonValue("organizer")
  organizer,
  @JsonValue("partner")
  partner,
}


RoleType roleTypeFromString(String roleString) {
  switch (roleString) {
    case 'RoleType.masterAdmin':
      return RoleType.masterAdmin;
    case 'RoleType.admin':
      return RoleType.admin;
    case 'RoleType.customer':
      return RoleType.customer;
    case 'RoleType.organizer':
      return RoleType.organizer;
    case 'RoleType.partner':
      return RoleType.partner;
    default:
      throw ArgumentError('Invalid role string: \$roleString');
  }
}
Enter fullscreen mode Exit fullscreen mode

This pattern enforces role validation at compile time, ensuring you always work with valid, predefined roles across your application.

Securing Tokens with SecuredStorageHandler

Authentication tokens (such as JWTs) contain sensitive information and should never be stored in plain text. Using secure storage tools like flutter_secure_storage ensures your tokens are encrypted and protected against tampering or theft.

You can abstract your token management into a service class that handles reading, writing, and clearing session data securely.

Session Management with Secure Token Handling

@LazySingleton(as: SessionManager)
class AppSessionManager extends SessionManager {
  Dio dio;

AppSessionManager(this.dio);
  @override
  Future<bool> get tokenValid async {
    final token = await getToken();
    return token != null && !Jwt.isExpired(token);
  }
  @override
  void invalidateSession({Function? doAfter}) async {
    await SecuredStorageHandler.fromGlobalKey(
            storedKey: GlobalSecuredStorageKeys.currentUser)
        .remove();
    await SecuredStorageHandler.fromGlobalKey(
            storedKey: GlobalSecuredStorageKeys.token)
        .remove();
    if (doAfter != null) {
      doAfter();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

With this setup, you ensure that user data is securely stored and properly invalidated when logging out or switching accounts. It also supports automatic token expiration handling using the jwt_decode package.

Using Role and Token to Navigate After Splash Screen

Upon launching your app, the SplashScreen can check whether the user has a valid session and direct them to the appropriate route based on their role. This ensures a seamless login experience and enforces access control.

Future.delayed(const Duration(seconds: 3), () async {
  try {
    final sessionManager = getIt<SessionManager>();
    final activeSessionExists = await sessionManager.tokenValid;

if (activeSessionExists) {
      final role = dotenv.get('ROLE');
      final parsedRole = roleTypeFromString(role);
      if (parsedRole == RoleType.customer) {
        context.replaceRoute(const HomeRoute());
      } else if (parsedRole == RoleType.organizer || parsedRole == RoleType.admin) {
        context.replaceRoute(const AdminHomeRoute());
      } else {
        context.replaceRoute(const SignInRoute());
      }
    } else {
      context.replaceRoute(const SignInRoute());
    }
  } catch (e, stackTrace) {
    debugPrint("Error in session check: \$e");
    debugPrint(stackTrace.toString());
    context.replaceRoute(const SignInRoute());
  }
});
Enter fullscreen mode Exit fullscreen mode

You can also expand this logic to support additional roles or perform background tasks like fetching user data after validating the session.

Expanding Enum Use with Extensions and Localization

To make enums even more powerful, consider using Dart extensions. These allow you to attach methods or properties to enum values, such as displaying human-readable labels or localizing them using AppLocalizations.

extension RoleTypeExtension on RoleType {
  String get label {
    switch (this) {
      case RoleType.masterAdmin:
        return "Master Admin";
      case RoleType.admin:
        return "Administrator";
      case RoleType.customer:
        return "Customer";
      case RoleType.organizer:
        return "Organizer";
      case RoleType.partner:
        return "Partner";
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This approach centralizes role-specific logic and keeps your UI code clean.

Conclusion

Enums offer a clean and structured approach to managing user roles in Flutter applications. When integrated with encrypted secure storage and dependency injection, they form the foundation for scalable and secure login systems. By using enums like RoleType for role validation, leveraging flutter_secure_storage for token safety, and using DI with packages like get_it and injectable, you can build robust authentication flows that adapt as your app grows.

With proper session management, route redirection based on user roles, and expandable enum logic through extensions and localization, your Flutter app will be secure, maintainable, and future-ready.

Top comments (0)