How to forbid navigating back to Login page after successful login? - flutter

I have a question. How to achieve this behavior using auto_route's AutoRedirectGuard:
User opens Flutter app in browser but session expired and is redirected to the Login Page.
User successfully logs in.
User is redirected to Home page.
User cannot click the "back" button and see Login page again.
1, 2, 3 is working. I just can't get the 4th step right. This is the code of the AuthGuard:
class AuthGuard extends AutoRedirectGuard {
final AuthService authService;
bool isLoggedIn;
AuthGuard(this.authService, this.isLoggedIn) {
authService.authChanges.listen((isLoggedIn) {
if (this.isLoggedIn != isLoggedIn) {
this.isLoggedIn = isLoggedIn;
reevaluate(strategy: const ReevaluationStrategy.rePushFirstGuardedRouteAndUp());
}
});
}
#override
Future<void> onNavigation(NavigationResolver resolver, StackRouter router) async {
if (await authService.isLoggedIn()) {
resolver.next();
} else {
redirect(const LoginRoute(), resolver: resolver);
}
}
}

If the user is logged-in and navigating to login redirect them to home page. In onNavigation function.

This is covered in a similar post that you can read the OP here.
You can set up a gate in main.dart conditioned on authentication, and use Navigator.pushReplacement when leaving the AuthScreen.
MaterialApp(
...
home: isLoggedIn ? HomeScreen() : AuthScreen(),
...
);

You can add an after login callback in LoginPage
On your LoginPage, add onLoginCallback parameter
final void Function(bool)? onLoginCallback;
const LoginPage({
Key? key,
this.onLoginCallback,
}) : super(key: key);
and then call it whenever user is done logging in
onLoginCallback?.call(true);
In your AuthGuard
#override
Future<void> onNavigation(NavigationResolver resolver, StackRouter router) async {
if (await authService.isLoggedIn()) {
resolver.next();
} else {
router.push(LoginRoute(
onLoginCallback: (success) {
if (success) {
resolver.next();
router.removeLast(); // <- here is the part so that the user can't go back to login
}
},
));
}
}

Related

How to cache authorization Data(access_token)?

I have macOS app and I want to cache data when user sign in or sign up. I have access_token and refresh_token.
And when user launch Mac OS app again, I want to make user won't sign in again and immediately transfer to MainWidget()
For storing access_token and refresh_token, I use shared_preferences
This is my PreferencesProvider:
class PreferencesProvider {
static PreferencesProvider? _instance;
SharedPreferences? _preferences;
factory PreferencesProvider() {
_instance ??= PreferencesProvider._();
return _instance!;
}
PreferencesProvider._() {
_initPreferences();
}
Future<void> _initPreferences() async {
_preferences = await SharedPreferences.getInstance();
}
Future<void> setAccess(String value) async {
await _initPreferences();
await _preferences!.setString("ACCESS_TOKEN", value);
}
Future<String?> getAccess() async {
await _initPreferences();
return _preferences!.getString("ACCESS_TOKEN");
}
Future<void> clearAccess() async {
await _initPreferences();
await _preferences!.remove("ACCESS_TOKEN");
}
Future<void> setRefresh(String value) async {
await _initPreferences();
await _preferences!.setString("REFRESH_TOKEN", value);
}
Future<String?> getRefresh() async {
await _initPreferences();
return _preferences!.getString("REFRESH_TOKEN");
}
}
And this is my authorization cubit when I sign in or Sign up(if authorization is successful):
if (data['success']) {
await _preference.clearAccess();
PreferencesProvider()
..setUserId(data['userId'])
..setAccess(data['accessToken'])
..setRefresh(data['refreshToken']);
_apiProvider.setToken(data["accessToken"]);
}
And this is what I show in Main App: home: const StartScreen(),
When I launched my Mac OS app and if I signed in then I want to show home: MainWidget()
As the home: of your MaterialApp, always assign an intermediate screen[whether or not you are logged in], in this screen we will check if the user is logged in or not and redirect him to respective screens. Let's say the intermediate screen is SplashScreen which could look something like this:
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
Future<bool> _checkLoginStatus() async {
try {
//check whether we have access and refresh tokens here (maybe also perform
// a network request to check if the tokens are still valid) and
// based on that, return true or false
} catch (e) {
return false;
}
}
#override
void initState() {
_checkLoginStatus().then((value) {
if (value == true) {
//navigate the user to MainWidget()
} else {
//navigate the user to StartScreen()
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}
PS: if you intend to save your tokens locally, consider using packages like flutter_secure_storage or hive so that you can encrypt your data while saving.

Flutter firebase auth login is not updating to home page

I am trying to update the home of MaterialApp widget depending on whether the user has sign up or not.
Below is the code inside the state of ``MaterialApp```
String? _userUid;
#override
void initState() {
FirebaseAuth.instance.authStateChanges().listen((user) {
//print('changed');
print(user?.uid);
updateUser(user?.uid);
});
super.initState();
}
void updateUser(String? USER_UID) {
setState(() {
_userUid = USER_UID;
});
}
Below is the code for the home property
home: _userUid == null ? const Onbaording2() : const HomeScreen(),
Somewhere inside the widget tree
final user = await _firebaseAuth.createUserWithEmailAndPassword(
email: email!,
password: password!,
);
After running the code above, there is a user created in firebase but the screen does not change.
Also the signOut works perfectly by signing me out when I use firebaseAuth.instance.signOut();
even if you change your variable it will not be redirected to the home screen because materialapp is only called when you first load or restart the app so rather than adding this condtion in home page or other way can be
#override
void initState() {
FirebaseAuth.instance.authStateChanges().listen((user) {
//print('changed');
print(user?.uid);
if(user == null){ navigate to onboarding }
else{ navigate to home page }
});
super.initState();
}

Flutter what is the best approach to use navigator and ChangeNotifierProvider together

I'm new to flutter, this question may be a very basic one.
I have a firebase phone auth login page to implement this,
if the user is logged in, then navigate to home page
else if the user is a new user, then navigate to the sign-up page
The problem is, whenever the values are changed at the provider, the consumer will get notified and rebuild the build method. I won't be able to listen to them within the build method and return a Navigator.of(context).pushNamed(). Any idea what is the right way to use ChangeNotifierProvider along with listeners and corresponding page navigation?
I have Login class and provider class as below,
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => LoginProvider(),
child: Consumer<LoginProvider>(builder: (context, loginState, child) {
return Scaffold(
...
body: RaisedButton(
onPressed: **loginState.doLogin(_textController.text, context);**
...
)
}),
);
}
}
class LoginProvider with ChangeNotifier {
bool _navigateToSignup = false;
bool get navigateToSignup => _navigateToSignup;
Future doLogin(String mobile, BuildContext context) async {
FirebaseAuth _auth = FirebaseAuth.instance;
_auth.verifyPhoneNumber(
...
verificationCompleted: (AuthCredential credential) async {
UserCredential result = await _auth.signInWithCredential(credential);
User user = result.user;
// if user is new user navigate to signup
// do not want to use Navigator.of(context).pushNamed('/signupPage'); here, instead would like to notify listeners at login page view and then use navigator.
if (user.metadata.creationTime == user.metadata.lastSignInTime) {
_navigateToSignup = true;
} else {
if (result.user != null) {
_navigateToHome = true;
//Navigator.of(context).pushNamedAndRemoveUntil('/homePage', ModalRoute.withName('/'));
}
}
notifyListeners();
},
...
);
}
}
Thanks in advance.
There are several approaches, you choose the one that suits you best.
Pass the context to the ChangeNotifier as you are already doing. I don't like this as well, but some people do it.
Pass a callback to your ChangeNotifier that will get called when you need to navigate. This callback will be executed by your UI code.
Same as 2, but instead of a callback export a Stream and emit an event indicating you need to Navigate. Then you just listen to that Stream on your UI and navigate from there.
Use a GlobalKey for your Navigator and pass it to your MaterialApp, than you can use this key everywhere. More details here.

Making Private Route in Flutter

How can I make a wrapper over my private routes, which navigate to screen only when user is authorized, otherwise redirect to login and get back to the original screen after login.
How can make this in a generalized way, so that I just reuse it on my other Private future screens?
If you are using routes parameter in your MaterialApp, you can replace it with following implementation
import 'dart:collection';
import 'package:flutter/widgets.dart';
class ConditionalRouter extends MapMixin<String, WidgetBuilder> {
final Map<String, WidgetBuilder> public;
final Map<String, WidgetBuilder> private;
ConditionalRouter({this.public, this.private});
#override
WidgetBuilder operator [](Object key) {
if (public.containsKey(key))
return public[key];
if (private.containsKey(key)) {
if (MyAuth.isUserLoggedIn)
return private[key];
// Adding next page parameter to your Login page
// will allow you to go back to page, that user were going to
return (context) => LoginPage(nextPage: key);
}
return null;
}
#override
void operator []=(key, value) {}
#override
void clear() {}
#override
Iterable<String> get keys {
final set = Set<String>();
set.addAll(public.keys);
set.addAll(private.keys);
return set;
}
#override
WidgetBuilder remove(Object key) {
return public[key] ?? private[key];
}
}
And use it like that:
MaterialApp(
// ...
routes: ConditionalRouter(
public: {
'/start_page': (context) => StartPage()
},
private: {
'/user_profile': (context) => UserProfilePage()
}
)
)
Use StreamBuilder widget and provide it a Stream of access token/uid if there is no data then return login screen and when user is authenticated then put access token into the stream that will rebuild the StreamBuilder which return the page when user is authenticated.
Use bloc pattern or bloc library for better state management.
Hope this will help you.
Generalised idea, you could make the controller a Static variable
class LoginController{
final Function onContactingServerDone;
LoginController({this.onContactingServerDone,});
bool loggedIn;
login()async {
//contact server,get token or verify token etc
onContactingServerDone();
}
}
and in your screen
LoginController _loginController;
initState(){
_loginController = LoginController(onContactingServerDone: (){
if(_loginController.loggedIn){
Navigator.of(context).pushNamed('privateRoute');
} else {
Navigator.of(context).pushNamed('login');
}
},);
_loginController.login();
}
Widget build(context){
return CircularProgressIndicator();
}

In flutter how to navigate to specific home page based on the type of user logged in?

I have a requirement in my application to include two types of users.
one type of user (user_1) will have access to a form page after logging in and the second type (admin) of user will have access to the filled in forms of user_1?
The login screen is the same for both the users. Based on the login credentials, it will be decided whether to navigate user_1 page or admin page.
How to go about this in flutter? To navigate to different home pages for different users?
I had the same thing to do.
I use shared preference to know who is the person (in your case an admin or not). Shared preference is a persistent and secured way to stored data. Here the page of the plug in.
In my initialization page I check the shared preference and redirect to the page depending the result.
/// You can put the logo of your app
class LoadingScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
isLogged(context);
return Scaffold(
body: Center(
child: Icon(
Icons.beach_access,
),
),
);
}
}
Future isLogged(context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String isAdmin = prefs.getBool('isAdmin');
if (uid == null) {
/// If it's the first time, there is no shared preference registered
Navigator.pushNamedAndRemoveUntil(...);
} else {
if (isAdmin) {
/// If the user is an admin
Navigator.pushNamedAndRemoveUntil(...);
} else {
/// If the user is not an admin
Navigator.pushNamedAndRemoveUntil(...);
}
}
}
You just have to set up the shared preference when the user connect:
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('isAdmin', true);
And don't forget to lunch your loading/initialization screen in your main.dart
Here is a way I use to manage this:
void main() async {
String _defaultHome = "/user_home";
bool user = await Helper.getLoggeduser();
if (user.isAdmin) {
_defaultHome = "/admin_home";
}
runApp(App(home: _defaultHome));
}
Then in the App class :
class App extends StatelessWidget {
final String home;
App({
#required this.home,
});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
initialRoute: home,
routes: routes,
);
}
}
Of course, don't forget to import everything necessary.