Unable to get user profile data from API - flutter

I an trying to get the user data from the API and display in the UI using flutter http package. After login in, the API returned 200 OK response. When I tried navigating back to the user profile page to get the logged in user from the API i got a 401 unauthorized access response from the API even after putting the authorization bearer token (gotten from the the login) in the http header it keeps returning 401 response. After the error continued I added a refresh token to the api but the get user keep throwing 401 response.
The Api login service
static Future loginUser(User user) async {
// Try low level Http_Client
HttpClient httpClient = HttpClient();
HttpClientRequest request = await httpClient.postUrl(
Uri.parse('$_baseUrl/login'),
);
request.headers.set('Content-Type', 'application/json');
request.add(
utf8.encode(
jsonEncode({
"userName": user.userName,
"userEmail": user.userEmail,
"password": user.password,
}),
),
);
HttpClientResponse response = await request.close();
String message = await response.transform(utf8.decoder).join();
final jsonResponse = jsonDecode(message);
httpClient.close();
if (response.statusCode == 200 || response.statusCode == 201) {
print('Response: $jsonResponse');
final _token = jsonResponse['Token'];
final _expiration = jsonResponse['Expires'];
await AppStorage.writeKey('token', _token);
print('Token Expiration: $_expiration');
return User.fromJson(jsonDecode(jsonResponse));
} else if (response.statusCode == 401 || response.statusCode == 403) {
print('Error Msg: $jsonResponse');
_refreshToken();
} else {
print("Failed Response : $jsonResponse");
return false;
}
}
The api to get user
static Future getUser() async {
String? token = await AppStorage.getKey('token');
HttpClient httpClient = HttpClient();
HttpClientRequest request = await httpClient.putUrl(
Uri.parse('$_baseUrl/profile'),
);
request.headers.set('Authentication', 'Bearer $token');
request.headers.set('Content-Type', 'application/json');
var response = await request.close();
var message = await response.transform(utf8.decoder).join();
if (message.isEmpty) {
print('Message Is Empty');
return;
} else {
var jsonResponse = json.decode(message);
print('Message: $message');
httpClient.close();
if (response.statusCode == 200 || response.statusCode == 201) {
print('Response: $jsonResponse');
return User.fromJson(jsonResponse);
} else if (response.statusCode == 401 || response.statusCode == 403) {
// If response returns 401 refresh token
print('Error Msg: $jsonResponse');
_refreshToken();
} else {
print("Failed Response : $jsonResponse");
}
}
}
The refresh token api service
static Future<dynamic> _refreshToken() async {
print('Refreshing Token ...');
String? token = await AppStorage.getKey('token');
HttpClient httpClient = HttpClient();
HttpClientRequest request = await httpClient.postUrl(
Uri.parse('$_baseUrl/refresh'),
);
request.headers.set('Content-Type', 'application/json');
request.headers.set('Authentication', 'Bearer $token');
var response = await request.close();
var message = await response.transform(utf8.decoder).join();
var jsonResponse = jsonDecode(message);
httpClient.close();
if (response.statusCode == 200 || response.statusCode == 201) {
print('Response: $jsonResponse');
// Get new token from the response
final _newToken = jsonResponse['Token'];
final _expiration = jsonResponse['Expires'];
// Store the new token
await AppStorage.writeKey('token', _newToken);
print('Token Expiration: $_expiration');
// Retry Get user token
getUser();
} else if (response.statusCode == 401 || response.statusCode == 403) {
// If response returns 401 log the user out
print('Error Msg: $jsonResponse');
_logUserOut();
} else {
print("Failed Response : $jsonResponse");
}
}

Related

how to redirect the user to the login page if the token has expired

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.
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();
}
}
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.
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
}
}

Retry to get a new access token after dio QueuedInterceptor returns 401

