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
The issue you’re facing is that the
getSpecialization
API endpoint is protected by theverifyAccessToken
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:getSpecializations
method in the Flutter login APIService as follows: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 theverifyAccessToken
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.
NOTE 1:
Fetching data and store it in the cache with flutter
example:
NOTE 2:
I have then a middleware verifyAccessToken that checks if the token is available from the cookie
NOTE 3:
Refreshing the access token
example: