I seem to be facing an issue with authentication in a MEAN stack app, possibly due to my limited understanding of promises and the $http's .then()
method. When I try to authenticate to my backend Node server with incorrect credentials, the success callback of $http's .then()
method is being called instead of the error callback. Here's how things are set up:
I'm utilizing the jsonwebtoken
and express-jwt
packages, AngularJS interceptors for token addition to requests and checking 401 responseErrors, a TokenService for JWT management, and a UserService for login/logout functionalities.
Upon debugging, here's what unfolds:
- A login request is sent out
- The server receives the request, searches for the specified user but fails to find them in the database. It responds with a 401 error along with a JSON object containing error details.
- The HttpInterceptor triggers the
responseError
method, correctly identifies it as a status 401, deletes any existing tokens, redirects to the/login
screen, and issues a$q.reject(response)
. UserService.login()
appropriately uses the error callback and returnsresponse
.- Issue - the success callback within my login.js
.login()
method executes instead of the intended error callback. I have a suspicion that this connects to the topic discussed in a certain article about promise chaining, but my knowledge reaches its limit here, leaving me unsure of the next step to inform the subsequent callback in the chain that the previous one encountered an error...
This is how it's structured:
Express:
authRoutes.js
authRoutes.post("/login", function (req, res) {
User.findOne({username: req.body.username}, function (err, user) {
if (err) res.status(500).send(err);
if (!user) {
res.status(401).send({success: false, message: "User with the provided username was not found"})
} else if (user) {
bcrypt.compare(req.body.password, user.password, function (err, match) {
if (err) throw (err);
if (!match) res.status(401).json({success: false, message: "Incorrect password"});
else {
var token = jwt.sign(user, config.secret, {expiresIn: "24h"});
res.json({token: token, success: true, message: "Here's your token!"})
}
});
}
});
});
Following debugging, it appears that upon logging in with incorrect credentials, it successfully reaches the res.status(401).send(...)
line, indicating that this aspect seems accurate.
Angular:
app.js (including HttpInterceptor)
var app = angular.module("TodoApp", ["ngRoute"]);
app.factory("AuthInterceptor", ["$q", "$location", "TokenService", function ($q, $location, TokenService) {
return {
request: function (config) {
var token = TokenService.getToken();
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = "Bearer " + token
}
return config;
},
responseError: function (response) {
if (response.status === 401) {
TokenService.removeToken();
$location.path("/login");
}
return $q.reject(response);
}
}
}]);
app.config(function ($routeProvider, $httpProvider) {
$httpProvider.interceptors.push('AuthInterceptor');
$routeProvider
.when("/", {
templateUrl: "landing/landing-page.html"
});
});
userService.js
var app = angular.module("TodoApp");
app.service("UserService", ["$http", "TokenService", function ($http, TokenService) {
this.signup = function (user) {
return $http.post("http://localhost:8080/auth/signup", user).then(function (response) {
return response;
}, function (response) {
return response;
});
};
this.login = function (user) {
return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
if (response.data.success) TokenService.setToken(response.data.token);
return response;
}, function (response) {
return response;
})
};
this.isAdmin = function (user) {
return user.admin;
};
}]);
login.js (Where the problem manifests)
var app = angular.module("TodoApp");
app.config(function ($routeProvider) {
$routeProvider
.when("/login", {
templateUrl: "auth/login.html",
controller: "LoginController"
})
});
app.controller("LoginController", ["$scope", "$http", "$location", "UserService", "TokenService", function ($scope, $http, $location, UserService, TokenService) {
$scope.login = function (user) {
UserService.login(user).then(function (response) {
$location.path("/todo");
}, function (response) {
console.log("There was a problem: " + response);
});
}
}]);
In the snippet
UserService.login(user).then(function (response) { $location.path("/todo");
, the line executing attempts to redirect the user to the list of Todo items, when the intention is for it to run the console.log("There was a problem: " + response);
line instead...
As mentioned earlier, there could be a link to promise chaining and error handling midway through the chain rather than propagating down the chain. I am uncertain if adding a .catch()
block like suggested in the aforementioned site would resolve this. Even if that is the solution, articulating that properly poses a challenge for me.
If there exists a better way to organize this setup, I am open to suggestions. Since I need to teach this to a class, I aim to impart good practices.
Thank you in advance for any guidance!