I am trying to implement a JWT Access/Refresh token flow with flutter. After my access token expires, my QueuedInterceptor gets a new access token with the refresh token. Everything works fine, but it is not retrying to get the requested ressource and returns a 401. After a refresh of that page, the resource loads. How do I implement a retry with QueuedInterceptor ?
class AuthInterceptor extends QueuedInterceptor {
final Dio _dio;
AuthInterceptor(this._dio);
#override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
final accessToken = await storage.read(key: "accessToken");
final refreshToken = await storage.read(key: "refreshToken");
if (accessToken == null || refreshToken == null) {
const AuthState.unauthenticated();
final error = DioError(requestOptions: options, type: DioErrorType.other);
return handler.reject(error);
}
final accessTokenHasExpired = JwtDecoder.isExpired(accessToken);
final refreshTokenHasExpired = JwtDecoder.isExpired(refreshToken);
var _refreshed = true;
if (refreshTokenHasExpired) {
const AuthState.unauthenticated();
final error = DioError(requestOptions: options, type: DioErrorType.other);
return handler.reject(error);
} else if (accessTokenHasExpired) {
// regenerate new access token
_refreshed = await _regenerateAccessToken();
}
if (_refreshed) {
options.headers["Authorization"] = "Bearer $accessToken";
return handler.next(options);
} else {
final error = DioError(requestOptions: options, type: DioErrorType.other);
return handler.reject(error);
}
}
Future<bool> _regenerateAccessToken() async {
try {
var dio = Dio();
final refreshToken = await storage.read(key: "refreshToken");
final response = await dio.post(
"https://localhost:7104/api/Login/Token/Refresh",
options: Options(headers: {"Authorization": "Bearer $refreshToken"}),
);
if (response.statusCode == 200 || response.statusCode == 201) {
final newAccessToken = response.data["accessToken"];
storage.write(key: "accessToken", value: newAccessToken);
return true;
} else if (response.statusCode == 401 || response.statusCode == 403) {
const AuthState.unauthenticated();
return false;
} else {
return false;
}
} on DioError {
return false;
} catch (e) {
return false;
}
}
}
This is how I create the request with the interceptor. It throws a 401 if my access token is expired:
final dio = Dio();
dio.options.baseUrl = authenticationBackend;
dio.interceptors.addAll([
AuthInterceptor(dio),
]);
var response = await dio.get('$host/animals');
class RefreshTokenInterceptor extends Interceptor {
final Dio dio;
RefreshTokenInterceptor({
required this.dio,
});
#override
void onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.response == null) {
return;
}
if (err.response!.statusCode == 401) {
var res = await refreshToken();
if (res != null && res) {
await _retry(err.requestOptions);
}
}
return handler.next(err);
}
/// Api to get new token from refresh token
///
Future<bool?> refreshToken() async {
///call your refesh token api here
}
/// For retrying request with new token
///
Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
final options = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
return dio.request<dynamic>(requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options);
}
}
And then use it
dio.interceptors.addAll(
[
/// interceptor for refreshing token
///
RefreshTokenInterceptor(dio: dio),
],
);

Flutter http post request gives status code 401

I am using API to verify phone number provided by user.... on postman api give perfect response and give OTP code in response but in flutter status code 401 is returned
here is my code
Future verifyPhone(String phoneNumber) async {
try {
String token = "528724967b62c6c9e546aeaee1b57e234991ad98";
var body = <String, String>{};
body['user_number'] = phoneNumber;
var url = Uri.parse(ApiKeys.phoneVerifyApiKey);
var response = await http.post(
url,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"authentication": "Bearer $token"
},
body: body,
);
if (response.statusCode == 200) {
print("Code sent");
} else {
print("Failed to send code");
print(response.statusCode);
}
} catch (err) {
print(err.toString());
}
notifyListeners();
}
instead of "code sent" i get "failed to send code" and status code 401
EDIT
You can send form request this way
Future verifyPhone(String phoneNumber) async {
try {
String token = "528724967b62c6c9e546aeaee1b57e234991ad98";
var body = <String, String>{};
body['user_number'] = phoneNumber;
var url = Uri.parse(ApiKeys.phoneVerifyApiKey);
var headers ={
"Content-Type": "application/x-www-form-urlencoded",
"authentication": "Bearer $token"
};
var request = http.MultipartRequest('POST', url)
..headers.addAll(headers)
..fields.addAll(body);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print("Code sent");
} else {
print("Failed to send code");
print(response.statusCode);
}
} catch (err) {
print(err.toString());
}
notifyListeners();
}
EDIT
To access :
var _data = jsonDecode(response);
var list = _data["data"];
print(list[0]['otp_code']);

flutter Unhandled Exception: DioError [DioErrorType.response]: Http status error [422]

I have an API which sends status 201 in case of a success and if there's any error with the submitted data it sends status 422 (Unprocessable Entity) with a JSON response.
{
"message": "The given data was invalid.",
"errors": {
"mobile": [
"The selected mobile is invalid."
]
}}
I am using Dio to post user credentials (mobile, password) if I enter the correct user credential I can fetch data from it but when I enter the wrong credential gives me this error:
Unhandled Exception: DioError [DioErrorType.response]: Http status error [422]
Dio code
userLogin(
String password,
String mobile,
) async {
try {
String url = "url";
Dio dio = Dio();
dio.options.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
};
var response = await dio.post(url, queryParameters: {
"password": password,
"mobile": mobile,
});
if (response.statusCode == 200) {
return response.data;
} catch (e) {
return e.toString();
}}
How I cloud handle error response and success?
If some of Http status codes in responses are approved then you could use validateStatus function in BaseOptions to make them valid for all dio requests.
Dio dio = Dio(
BaseOptions(
headers: {...},
validateStatus: (statusCode){
if(statusCode == null){
return false;
}
if(statusCode == 422){ // your http status code
return true;
}else{
return statusCode >= 200 && statusCode < 300;
}
},
)
);
or validateStatus function in Options of concrete request
var response = await dio.post(url,
queryParameters: {
"password": password,
"mobile": mobile,
},
options: Options(
responseType: ResponseType.json,
validateStatus: (statusCode){
if(statusCode == null){
return false;
}
if(statusCode == 422){ // your http status code
return true;
}else{
return statusCode >= 200 && statusCode < 300;
}
},
),
);
The catch method has to be added to the try. In your case it was added to if(response.statuscode ==200)
userLogin(
String password,
String mobile,
) async {
try {
String url = "url";
Dio dio = Dio();
dio.options.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
};
var response = await dio.post(url, queryParameters: json.encode({
"password": password??"",
"mobile": mobile??"",
}));
if (response.statusCode == 200) {
return response.data;
}
else{
print(response.data);
return "request failed";
}
}catch (e) {
return e.toString();
}
My Api response was this enter image description here
I have dealt with this method by allowing it BaseOptions
here is my post API code and bingo got the solution to problem
Future postApiResponse(
String url, dynamic data, bool tokentrue, String? token) async {
dynamic responceJson;
try {
// print('here 222');
if (kDebugMode) {
print('In Dio in try');
print(url);
print(data.toString());
print(tokentrue.toString());
print(token.toString());
print('In Dio in try');
}
Dio dio = Dio(BaseOptions(validateStatus: (statusCode) {
if (statusCode == 422) {
return true;
}
if (statusCode == 200) {
return true;
}
return false;
}));
if (tokentrue == true) {
// dio.options.headers['content-Type'] = 'application/json';
dio.options.headers['Accept'] = 'application/json';
dio.options.headers["authorization"] = "Bearer $token";
} else {
dio.options.headers['Accept'] = 'application/json';
}
// print('responceJson.toString()');
Response responce = await dio
.post(
url,
data: data,
)
.timeout(const Duration(seconds: 20));
debugPrint('.toString()');
responceJson = returnResponce(responce);
debugPrint(responce.toString());
} on DioError catch (e) {
returnExceptionError(e);
}
return responceJson;
}
DioError [DioErrorType.response]: Http status error [422]
The Solution :)
Dio dio = Dio(
BaseOptions(
headers: {...},
validateStatus: (statusCode){
if(statusCode == null){
return false;
}
if(statusCode == 422){ // your http status code
return true;
}else{
return statusCode >= 200 && statusCode < 300;
}
},
)
);

How to refresh token and retry request on 401 error using Flutter

I try to refresh token and retry request on 401 error, but can not understand how to do it by right way
This is a recreation from what I remember so there can be typo and small errors maybe.
I hope you get an idea what I am trying to do here.
import 'package:http/http.dart' as http;
class APIUtility {
Uri uri;
String path, method;
var body;
var headers;
APIUtility({ #required this.path, #required this.method, this.body}) {
this.uri = Uri.parse("http://localhost:4000/api/${this.path}");
this.headers = {'Content-Type': 'application/json'};
}
Future request({ bool useToken = true }) async {
http.Response response;
if ( useToken ) { this.header['token'] = await getAccessToken(); }
try {
response = await // call api with http package with correct path, method and body
if ( useToken && response.statusCode == 401 ) return await _refreshTokenAndRequest();
else return jsonDecode(response.data);
}
catch (e) {
print(e);
return null;
}
}
_refreshTokenAndRequest() async {
String accessToken = await getAccessToken();
String refreshToken = await getRefreshToken();
var body = {'access_token': accessToken, 'refresh_token': refreshToken};
http.Response response = await http.post("${this.baseUrl}/api/auth/refresh", body: body);
if (response.statusCode == 200 || response.statusCode == 201) {
saveAccessToken(response.body['access_token']);
saveRefreshToken(response.body['refresh_token']);
return await request();
} else {
// Logout user from app
// Delete all database, token and all user info and show login screen;
return null;
}
}
}