6

I want user in my app to stay logged in. I'm using the firebase authentification with IDToken which lasts for 1hour until it expires. I want to auto refresh the session everytime if it is going to expire.

what Ive read so far here https://firebase.google.com/docs/reference/rest/auth/#section-refresh-token it should be somehow possible with https://securetoken.googleapis.com/v1/token?key=[API_KEY]

This is my full code for authentification right now (flutter)

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../provider/http_exception.dart';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';

class Auth with ChangeNotifier {
  String _token;
  DateTime _expiryDate;
  String _userId;
  Timer _authTimer;
  bool wasLoggedOut = false;
  bool onBoarding = false;

  Future<void> createUser(String email, String firstName, String lastName) async {
    final url = 'https://test45.firebaseio.com/users/$userId.json?auth=$token';
    final response = await http.put(url, body: json.encode({
      'userEmail': email,
      'userIsArtist': false,
      'userFirstName': firstName,
      'userLastName': lastName,
    }));
    print('post ist done');
    print(json.decode(response.body));
  }

  bool get isAuth {
    return token != null;
  }

  String get userId {
    return _userId;
  }

  String get token {
    if (_expiryDate != null &&
        _expiryDate.isAfter(DateTime.now()) &&
        _token != null) {
      return _token;
    }
    return null;
  }

  Future<void> authenticate(
      String email, String password, String urlSegement) async {
    final url = 'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegement?key=AIzaSyD8pb3M325252dfsDC-4535dfd';

    try {
      final response = await http.post(url,
          body: json.encode({
            'email': email,
            'password': password,
            'returnSecureToken': true,
          }));
      final responseData = json.decode(response.body);
      if (responseData['error'] != null) {
        throw HttpException(responseData['error']['message']);
      }
      _token = responseData['idToken'];
      _userId = responseData['localId'];
      _expiryDate = DateTime.now().add(Duration(seconds: int.parse(responseData['expiresIn'])));
      _autoLogout();
     
      notifyListeners();

      final prefs = await SharedPreferences.getInstance();
      final userData = json.encode({
        'token': _token,
        'userId': _userId,
        'expiryDate': _expiryDate.toIso8601String(),
      });
      prefs.setString('userData', userData);
    } catch (error) {
      throw error;
    }
  }

  Future<void> signup(String email, String password) async {
    return authenticate(email, password, 'signUp');
  }

  Future<void> signin(String email, String password) async {
    return authenticate(email, password, 'signInWithPassword');
  }

  Future<bool> tryAutoLogin() async {
    final prefs = await SharedPreferences.getInstance();
    if(!prefs.containsKey('userData')){
      return false;
    }
    final extractedUserData = json.decode(prefs.getString('userData')) as Map<String, Object>;
    final expiryDate = DateTime.parse(extractedUserData['expiryDate']);

    if(expiryDate.isBefore(DateTime.now())) {
      return false;
    }

    _token = extractedUserData['token'];
    _userId = extractedUserData['userId'];
    _expiryDate = expiryDate;

    notifyListeners();
    _autoLogout();
    return true;
  }


  Future<void> logout() async {
    _token = null;
    _userId = null;
    _expiryDate = null;
    if(_authTimer != null){
      _authTimer.cancel();
      _authTimer = null;
    }
    notifyListeners();
    final prefs = await SharedPreferences.getInstance();
    prefs.remove('userData');
  }

  void _autoLogout() {
    if(_authTimer != null) {
      _authTimer.cancel();
    }
  final timetoExpiry =  _expiryDate.difference(DateTime.now()).inSeconds;
    _authTimer = Timer(Duration(seconds: timetoExpiry), logout);
  }
}

how to modify my auth.dart to achieve the auto refreshing?

EDIT:

As mentioned in the comments, im working with providers where I have the following functions to retrieve the token:

update(String token, id, List<items> itemsList) {
    authToken = token;
    userId = id;
  }

also in every of my API calls im using the auth parameter already:

var url = 'https://test45.firebaseio.com/folder/$inside/$ym.json?auth=$authToken';

I just need somebody who can show me how to modify my code with the refresh token.

Thanks in advance!

EDIT:

I tried to implement it, but im getting an infinite loop, please help:

String get token {
    if (_expiryDate != null &&
        _expiryDate.isAfter(DateTime.now()) &&
        _token != null) {
      return _token;
    }
    refreshSession();
  }

  Future<void> refreshSession() async {
        final url = 'https://securetoken.googleapis.com/v1/token?key=5437fdjskfsdk38438?grant_type=refresh_token?auth=$token';
      
      try {
      final response = await http.post(url,
          body: json.encode({
  'token_type': 'Bearer',
          }));
      final responseData = json.decode(response.body);
      if (responseData['error'] != null) {
        throw HttpException(responseData['error']['message']);
      }
      _token = responseData['id_token'];
      _userId = responseData['user_id'];
      _expiryDate = DateTime.now().add(Duration(seconds: int.parse(responseData['expires_in'])));
      _autoLogout();
     
      notifyListeners();

      final prefs = await SharedPreferences.getInstance();
      final userData = json.encode({
        'token': _token,
        'userId': _userId,
        'expiryDate': _expiryDate.toIso8601String(),
      });
      prefs.setString('userData', userData);
    } catch (error) {
      throw error;
    }
      }
Marcel Dz
  • 2,321
  • 4
  • 14
  • 49
  • 3
    Any reason why you're not using the provided [SDK for Firebase Auth](https://pub.dev/packages/firebase_auth), which manages everything for you? – Doug Stevenson Aug 23 '20 at 04:46
  • well basically no, Ive learned flutter from an education, they showed us working with the rest api and http requests. Do you have any idea what to change for auto refresh? – Marcel Dz Aug 23 '20 at 04:49
  • 2
    I don't know, but I'm sure it would be **a lot** easier if you just use the SDK like most everyone else. – Doug Stevenson Aug 23 '20 at 04:50
  • well im really far with my app not using any Firebase SDK wether Realtime database, firebase storage or authentification, only using the api in direct way. If it is possible I want to keep it for now this way without using it – Marcel Dz Aug 23 '20 at 04:56
  • It would be better if you switched to the Firebase SDK methods as suggested by @DougStevenson – Siddharth Agrawal Aug 24 '20 at 06:16
  • maybe yea, but in this way you are directly working with the api, it has to work... Is it that difficult? – Marcel Dz Aug 24 '20 at 13:05
  • what I do is authenticate then let my server do the work. It isn't hard to make it work with the api. I really would suggest using the flutter sdk since it is regularly maintained and contains less security vulnerabilites. – CoderUni Sep 17 '20 at 13:46
  • @Uni can you show me how my final auth.dart would be look like then? – Marcel Dz Sep 18 '20 at 05:56
  • @MarcelDz https://github.com/iamshaunjp/flutter-firebase/blob/lesson-27/brew_crew/lib/services/auth.dart – CoderUni Sep 18 '20 at 06:04
  • okay guys i tried to implement the api function myself, im getting an infinite loop, but i think im quite close to the solution, please help, code is in my question – Marcel Dz Sep 18 '20 at 07:23

4 Answers4

7

I edited your refresh_token() function.

Firstly, you should use your web api key on your firebase project with the link. You should also save the refresh token. And if you post like this, it will work. If don't work, try without json.encode() function on your body as I commit.

Future<void> refreshSession() async {
  final url =
      'https://securetoken.googleapis.com/v1/token?key=$WEB_API_KEY'; 
  //$WEB_API_KEY=> You should write your web api key on your firebase project.
  
  try {
    final response = await http.post(
      url,
      headers: {
        "Accept": "application/json",
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: json.encode({
        'grant_type': 'refresh_token',
        'refresh_token': '[REFRESH_TOKEN]', // Your refresh token.
      }),
      // Or try without json.encode.
      // Like this:
      // body: {
      //   'grant_type': 'refresh_token',
      //   'refresh_token': '[REFRESH_TOKEN]',
      // },
    );
    final responseData = json.decode(response.body);
    if (responseData['error'] != null) {
      throw HttpException(responseData['error']['message']);
    }
    _token = responseData['id_token'];
    _refresh_token = responseData['refresh_token']; // Also save your refresh token
    _userId = responseData['user_id'];
    _expiryDate = DateTime.now()
        .add(Duration(seconds: int.parse(responseData['expires_in'])));
    _autoLogout();

    notifyListeners();

    final prefs = await SharedPreferences.getInstance();
    final userData = json.encode({
      'token': _token,
      'refresh_token': _refresh_token,
      'userId': _userId,
      'expiryDate': _expiryDate.toIso8601String(),
    });
    prefs.setString('userData', userData);
  } catch (error) {
    throw error;
  }
}

This is your full auth.dart file which I edited.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../provider/http_exception.dart';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';

class Auth with ChangeNotifier {
  String _token;
  String _refresh_token;
  DateTime _expiryDate;
  String _userId;
  Timer _authTimer;
  bool wasLoggedOut = false;
  bool onBoarding = false;

  Future<void> createUser(String email, String firstName, String lastName) async {
    final url = 'https://test45.firebaseio.com/users/$userId.json?auth=$token';
    final response = await http.put(url, body: json.encode({
      'userEmail': email,
      'userIsArtist': false,
      'userFirstName': firstName,
      'userLastName': lastName,
    }));
    print('post ist done');
    print(json.decode(response.body));
  }

  bool get isAuth {
    return token != null;
  }

  String get userId {
    return _userId;
  }

  String get token {
    if (_expiryDate != null &&
        _expiryDate.isAfter(DateTime.now()) &&
        _token != null && _refresh_token!=null) {
      return _token;
    }
    refreshSession();
    return null;
  }

  Future<void> authenticate(
      String email, String password, String urlSegement) async {
    final url = 'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegement?key=AIzaSyD8pb3M325252dfsDC-4535dfd';

    try {
      final response = await http.post(url,
          body: json.encode({
            'email': email,
            'password': password,
            'returnSecureToken': true,
          }));
      final responseData = json.decode(response.body);
      if (responseData['error'] != null) {
        throw HttpException(responseData['error']['message']);
      }
      _token = responseData['idToken'];
      _refresh_token = responseData['refreshToken'];
      _userId = responseData['localId'];
      _expiryDate = DateTime.now().add(Duration(seconds: int.parse(responseData['expiresIn'])));
      _autoLogout();
     
      notifyListeners();

      final prefs = await SharedPreferences.getInstance();
      final userData = json.encode({
        'token': _token,
        'refresh_token': _refresh_token,
        'userId': _userId,
        'expiryDate': _expiryDate.toIso8601String(),
      });
      prefs.setString('userData', userData);
    } catch (error) {
      throw error;
    }
  }

  Future<void> signup(String email, String password) async {
    return authenticate(email, password, 'signUp');
  }

  Future<void> signin(String email, String password) async {
    return authenticate(email, password, 'signInWithPassword');
  }

  Future<bool> tryAutoLogin() async {
    final prefs = await SharedPreferences.getInstance();
    if(!prefs.containsKey('userData')){
      return false;
    }
    final extractedUserData = json.decode(prefs.getString('userData')) as Map<String, Object>;
    final expiryDate = DateTime.parse(extractedUserData['expiryDate']);

    if(expiryDate.isBefore(DateTime.now())) {
      return false;
    }

    _token = extractedUserData['token'];
    _refresh_token = extractedUserData['refresh_token'];
    _userId = extractedUserData['userId'];
    _expiryDate = expiryDate;

    notifyListeners();
    _autoLogout();
    return true;
  }


  Future<void> logout() async {
    _token = null;
    _refresh_token = null;
    _userId = null;
    _expiryDate = null;
    if(_authTimer != null){
      _authTimer.cancel();
      _authTimer = null;
    }
    notifyListeners();
    final prefs = await SharedPreferences.getInstance();
    prefs.remove('userData');
  }

  void _autoLogout() {
    if(_authTimer != null) {
      _authTimer.cancel();
    }
  final timetoExpiry =  _expiryDate.difference(DateTime.now()).inSeconds;
    _authTimer = Timer(Duration(seconds: timetoExpiry), logout);
  }
  
  


Future<void> refreshSession() async {
  final url =
      'https://securetoken.googleapis.com/v1/token?key=$WEB_API_KEY'; 
  //$WEB_API_KEY=> You should write your web api key on your firebase project.
  
  try {
    final response = await http.post(
      url,
      headers: {
        "Accept": "application/json",
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: json.encode({
        'grant_type': 'refresh_token',
        'refresh_token': '[REFRESH_TOKEN]', // Your refresh token.
      }),
      // Or try without json.encode.
      // Like this:
      // body: {
      //   'grant_type': 'refresh_token',
      //   'refresh_token': '[REFRESH_TOKEN]',
      // },
    );
    final responseData = json.decode(response.body);
    if (responseData['error'] != null) {
      throw HttpException(responseData['error']['message']);
    }
    _token = responseData['id_token'];
    _refresh_token = responseData['refresh_token']; // Also save your refresh token
    _userId = responseData['user_id'];
    _expiryDate = DateTime.now()
        .add(Duration(seconds: int.parse(responseData['expires_in'])));
    _autoLogout();

    notifyListeners();

    final prefs = await SharedPreferences.getInstance();
    final userData = json.encode({
      'token': _token,
      'refresh_token': _refresh_token,
      'userId': _userId,
      'expiryDate': _expiryDate.toIso8601String(),
    });
    prefs.setString('userData', userData);
  } catch (error) {
    throw error;
  }
}
}
FurkanKURT
  • 460
  • 3
  • 9
  • thank you ver much for your code, appreciate it alot! So I changed this 'refresh_token': '[REFRESH_TOKEN]', to this 'refresh_token': _refresh_token, the code looks fine, but im getting error missing grant type. please help – Marcel Dz Sep 20 '20 at 07:26
  • Also if I print the content of refresh token before line if (responseData['error'] != null) {, im receiving the refresh token, but then in the error we have MISSING GRANT TYPE – Marcel Dz Sep 20 '20 at 07:29
  • Did you send this value also? 'grant_type':'refresh_token'. Because it must to send with refresh token. And don't change anything. If you send it like 'grant_type':'your refresh token' I won't work. – FurkanKURT Sep 21 '20 at 09:17
  • I did it like this: body: json.encode({ 'grant_type': 'refresh_token', 'refresh_token': refresh_token, // Your refresh token. }), but if you keep it like you have, it doesnt make any difference, still the same error. PLease help :( – Marcel Dz Sep 21 '20 at 09:34
  • Hmm, something’s wrong but I couldn’t get it. If you available, I can connect to you via AnyDesk or something like remote connection program. Send a mail to me fkurt97@gmail.com. – FurkanKURT Sep 21 '20 at 10:13
  • change this body: json.encode({ 'grant_type': 'refresh_token', 'refresh_token': '[REFRESH_TOKEN]', // Your refresh token. }) to body: { 'grant_type': 'refresh_token', 'refresh_token': '[REFRESH_TOKEN]', // Your refresh token. } – Mohit H Oct 04 '22 at 14:13
2

You need to save the refresh token.
Follow this topic to refresh your IDToken using the refresh token: https://firebase.google.com/docs/reference/rest/auth#section-refresh-token

When making any calls to the API, use a function to retrieve the IDToken. This function must check if the current IDToken is still valid and, if not, ask for a new one (using the link provided).

Rod
  • 1,601
  • 12
  • 18
  • Can you modify my auth.dart functions to a working solution? – Marcel Dz Aug 25 '20 at 14:40
  • You only need to modify your get token. If the token is invalid (what you are already checking), call the refresh API, similar to what you are doing for authenticate. – Rod Aug 25 '20 at 14:43
  • okay so same function like my authenticate only with the https://securetoken.googleapis.com/v1/token?key=[API_KEY] ? and how does the json body look like there? (sry my english, if you could do me the function as example, i think i can learn from that the easiest way – Marcel Dz Aug 25 '20 at 14:52
  • Could you provide me a working example? Im having problems to understand, receive and change the refresh token in my case. @racr0x Also I edited my question to show how I receive th idtoken at the moment with every api call and with providers – Marcel Dz Aug 27 '20 at 06:45
  • Can you modify my auth.dart? That would be amazing! – Marcel Dz Aug 30 '20 at 11:40
  • okay guys i tried to implement the api function myself, im getting an infinite loop, but i think im quite close to the solution, please help, code is in my question – Marcel Dz Sep 18 '20 at 07:23
1

I think the Dio library is right for you

dio = Dio();
dio.options.baseUrl = URL_API_PROD;
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (Options option) async{
    
    //getToken() : you can check token expires and renew in this function
    await getToken().then((result) {
      token = result;
    });
    option.headers = {
      "Authorization": "Bearer $token"
    };
  }
));

Response response = await dio.get('/api/users');
Hưng Trịnh
  • 947
  • 1
  • 12
  • 23
  • hello, thank you for your approach. can you also show me how to do it with the rest api with the securetoken.googleapis.com/v1/token?key=[API_KEY] ? i tried to implement it but, i didnt get any good result. that would be nice if you can show me – Marcel Dz Sep 01 '20 at 05:41
  • okay guys i tried to implement the api function myself, im getting an infinite loop, but i think im quite close to the solution, please help, code is in my question – Marcel Dz Sep 18 '20 at 07:23
  • 1
    It's not necessary to use dio. You can use just http. – FurkanKURT Sep 19 '20 at 08:59
0

body expects string...Hence change body in refreshSession() to body: 'grant_type=refresh_token&refresh_token=[YOUR REFRESH TOKEN]',.

You need to load 'refreshToken' from SharedPreferences before sending http.post request.

Simas Joneliunas
  • 2,890
  • 20
  • 28
  • 35