James Williams
LinkedInMastodonGithub

Basic Authentication with Ratpack

In this post, we're going to cover how to create a AJAX login request using JQuery. While the format of the request is specific to JQuery, any front side JS library will do. If you are new to Ratpack, check out my introductory post here.

Basic Authentication receives access to a resource by submitting a request and attaching a header formed by Base64 encoding the concatenation the username, a colon, and the password. It is easy to implement but relies on the connection between the client and the server be secure. A MITM attack could easily compromise data if it is transmitted using HTTP.

Starting from the front end, I created a HTML file to hold our AJAX code, show below. One thing that is useful to note is that although it appear to be a regular HTML file, it is also a Groovy template. That is why the $ that JQuery uses have to be escaped. The form takes a username and password, encodes them, and sets the header. On success, the result text is pushed to a div.

    <html>
    <head>
        <title>Login with Ratpack</title>
        <script src="/js/jquery.js"></script>
        <script src="/js/jquery.base64.js"></script>
        <script>
        function login() {
            var username = $("#username").val();
            var password = $("#password").val();
            $.ajax({
                    'type':'POST',
                    'url': 'http://localhost:5000/login',
                    'otherSettings': 'othervalues',
                    'beforeSend': function(xhr) {
                        xhr.setRequestHeader("Authorization", 
                                                      "Basic " + $.base64.encode(username + ":" + password))
                    },
                    success: function(result) {
                        $("#results").html(result);
                    }
            });
        }
        </script>
    </head>
    <body>
            <h2>Login Form with Ratpack</h2>
            <div>
                <span>Username:</span><input id="username" type="text" columns=20>
            </div>
            <div>
                <span>Password:</span><input id="password" type="password" columns=20>
            </div>
            <button onclick='login()' >Login</button>
            <div id="results"/>
    </body>
    </html>

Next, let's move to the backend. Let's start by creating a helper to assist with authentication requests. The handler takes a HttpServletRequest in as a parameter and attempts to pull off the Authorization header. From that header, it retrieves the encoded username and password (in non-demo code, use try-catch blocks, do as I say not as I do). The demo punts on any type of strong validation but you can see where it would go. We end by using meta-magic to attach the doAuth function to the application.

    class Auth {
        static doAuth = {app ->
            def handler = { req ->
                def authHeader = req.getHeader("Authorization")
                def encodedValue = authHeader.split(" ")[1]
                def decodedValue = new String(encodedValue.decodeBase64())?.split(":")
                // do some sort of validation here
                if (decodedValue[0] == "") {
                    return "Unauthorized"
                } else {
                    decodedValue
                }
            }
            app.metaClass.doAuth = handler;
        }
    }

Last but not least, we get to lay out the routes. After setting up the public and template directories, we render the HTML we declared above on "/." When the login button is clicked, a POST request is made to "/login" which executes the doAuth helper function on the request. If the auth was successful, the server echoes back the username and password submitted. Otherwise, it returns "Unauthorized." Before instructing the servlet to execute the app, we call the static doAuth function on app so that it can add itself to app's metaclass.

   def app = Ratpack.app {
      set 'public', 'public'
      set 'templateRoot', 'templates'

      get("/") {
          render 'index.html'
      }

      post("/login") {
          result = doAuth(request)
          if (result != "Unauthorized") {
              "Logged in with username ${result[0]} and password ${result[1]}"
          } else {
              "Unauthorized"
          }
      }
    }
    Auth.doAuth(app)
    RatpackServlet.serve(app)