0

i have a rest api server with two endpoint for JWT authentication (/auth/token/ and /auth/token/refresh). and i want to create an app that use that rest server. what i did until now is: created a userManager class .when the user login i save the username, password , access token , refresh token and expiration time for both tokens (i'm usin secure storage package) . whenever i send a request (if the request require authentication) i verify if the access token is valid .if its not valid i refresh it and send the request. the access token have 5 min exp time and refresh token have 24 hours (the default for django).

my code :


    import 'dart:convert';

import 'package:ads_site_app/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart';

import 'data.dart';

class UserManager {
  static final _storage = FlutterSecureStorage();

  static const _keyUsername = 'username';
  static const _keyPassword = 'password';

  static const _keyEmail = 'email';

  static const _keyAccessToken = 'accesstoken';
  static const _keyRefreshToken = 'refreshtoken';

  static const _keyAccessExpTime = 'accesstokenExptime';
  static const _keyRefreshExpTime = 'refreshtokenExptime';

  static String _loginError = "";

  static String get loginError => _loginError;

  static set loginError(String loginError) {
    _loginError = loginError;
  }

  static Future setUsername(String username) async =>
      await _storage.write(key: _keyUsername, value: username);

  static Future<String?> getUsername() async =>
      await _storage.read(key: _keyUsername);

  static Future _setPassword(String password) async =>
      await _storage.write(key: _keyPassword, value: password);

  static Future<String?> _getPassword() async {
    final value = await _storage.read(key: _keyPassword);

    return value;
  }

  static Future _setAccessToken(String token) async {
    final value = token;

    await _storage.write(key: _keyAccessToken, value: value);
  }

  static Future<String?> getAccessToken() async {
    final value = await _storage.read(key: _keyAccessToken);

    return value;
  }

  static Future _setRefreshToken(String token) async {
    final value = token;

    await _storage.write(key: _keyRefreshToken, value: value);
  }

  static Future<String?> getRefreshToken() async {
    final value = await _storage.read(key: _keyRefreshToken);

    return value;
  }

  static Future _setAccessTokenExpTime(DateTime time) async {
    final value = time;

    await _storage.write(key: _keyAccessExpTime, value: value.toString());
  }

  static Future<DateTime?> getAccessTokenExpTime() async {
    final value = await _storage.read(key: _keyAccessExpTime);

    return value == null ? null : DateTime.parse(value);
  }

  static Future _setRefreshTokenExpTime(DateTime time) async {
    final value = time;

    await _storage.write(key: _keyRefreshExpTime, value: value.toString());
  }

  static Future<DateTime?> getRefreshTokenExpTime() async {
    final value = await _storage.read(key: _keyRefreshExpTime);

    return value == null ? null : DateTime.parse(value);
  }

  //////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////

  static Future<void> login(
      String url, String path, String username, String password) async {
    Response response;
    try {
      response = await post(
          Uri.http(
            url,
            path,
          ),
          body: {'username': username, 'password': password});
      if (response.statusCode >= 200 && response.statusCode < 300) {
        var time = DateTime.now();
        var data = jsonDecode(response.body) as Map;
        var refreshParsedToken = parseJwt(data['refresh']);
        var accessParsedToken = parseJwt(data['access']);
        var refreshExpTime = DateTime.fromMillisecondsSinceEpoch(
                refreshParsedToken['exp'] * 1000)
            .subtract(Duration(minutes: 30));
        var accessExpTime =
            DateTime.fromMillisecondsSinceEpoch(accessParsedToken['exp'] * 1000)
                .subtract(Duration(minutes: 2));
        _setRefreshToken(data['refresh']);
        _setAccessToken(data['access']);
        _setAccessTokenExpTime(accessExpTime);
        _setRefreshTokenExpTime(refreshExpTime);
        setUsername(username);
        _setPassword(password);
      } else {
        throw InvalidCredentials('invalid username or password');
      }
    } catch (e) {
      print(e);
      throw e;
    }
  }

  static Future<bool> refreshToken(String url, String path) async {
    Response response;
    String? token = await getRefreshToken();
    try {
      response = await post(
          Uri.http(
            url,
            path,
          ),
          body: {'refresh': ' ${token}'});
      if (response.statusCode >= 200 && response.statusCode < 300) {
        var data = jsonDecode(response.body) as Map;
        await _setAccessToken(data['access']);
        return true;
      } else {
        return false;
      }
    } catch (e) {
      print('user manager 145');
      return false;
    }
  }

  static Future<dynamic> getRequest(
      String url, String path, bool require_Auth) async {
    Response response;
    if (require_Auth) {
      if (await isLogedIn()) {
        String? token = await getAccessToken();
        try {
          response = await get(
              Uri.http(
                url,
                path,
              ),
              headers: {'Authorization': 'Bearer ${token}'});
          return response;
        } catch (e) {
          print('connection lost usermanager 112');
          print(e);
          return null;
        }
      } else {
        var username = await getUsername();
        var password = await _getPassword();

        if (username != null && password != null) {
          try {
            await login(url, '/api/auth/token/', username, password);
            String? token = await getAccessToken();
            try {
              response = await get(
                  Uri.http(
                    url,
                    path,
                  ),
                  headers: {'Authorization': 'Bearer ${token}'});
              return response;
            } catch (e) {
              print('connection lost usermanager 112');
              print(e);
              return null;
            }
          } catch (e) {
            print('login failed user manager 140');
            return null;
          }
        }
      }
    } else {
      try {
        response = await get(
          Uri.http(
            url,
            path,
          ),
        );
        return response;
      } catch (e) {
        print('connection lost usermanager 209');
        print(e);
        return null;
      }
    }
  }

  static Future<bool> isLogedIn() async {
    var accessExpTime = await getAccessTokenExpTime();
    var refreshExpTime = await getRefreshTokenExpTime();
    if (accessExpTime == null ||
        refreshExpTime == null ||
        (accessExpTime.isBefore(DateTime.now()) &&
            refreshExpTime.isBefore(DateTime.now()))) {
      return false;
    } else if (accessExpTime.isAfter(DateTime.now())) {
      return true;
    } else if (refreshExpTime.isAfter(DateTime.now())) {
      return await refreshToken(url, '/api/auth/token/refresh');
    }
    return false;
  }

  static void register(String username, String email, String password1,
      String password2) async {}

  static void logout(BuildContext context, String redirectPath) async {
    _storage.delete(key: _keyAccessExpTime);
    _storage.delete(key: _keyAccessToken);

    _storage.delete(key: _keyRefreshExpTime);
    _storage.delete(key: _keyRefreshToken);
    Navigator.pushReplacementNamed(context, redirectPath);
  }
}

parseJWT method is here : How to get the claims from a JWT in my Flutter Application

my questions are:

  • how to let the user stay connected always (is the only way is storing the credentials and sending theme when the refresh token expire? if so is it secure ? and how to revoke the session in this case )
  • if i'm sending a request and found that the refresh token expired what should i do (relogin with the saved cridentials? loging out the user?).
  • is my implimentaion good? if not what to change in it.

0 Answers0