Jose Juan MontielJose Juan Montiel

And now what…​

After the examples discussed in the first part, where the protagonist was only the browser and the html, and where we have seen how a page can invoke (and is normal) images from other domains, if in the invocation urls it is pass parameters, they arrive at the destination server, and if this generates a session, the browser is responsible for creating the necessary cookies to maintain it.

But the thing changes, when javascript is in charge of making the request, and it is also another domain. Cors and cookies management will be the main problems. Let’s see.

Authentication

To take the use of the session a bit further, we are going to create a page of login. Remember that here, A represents the 3rd party, and B the page whereby we sail

With this login page

<form name='f' action="login" method='POST'>
   <table>
      <tr>
         <td>User:</td>
         <td><input type="text" id="username" name="username" value="" /></td>
      </tr>
      <tr>
         <td>Password:</td>
         <td><input type="password" id="password" name="password" /></td>
      </tr>
      <tr>
         <td><input name="submit" type="submit" value="submit" /></td>
      </tr>
   </table>
</form>

We can call this driver, where if the user / password matches admin / password we save in session, with the user key, the value of the username.

    @PostMapping("/login")
    public String login(
        @RequestParam(value="username", required=false) String username,
        @RequestParam(value="password", required=false) String password,
        HttpSession session) {

      if (session.getAttribute("user")!=null) {
        return "redirect:/isLogged";
      } else if ("admin".equals(username) && "password".equals(password)) {
        session.setAttribute("user", username);
        return "redirect:/isLogged";
      } else {
        return "redirect:/login";
      }
    }

And after that, we redirected to the isLogged page, where it shows if we are logged.

<p th:unless="${session == null}" th:text="'User logged: ---' + ${session.user} + '---'" />
<p th:text="'isLogged? ' + ${isLogged}" />

where the value true / false comes from that it is admin, that value in session for user.

    model.addAttribute("isLogged","admin".equals(session.getAttribute("user")));

To test the operation, we set up a logout endpoint, which invalidates the session.

    @GetMapping("/logout")
    public String login(HttpSession session) {
      session.invalidate();
      return "redirect:/login";
    }

Now, start the interesting part of the experiment, let’s simulate what the browser would do for us if we mount a login form in our page B to another domain A, but in such a way that it is done by javascript and without refreshing the page.

    function loginRemoto() {
      var username = document.getElementById("username").value; (1)
      var password = document.getElementById("password").value; (2)

      var http = new XMLHttpRequest();
      var url = "http://a.127.0.0.1.xip.io:9000/login";
      var params = "username="+username+"&password="+password;
      http.open("POST", url, true); (3)

      //Send the proper header information along with the request
      http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); (4)

      http.onreadystatechange = function() { //Call a function when the state changes.
          if(http.readyState == 4 && http.status == 200) {
            var jsessionid = getJSessionId(http.responseURL); (5)
            document.cookie = "jsessionfroma="+jsessionid;
          }
      }
      http.send(params);
    }
1 We obtain the user
2 We get the password
3 We build a POST request to the login of A
4 This is the content-type that the browser sends in the forms
5 This we will see later

Access-Control-Allow-Origin

The problem is that, the browser can let us ask in A, images of B, but sending requests XMLHttpRequest (or what we have known by ajax) and They are another subject. And the issue is that in this case, what would the receiver of this request, by default does not allow the reception of requests from another domain different from yours. Aqui They detail how this works. And aqui they tell how Spring manages the theme.

JS APIs for third-party services on your Web

At this point, you might get to ask yourself, because certain types of websites, that allow you to use their services on your website, or being a little more precise, allow your users to use their services on your website, they need register the url of your web.

Well, I never knew, but after these proofs of concept, I would say that because they need to configure their servers, so that when the "remote address" of the request is the same as that of any of those who have registered "clients", return an "Access-Control-Allow-Origin" with that host.

This would be the filter, which controls this.

    @Component
    public class CorsFilter extends OncePerRequestFilter {

        @Override
        protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
                                        final FilterChain filterChain) throws ServletException, IOException {
            response.addHeader("Access-Control-Allow-Origin", "b.127.0.0.1.xip.io");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, HEAD, OPTIONS");
            response.addHeader("Access-Control-Allow-Headers", "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
            response.addHeader("Access-Control-Expose-Headers", "Access-Control-Allow-Origin, Access-Control-Allow-Credentials");
            response.addHeader("Access-Control-Allow-Credentials", "true");
            response.addIntHeader("Access-Control-Max-Age", 10);
            filterChain.doFilter(request, response);
        }
    }

And the cookies?

Recall, that the goal is that from a page of B, there is a Javascript that connects with A to validate a user / password and that later, B can read a cookie generated by that Javascript, really in B, to make a call from server B to server A, posing as the user of B, that I make the request to A, to save in session B, the confidence in that login made in A. The flow that we describe at the end of the first article.

First, remember the step (5) that we leave without explaining

    var jsessionid = getJSessionId(http.responseURL); (5)
    document.cookie = "jsessionfroma="+jsessionid;

is the one that is responsible for setting the cookie in B. There are more ways, that I have not explored, but in this case, in the url returned by the login process, as the server A sees that B can not receive a cookie, generates a redirect url that carries as parameter the JSESSIONID, value to be able to correlate the session that we has generated A to us that we navigate from B, and we have launched the XMLHttpRequest of login.

So we parse that URL (getJSessionId) and we keep the JSESSIONID and what We keep in a cookie (document.cookie) of B, which is where we are browsing.

Now, we evolved the isLogged driver code of B (and A, it’s the same code, although we are now navigating from B) to:

    @GetMapping("/isLogged")
    	public String isLogged(@CookieValue(value="jsessionfroma", required=false) String jsessionfroma, (1)
      HttpSession session, Model model) {
    		model.addAttribute("isLogged","admin".equals(session.getAttribute("user")));

    		if (jsessionfroma!=null) {  (2)
    			ParameterizedTypeReference<String> typeRef = new ParameterizedTypeReference<String>() {};
    			HttpHeaders requestHeaders = new HttpHeaders();
    			requestHeaders.add("Cookie", "JSESSIONID=" + jsessionfroma + "; domain=a.127.0.0.1.xip.io;");  (3)
    			HttpEntity requestEntity = new HttpEntity(null, requestHeaders);

    			ResponseEntity<String> response = restTemplate.exchange("http://a.127.0.0.1.xip.io:9000/isLogged", HttpMethod.GET, requestEntity, typeRef);

    			boolean isRemoteLogged = response.getBody().contains("isLogged? true");  (4)

    			Pattern pattern = Pattern.compile("---(.*?)---");
    			Matcher matcher = pattern.matcher(response.getBody());
    			String username = "";
    			while (matcher.find()) {
    				username = matcher.group(1);  (5)
    			}
    			if (isRemoteLogged) {
    				session.setAttribute("user",username);  (6)
    			}

    			System.out.println(response.getBody());
    		}

    		return "isLogged";
    	}
1 Read the cookie jsessionfroma, which is what we generated from Javascript that I call A from B to do the login. The "step 5"
2 If we have that cookie, then we proceed to make the call to A from the back of B.
3 In the request that we are going to make to A, we are going to add the header cookie, with the value of the JSESSIONID obtained, that is, we will send it to A the cookie for that sees that we were logado.
4 We pause the answer, to see if in A we paint that we are logados.
5 We are left with the username logado.
6 And if we are logged in the remote, then we save in the session of B, the logged user

Therefore, in this way we have managed to achieve from Javascript in a service of third parties, and from a back communication, with that service and with the cookie that that javascript has generated us, check that we are correctly logged, to log us automatically on our website.

The code

In this repository from github.

But it gives me, that I have many questions left in the pipeline, and other approaches possible. JSON-P? Google advertising? Security issues? SSO? Proxy of services in Apache or Nginx? JWT?

A chapter 3?