How to proper authenticate an AngularJS client to the server

All we need is an easy explanation of the problem, so here it is.

I’m building an AngularJS web-app that uses a RESTful API (Jersey).

On the server side I am using a Java Application Server (in detail Glassfish 4).

My setup is as follows:

  • AngularJS webapp is deployed as a single war file to the Java EE Application server.
  • The RESTfulAPI (and the backend logic) is also deployed as a war file to the same Java EE Application server.
  • The AngularJS webapp calls the REST API to get the data it needs.
  • The RESTful API:
    • /api/public/* for public not restricted access (e.g. /api/public/users/signup to register as a new user). Any user is allowed to access this area. No login required
    • /api/private/* for restricted access (e.g. /api/private/account/{id} to retrieve some account specific data)
  • The private resources are protected with the Java EE internal security concepts (see below for web.xml details).

Web.xml

<security-constraint>
    <web-resource-collection>
        <web-resource-name>PRIVATE REST API</web-resource-name>
        <url-pattern>/private/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
        <http-method>HEAD</http-method>
        <http-method>PUT</http-method>
        <http-method>OPTIONS</http-method>
        <http-method>TRACE</http-method>
        <http-method>DELETE</http-method>
    </web-resource-collection>
    <auth-constraint>
        <description>Have to be a USER</description>
        <role-name>USERS</role-name>
    </auth-constraint>
</security-constraint>
<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>userauth</realm-name>
</login-config>
<security-role>
    <description/>
    <role-name>USERS</role-name>
</security-role>

This setup works for me, but only if I invoke the URLs manually in the browser.
If I am not authenticated and address a secured area, the browser asks for username and password. If the provided credentials are valid, I from there on have access to the restricted area: Fine.

But how do I get that working with AngularJS?
To login there is a POST API call: /api/public/users/login where you have to provide credentials from a form (username and password).

The client side code is:

ctrls.controller('LoginController', function($scope, $http, $log, $location, UserService, RemoteServerHelperService) {
    $scope.loginDataWrong = undefined;
    $scope.login = function(credentials) {
    $http.post(RemoteServerHelperService.buildURL('/public/users/login'), credentials)
        .success(function (data, status) {
            UserService.setLoggedIn(credentials.email);
            $scope.loginDataWrong = undefined;
            $location.path('/app');
            $log.info("login attempt: successfull. User.loggedIn:" + UserService.isLoggedIn());
        }).error(function (data, status, headers, config) {
            UserService.invalidate();
            $scope.loginDataWrong = true;
            $log.info("login attempt: failed. User.loggedIn:" + UserService.isLoggedIn());
        });
    };
});

The client side code seems to work. I also have some routes in place to secure content on the client side, and so on. There are several posts out there which describe the client side code in detail. So this “snippet” should be enough.

The server side code is:

@POST
@Path("/login")
@Consumes(MediaType.APPLICATION_JSON)
public Response login(Credentials credentials, @Context HttpServletRequest request) {
    if (isAlreadyAuthenticated(request)) {
        try {
            request.logout();
        } catch (ServletException ex) {
            return Response.status(Status.CONFLICT).build();
        }
    }

    // not authenticated
    try {
        request.login(credentials.getEmail(), credentials.getPassword());
        return Response.ok().build();
    } catch (ServletException ex) {
        return Response.status(Status.CONFLICT).build();
    }
}

But however, this does not “authenticate” the client side for further requests. When I make a API call on a restricted area after successfully login I get a 403 FORBIDDEN response from the server side. So I assume that I am doing something wrong. Do you have any ideas how to authenticate the client to the server for further requests?

A possible solution could be to switch to FORM-based authentication and simply invoke j_security_check with j_username and j_password, but for now I want to stick with BASIC-Authentication and perform the authentication manually via the RESTful API.

The setting $httpProvider.defaults.withCredentials = true; somehow doesn’t seem to have any effect.

Update:

Thanks to the tip of lossleader I solved the problem.
I removed the login() method, because I want the Java EE-Server to take care of authentication.

To access secured areas the AngularJS webapp has to set the HTTP-Header correctly. In my case $http.defaults.headers.common['Authorization'] = 'Basic <username:pw>'; where username:password has to be Base64 encoded.

After I set the headers correctly, no Password Prompt is shown and the AngularJS webapp can access the REST API.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Thanks to the tip of lossleader I solved the problem. I removed the login() method, because I want the Java EE-Server to take care of authentication.

To access secured areas the AngularJS webapp has to set the HTTP-Header correctly. In my case $http.defaults.headers.common[‘Authorization’] = ‘Basic ‘; where username:password has to be Base64 encoded.

After I set the headers correctly, no Password Prompt is shown and the AngularJS webapp can access the REST API.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply