skip to Main Content

I am bulding an app with flutter. I am at the point of retriving data during the login process. The idea is to store this data in the cache and use it later for other operations. I am using nodejs with mongodb as backend and flutter as frontend. I want to retrive a list of specialization during the login process. Once the user is logged in the token is created and informations like userId, email and token are stored in the cookie. I have then a middleware verifyAccessToken that checks if the token is available from the cookie, if the check is ok than the api getSpecialization returns the list of specializations available. The problem i am facing is that i am including in the login APIservice in flutter the getSpecialization but i am not able to retrive the list since the verifyAccessToken middleware gets bypassed resulting in a 403 error. Any help would be much appreciated. Below is my backend and frontend code:

Nodejs
Login:

const authService = require("../../../services/authService");

exports.login = async (req, res, next) => {
    const { email, password } = req.body;
    console.log("login req body is ", req.body);
    //simple checker for username and password
    if (!email || !password) {
        return res.status(400).json({ message: "email or password is missing" });
    }

    try {
        //get the data from our auth service
        const { user, validPassword, token,  maxAge } = await authService.loginUser(email, password)
        //check if the user exists
        if (!user) {
            return res.status(401).json({
                message: "Login not successful",
                error: "User not found",
            });
        }
        //return error if the password is incorrect
        if (!validPassword) {
            return res.status(401).json({
                message: "Login not successful",
                error: "Password is incorrect",
            });
        }
        res.cookie("email", user.email, {
            httpOnly: true,
            maxAge: maxAge * 1000, // convert 2h to ms; maxAge uses milliseconds
          });

          res.cookie("userId", user._id,{
            httpOnly: true,
            maxAge: maxAge * 1000, // convert 2h to ms; maxAge uses milliseconds
          });
          
        //send our cookie with the token
        res.cookie("jwt", token, {
            httpOnly: true,
            maxAge: maxAge * 1000, //convert 2h to ms; maxAge uses miliseconds

        });

        //if everything is good return the user
        return res.status(200).json({
            message: "Login successful",
            user,
            
        });
       

    } catch (err) {
        res.status(401).json({
            message: "Login not successful",
            error: err.message,
        });
    }
};

verifyAccessToken:

const jwt = require("jsonwebtoken");
require("dotenv").config();

//this only works if you use cookie-parser
const checkCookie = (req) => {
    console.log('inside checkCookie')
    console.log('all our cookies are: ', req.cookies)
    //we get the jwt cookie we need
    return req.cookies['jwt']
}

//middleware for verifying the JWT
const verifyAccessToken = (req, res, next) => {
    //define token
    let token;

    //authenticate through Bearer token
    if (req.headers.authorization && req.headers.authorization.startsWith("Bearer ")) {
        token = req.headers.authorization.split(' ')[1]
        //logs so you see what's happening
        console.log('auth bearer token')
        console.log({ token })
    }
    else {
        token = req.headers["x-access-token"] || checkCookie(req) || null
        //logs
        console.log('our token is from x-access-token header, a cookie or null')
        console.log({ token })
    }

    //if we can't get our token anywhere then the response is an error
    if (!token) {
        return res.status(403).send("A token is required for authentication");
    }
    try {
        //we use the JWT library to verify the token
        //and we need to know the token_key which we encrypted our information
        const decoded = jwt.verify(token, process.env.TOKEN_KEY);
        //log
        console.log({ decoded })
        //the middleware adds the user information to the request and sends it to the route
        req.user = decoded;
    } catch (err) {
        return res.status(401).send("Invalid Token");
    }
    //if you have doubts check Express middleware docs: http://expressjs.com/en/guide/using-middleware.html
    return next();
};

module.exports = verifyAccessToken;

getSpecialization:

const {Specialization} = require('../../../models/specialization')


exports.getSpecialization = async (req, res) => {
    try {
      const specializzazioni = await Specialization.find({}, { _id: 0, __v: 0 }); // Escludi _id e __v dalla risposta
      res.json(specializzazioni);
    } catch (error) {
      console.error('Errore durante il recupero delle specializzazioni:', error);
      res.status(500).json({ message: 'Errore durante il recupero delle specializzazioni' });
    }
  };

routes

router.route("/login").post(login);


//router.use(verifyAccessToken);
router.use(verifyAccessToken);
router.route("/book").post(book);
router.route("/getbooking/:id").get(getBooking);
router.route("/logout").get(logout);
router.route("/getSpecialization").get(getSpecialization);

Flutter
Login APIService

static Future<bool> login(LoginRequestModel model) async {
    Map<String, String> requestHeaders = {
      'Content-Type': 'application/json',
    };

    var url = Uri.http(Config.apiUrl, Config.loginAPI);

    var response = await client.post(url,
        headers: requestHeaders,
        body: jsonEncode(model.toJson())
    );

    if (response.statusCode == 200) {
      // Settare i dettagli del login
      await SharedService.setLoginDetails(loginResponseJson(response.body));

      // Ottenere il valore del cookie 'set-cookie' dalla risposta
      String? setCookie = response.headers['set-cookie'];

      if (setCookie != null) {
        // Estrarre il valore del token dal cookie
        String? token = parseCookieValue(setCookie);
        print (token);

        if (token != null) {
          SharedPreferences prefs = await SharedPreferences.getInstance();
          await prefs.setString('jwt', token);
        }
      }
      await getSpecializations();
      return true;
    } else {
      return false;
    }
  }

getSpecialization

static Future<List<Specialization>> getSpecializations() async {
    var url = Uri.http(Config.apiUrl, Config.getSpecializaionAPI);

    var response = await client.get(url);

    if (response.statusCode == 200) {
      // Converte la risposta JSON in una lista di oggetti Specialization
      List<dynamic> jsonList = jsonDecode(response.body);
      List<Specialization> specializations = jsonList
          .map((item) => Specialization(
        specId: item['specId'],
        description: item['description'],
      ))
          .toList();

      return specializations;
    } else {
      return []; // o gestisci l'errore in base alle tue esigenze
    }
  }

2

Answers


  1. The issue you’re facing is that the getSpecialization API endpoint is protected by the verifyAccessToken middleware, but when you call it from the Flutter login process, the middleware is bypassed, resulting in a 403 error.

    To resolve this, you need to ensure that the authentication token is sent along with the request to the getSpecialization endpoint. Here’s how you can modify your Flutter code to include the token:

    1. Modify your getSpecializations method in the Flutter login APIService as follows:
    static Future<List<Specialization>> getSpecializations() async {
      var url = Uri.http(Config.apiUrl, Config.getSpecializaionAPI);
    
      SharedPreferences prefs = await SharedPreferences.getInstance();
      String? token = prefs.getString('jwt');
    
      Map<String, String> requestHeaders = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      };
    
      var response = await client.get(url, headers: requestHeaders);
    
      if (response.statusCode == 200) {
        // Converte la risposta JSON in una lista di oggetti Specialization
        List<dynamic> jsonList = jsonDecode(response.body);
        List<Specialization> specializations = jsonList
            .map((item) => Specialization(
                  specId: item['specId'],
                  description: item['description'],
                ))
            .toList();
    
        return specializations;
      } else {
        return []; // o gestisci l'errore in base alle tue esigenze
      }
    }
    
    1. Make sure you have imported the required packages for Flutter:
    import 'package:http/http.dart' as http;
    import 'package:shared_preferences/shared_preferences.dart';
    

    By including the Authorization header with the token retrieved from the shared preferences, you ensure that the token is sent along with the request to the backend, allowing the verifyAccessToken middleware to authenticate the request properly.

    Please note that this solution assumes you have already implemented the login process and stored the token in the shared preferences. If you haven’t implemented it yet, make sure to store the token in the shared preferences after a successful login.

    With these modifications, you should be able to retrieve the list of specializations after the login process without encountering a 403 error.

    Login or Signup to reply.
  2. NOTE 1:

    Fetching data and store it in the cache with flutter

    • with this one , just store your access token in a local db (i.e. sqflite, sembast etc.) or shared prefs and that access token should be included in your "client" instance whenever you make any request such as GET or POST.

    example:

      Map<String, String> requestHeaders = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      };
    
      var response = await client.get(url, headers: requestHeaders);
    

    NOTE 2:

    I have then a middleware verifyAccessToken that checks if the token is available from the cookie

    • this is the one that’s returning the error. "A token is required for authentication" the latter should resolve your issue.

    NOTE 3:

    Refreshing the access token

    • the access token might have an expiration so make sure to use a refresh token mechanism before making any request again. In this case we can use dio plugin.

    example:

    var payload = {
              'grant_type': 'refresh_token',
              'refresh_token': REFRESH_TOKEN,
              'client_id': CLIENT_ID,
              'client_secret': CLIENT_SECRET,
            };
        
          
              var response = await dio.post(url,
                  data: payload,
                  options: Options(contentType: Headers.formUrlEncodedContentType));
    
     
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search