Authentication - The success callback of $http is executed rather than the error callback

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:

  1. A login request is sent out
  2. 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.
  3. 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).
  4. UserService.login() appropriately uses the error callback and returns response.
  5. 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!

Answer №1

Take a closer look at this part of your code:

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

Upon analyzing the error callback in the above code snippet, it is important to note that providing a return value means passing it to the next callback in the promise chain. To avoid confusion, ensure you either return a rejected promise or throw an error from the callback to propagate the error further. Otherwise, treating errors as successful outcomes will lead to unintended consequences.

To address this issue, consider one of the following solutions:

1. Remove the error callback entirely:

return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
    if (response.data.success) TokenService.setToken(response.data.token);
    return response;
});

2. Ensure failed promises are returned:

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 $q.reject(response);
});

3. Throw an error when encountering failures:

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) {
    throw new Error(response);
});

Answer №2

Have you ever considered using $q.reject in error scenarios when dealing with then() callbacks?

For example:

// don't forget to inject $q as a dependency

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) {
        $q.reject(response);
    })
};

Additional resources: https://docs.angularjs.org/api/ng/service/$q

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The error message "Cannot read property 'addEventListener' of undefined" occurred while trying to register the service worker using `navigator.serviceWorker.register('worker.js')`

I'm grappling with a JavaScript issue and need some help. You can find the demo of the functioning script by the author right here. I've implemented the same code as displayed on his demo page. I've downloaded the worker.js file and ...

How can I initialize a checkbox with a default value of false?

Currently, I have: <input ng-model="answer.response" type="checkbox" /> Upon fetching data from the server, answer.response will initially be empty. Is there a method to set this as false by default? ...

We apologize, but the module you are looking for cannot be found: Unable to locate 'fs'

Trying to create a new MDX blog website using Next.js 13 with the latest app router, but encountering an error message saying "Module not found: Can't resolve 'fs'". It was working fine in Next.js 12 and pages directory, but not in the lates ...

Unable to track user (Mineflayer - Node.js)

Trying to track a player using the mineflayer library in Node.js, but encountering an error within the source code of the library itself. Attempted code: const mineflayer = require('mineflayer'); const { pathfinder, Movements, goals } = require( ...

Error encountered while trying to load component: template or render function is not defined

I have set up a basic vue.js configuration using browserify and vueify. Following advice from previous tutorials, I included aliasify as a dependency to utilize the template engine. Below is my package.json file: { "name": "simple-vueify-setup", "ve ...

Error 600: The element you have selected is not a valid target for this action. (Internet Explorer exclusive

I have been developing an AJAX calendar that functions perfectly in Chrome, Safari, and Firefox. However, I am encountering issues with Internet Explorer 9 and earlier versions. The error message I am receiving is SCRIPT 600: Invalid target element for th ...

A guide on updating table rows in Material UI and React Table

I am currently developing a table using Material UI/React that consists of multiple text fields per row and permits users to delete specific rows. Each row in the table is generated from a list, and I am utilizing React's state hooks to manage the sta ...

"Make a phone call following the completion of an HTTP

Upon receiving and storing data, I am looking to execute a function but the code I currently have is not producing the desired result. Could someone provide some insight into what I might be doing incorrectly? Here's my current code snippet: $h ...

The functionality of activating a button is not functioning as expected in AngularJS framework

Next to the question I have linked here, I am exploring a different approach. As a beginner in AngularJS/Cordova/Ionic, my goal is to achieve different outcomes when the "Eingepasst" button is clicked, each with unique logic compared to "Gewonnen" or "Ver ...

Having trouble reaching the upload file in PHP

I am attempting to transfer files to a remote server using JavaScript, with PHP as the backend. The JavaScript code seems to be functioning properly, but on the PHP side, the $_FILES and $_POST arrays are empty. However, the $_SERVER array contains some da ...

Adjust the size of an image within a canvas while maintaining its resolution

My current project involves using a canvas to resize images client-side before uploading to the server. maxWidth = 500; maxHeight = 500; //handle resizing if (image.width >= image.height) { var ratio = 1 / (image.width / maxWidth); } else { var ...

What is the reason the 'Add' type does not meet the 'number' constraint?

I experimented with type gymnastics using Typescript, focusing on implementing mathematical operations with numeric literals. First, I created the BuildArray type: type BuildArray< Length extends number, Ele = unknown, Arr extends unknown ...

Retrieve various key-value pairs from the JSON data in the global market API using AJAX

I have recently developed a real-time API specifically designed for monitoring World Stock Markets. This API covers popular indices such as Nifty, Dow Jones, Nasdaq, and SGX Nifty. If you are interested in accessing this Real Time API, you can do so by vi ...

Testing controls in AngularJS is an essential part of verifying the

Just diving into the world of Angular and wanting to write some basic unit tests for my controllers, here is what I have so far. app.js: 'use strict'; // Define the main module along with its dependencies angular.module('Prototype', ...

Unresolved promise: Internal server issue

I encountered an exception while working on my Nativescript app. EXCEPTION: Uncaught (in promise): Server error JS: ORIGINAL STACKTRACE: JS: Error: Uncaught (in promise): Server error JS: at resolvePromise (/data/data/com.yourdomain.appname/files/app/ ...

"Permission denied to access restricted URI" error encountered while attempting to utilize ng-template functionality

I am attempting to implement ng-include for recursive templates in my HTML. After testing it on jsfiddle and confirming that it works, I tried the same locally. However, I encountered the following error: Error: Access to restricted URI denied createHttpB ...

Passing a JSON object from a Rails controller to Javascript

After creating a hash structure in Ruby like this: Track_list = {:track1=>{:url=>"https://open.spotify.com/track/2Oehrcv4Kov0SuIgWyQY9e", :name=>"Demons"}, :track2=>{:url=>"https://open.spotify.com/track/0z8yrlXSjnI29Rv30RssNI", :name=> ...

How to retrieve the value of a selected radio button in an AngularJS radio button group that uses ng-repeat

In the following code snippet, I am trying to retrieve the value when any of the radio buttons is selected: <label ng-repeat="SurveyType in SurveyTypes"> <input type="radio" name="SurveyTypeName" ng-model="surveyData.SurveyTypeN ...

jquery interval randomly selecting a new option instead of verifying the correct answer

Below is a jQuery game created with aspx that runs on a 3000 millisecond interval. $(document).ready(function () { var interval; //initialize timer interval = setInterval(function () { //create timer ...

Convert JavaBeans sources into a JSON descriptor

I'm in search of a tool or method to analyze standard JavaBeans source code (featuring getters and setters) and create json descriptors using tools like grunt or ant, or any other suitable option. Here's an example: FilterBean.java: package com ...