0

hello I have a case where when the user token expires the user does not switch to the loginPage page, even though I have set it here. how do i solve this problem thanks.

enter image description here

i set it on splashscreen if token is not null then go to main page and if token is null then go to login page. but when the token expires it still remains on the main page

Future<void> toLogin() async {
    Timer(
      const Duration(seconds: 3),
      () async {
        SharedPreferences prefs = await SharedPreferences.getInstance();
        String? token = prefs.getString(Constant.token);
        Navigator.pushReplacementNamed(
          context,
          token != null ? AppRoute.mainRoute : AppRoute.loginRoute,
          arguments: token,
        );
      },
    );
  }

and function when user login

CustomButtonFilled(
                                  title: 'Login',
                                  onPressed: () async {
                                    final prefs =
                                        await SharedPreferences.getInstance();
                                    prefs.setString(Constant.token, '');
                                    if (nimController.text.isEmpty ||
                                        passwordController.text.isEmpty) {
                                      showError('NIM/Password harus diisi');
                                    } else {
                                      setState(() {
                                        isLoading = true;
                                      });
                                      User? user = await userProvider.login(
                                          nimController.text,
                                          passwordController.text);
                                      setState(() {
                                        isLoading = false;
                                      });
                                      if (user == null) {
                                        showError('NIM/Password tidak sesuai!');
                                      } else {
                                        userProvider.user = user;
                                        Navigator.pushNamedAndRemoveUntil(
                                          context,
                                          '/main',
                                          (route) => false,
                                        );
                                      }
                                    }
                                  },
                                ),

and this call api

Future<User?> login(String nim, String password) async {
    String url = Constant.baseURL;
    try {
      var body = {
        'username': nim,
        'password': password,
      };
      var response = await http.post(
        Uri.parse(
          '$url/login_mhs',
        ),
        body: body,
      );
      if (response.statusCode == 200) {
        final token = jsonDecode(response.body)['data']['access_token'];
        //Ini mulai nyimpen token
        await UtilSharedPreferences.setToken(token);
        print(token);
        // print(await UtilSharedPreferences.getToken());
        return User.fromJson(jsonDecode(response.body));
      } else {
        return null;
      }
    } catch (e) {
      print(e);
      throw Exception();
    }
  }
sharon
  • 363
  • 2
  • 11
  • In this case, you also need to store token expiry date in the shared preferences and check in splash screen if that date is in the past or not and if that date is in the past then you redirect to login or else on home screen. Main reason behind doing this is that even if your token has expired it won't get removed from the shared preference on it's own and you manually need to remove that if you get 401 or unauthorized response from any api. – Karan Mehta Dec 28 '22 at 06:24
  • i think you need to create one base api class there you can handle all the api responses from one class and set condition on status code if you get status code 401 (Token expiration) then Navigate to login screen. it will done your work easy from every screen or api you dont have to do such code every time. – Sumita Naiya Dec 28 '22 at 06:28
  • i've update how do i solve this? – sharon Dec 28 '22 at 06:36

2 Answers2

1

you can just make your own HTTP client using Dio and add Interceptor to automatically regenerate idToken if expired using the refreshToken given.

Http client gives an error if the refreshToken also gets expired.

In that case, just navigate to the login screen.

Full code for adding interceptor and making own HTTP client is given below

import 'package:dio/dio.dart';

import '../utils/shared_preference.dart';

class Api {
  static Dio? _client;
  static Dio clientInstance() {
    if (_client == null) {
      _client = Dio();
      _client!.interceptors
          .add(InterceptorsWrapper(onRequest: (options, handler) async {
        if (!options.path.contains('http')) {
          options.path = 'your-server' + options.path;
        }
        options.headers['Authorization'] =
            'Bearer ${PreferenceUtils.getString('IdToken')}';
        return handler.next(options);
      }, onError: (DioError error, handler) async {
        if ((error.response?.statusCode == 401 &&
            error.response?.data['message'] == "Invalid JWT")) {
          if (PreferenceUtils.exists('refreshToken')) {
            await _refreshToken();
            return handler.resolve(await _retry(error.requestOptions));
          }
        }
        return handler.next(error);
      }));
    }
    return _client!;
  }

  static Future<void> _refreshToken() async {
    final refreshToken = PreferenceUtils.getString('refreshToken');
    final response = await _client!
        .post('/auth/refresh', data: {'refreshToken': refreshToken});

    if (response.statusCode == 201) {
      // successfully got the new access token
      PreferenceUtils.setString('accessToken', response.data);
    } else {
      // refresh token is wrong so log out user.
      PreferenceUtils.deleteAll();
    }
  }

  static Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
    final options = Options(
      method: requestOptions.method,
      headers: requestOptions.headers,
    );
    return _client!.request<dynamic>(requestOptions.path,
        data: requestOptions.data,
        queryParameters: requestOptions.queryParameters,
        options: options);
  }
}

Dio client = Api.clientInstance();
var resposne = (hit any request);
   if(error in response is 401){
  //it is sure that 401 is because of expired refresh token as we 
  //already handled idTokoen expiry case in 401 error while 
  //adding interceptor.
navigate to login screen for logging in again.
}

Please accept the solution if it solves your problem.

0

If your session expire feature has some predefine interval or logic than you have to implement it in splash screen and based on that you can navigate user further. Otherwise you want to handle it in API response only you have add condition for statusCode 401.

checkSessionExpire(BuildContext context)
   if (response.statusCode == 200) { 
      //SuccessWork
   } else if (response.statusCode == 401) {
      //SessionExpire
   } else {
      return null
   }
}
Khyati Modi
  • 630
  • 1
  • 9
  • 19
  • https://pastebin.com/NrTy2y30 Undefined name 'context'. Try correcting the name to one that is defined, or defining the name. – sharon Dec 28 '22 at 07:57
  • You have to pass BuildContext as function parameter to use it, I have update the code. Please check. – Khyati Modi Dec 29 '22 at 06:26
  • https://drive.google.com/file/d/1ibj11EXDkrkTO0y_fgK7fFqNGpJ_vQHb/view?usp=sharing – sharon Dec 29 '22 at 07:03
  • https://pastebin.com/cLUn83ac still getting the error, how do i fix it or maybe am i putting it wrong – sharon Dec 29 '22 at 07:03
  • In your login function you have to pass buildContext. In my code checkSessionExpire is function name. Remove it from your code and add context in your login function. i.e. Future login (String nim, String password, BuildContext context) – Khyati Modi Dec 29 '22 at 07:20
  • https://drive.google.com/file/d/1H7tpVzsmpAMqYrMJeYcJEy1FoQStaBio/view?usp=sharing – sharon Dec 29 '22 at 07:24
  • Right, pass context there. await userProvider.login(nimController.text, passwordController.text, context) – Khyati Modi Dec 29 '22 at 07:31
  • the result is still the same, when my token expires it doesn't go to the loginPage page – sharon Dec 29 '22 at 07:39
  • Trace the response, are you getting 401 response code? If yes add some log in if condition to check does that line of code satisfy the condition or not. Than add your routing code in if else condition - else if (response.statusCode == 401) { // here add some log } – Khyati Modi Dec 29 '22 at 07:42
  • how, I'm a bit confused here – sharon Dec 29 '22 at 08:12
  • by using print statement or breakpoints you can easily debug your response status and code lines, – Khyati Modi Dec 29 '22 at 08:52
  • I have tried the result is 401 not authorized, and does not move pages – sharon Dec 29 '22 at 09:06
  • https://stackoverflow.com/questions/63271031/how-to-redirect-to-a-login-page-if-flutter-api-response-is-unauthorized – Khyati Modi Dec 29 '22 at 09:10