diff --git a/pam/auth_client.c b/pam/auth_client.c
index 668b25a3e88f9751dcc0cdc4623ffec118758dbb..8c7d559d7a40e431432174221cefe5bff5e65be9 100644
--- a/pam/auth_client.c
+++ b/pam/auth_client.c
@@ -9,7 +9,7 @@
 #include <curl/curl.h>
 #include "auth_client.h"
 
-static const char *kAuthApiPath = "/api/v1/auth";
+static const char *kAuthApiPath = "/api/1/auth";
 
 struct auth_client {
   CURL *c;
@@ -68,86 +68,130 @@ const char *auth_client_strerror(int err) {
   }
 }
 
-struct responsebuf {
+/*
+ * A dynamically sized memory buffer that can be appended to, and will
+ * grow accordingly. It is optimized to perform well for a specific
+ * size (the initial allocation).
+ */
+struct cbuf {
   char *buf;
-  int size;
+  size_t alloc, size;
 };
 
-static void responsebuf_init(struct responsebuf *rbuf) {
-  rbuf->buf = (char *)malloc(1);
-  rbuf->size = 0;
+static void cbuf_init(struct cbuf *cbuf, size_t alloc) {
+  cbuf->buf = (char *)malloc(alloc);
+  cbuf->alloc = alloc;
+  cbuf->size = 0;
 }
 
-static void responsebuf_free(struct responsebuf *rbuf) {
-  free(rbuf->buf);
+static void cbuf_free(struct cbuf *cbuf) {
+  free(cbuf->buf);
+}
+
+static void cbuf_append(struct cbuf *cbuf, void *data, size_t size) {
+  // Resize if necessary.
+  size_t required_alloc = cbuf->size + size + 1;
+  if (required_alloc > cbuf->alloc) {
+    size_t new_alloc = cbuf->alloc;
+    while (new_alloc < required_alloc) {
+      new_alloc *= 2;
+    }
+    cbuf->buf = (char *)realloc(cbuf->buf, new_alloc);
+    cbuf->alloc = new_alloc;
+  }
+
+  // Append data to the buffer.
+  memcpy(cbuf->buf + cbuf->size, data, size);
+  cbuf->size += size;
+  cbuf->buf[cbuf->size] = '\0';
+}
+
+static char *quote(const char *s) {
+  char *out = (char *)malloc(strlen(s) * 3 + 1), *optr;
+  for (optr = out; *s; s++) {
+    switch (*s) {
+    case ';':
+    case '/':
+    case '?':
+    case ':':
+    case '@':
+    case '&':
+    case '=':
+    case '+':
+    case '$':
+    case ',':
+      sprintf(optr, "%%%02X", (int)(*s));
+      optr += 3;
+      break;
+    default:
+      *optr++ = *s;
+    }
+  }
+  return out;
 }
 
 static size_t responsebuf_callback(void *contents, size_t size, size_t nmemb, void *userp) {
   size_t realsize = size * nmemb;
-  struct responsebuf *rbuf = (struct responsebuf *)userp;
+  struct cbuf *cbuf = (struct cbuf *)userp;
 
-  rbuf->buf = (char *)realloc(rbuf->buf, rbuf->size + realsize + 1);
-  if (rbuf->buf == NULL) {
-    return 0;
-  }
-  memcpy(rbuf->buf + rbuf->size, contents, realsize);
-  rbuf->size += realsize;
-  rbuf->buf[rbuf->size] = 0;
+  cbuf_append(cbuf, contents, realsize);
   return realsize;
 }
 
+static void post_field_add(struct cbuf *form_data, const char *key, const char *value) {
+  char *quoted_key = quote(key), *quoted_value = quote(value);
+  if (form_data->size != 0) {
+    cbuf_append(form_data, "&", 1);
+  }
+  cbuf_append(form_data, quoted_key, strlen(quoted_key));
+  cbuf_append(form_data, "&", 1);
+  cbuf_append(form_data, quoted_value, strlen(quoted_value));
+  free(quoted_key);
+  free(quoted_value);
+}
+
 int auth_client_authenticate(auth_client_t ac,
                              const char *username,
                              const char *password,
                              const char *otp_token,
                              const char *source_ip) {
-  struct curl_httppost *formpost = NULL;
-  struct curl_httppost *formlast = NULL;
-  struct responsebuf rbuf;
+  struct curl_slist *headers = NULL;
+  struct cbuf form;
+  struct cbuf responsebuf;
   CURLcode res;
   int retval;
 
   // Build the POST request contents.
-  curl_formadd(&formpost, &formlast,
-               CURLFORM_COPYNAME, "service",
-               CURLFORM_COPYCONTENTS, ac->service,
-               CURLFORM_END);
-  curl_formadd(&formpost, &formlast,
-               CURLFORM_COPYNAME, "username",
-               CURLFORM_COPYCONTENTS, username,
-               CURLFORM_END);
+  cbuf_init(&form, 256);
+  post_field_add(&form, "service", ac->service);
+  post_field_add(&form, "username", username);
   if (password) {
-    curl_formadd(&formpost, &formlast,
-                 CURLFORM_COPYNAME, "password",
-                 CURLFORM_COPYCONTENTS, password,
-                 CURLFORM_END);
+    post_field_add(&form, "password", password);
   }
   if (otp_token) {
-    curl_formadd(&formpost, &formlast,
-                 CURLFORM_COPYNAME, "otp",
-                 CURLFORM_COPYCONTENTS, otp_token,
-                 CURLFORM_END);
+    post_field_add(&form, "otp", otp_token);
   }
   if (source_ip) {
-    curl_formadd(&formpost, &formlast,
-                 CURLFORM_COPYNAME, "source_ip",
-                 CURLFORM_COPYCONTENTS, source_ip,
-                 CURLFORM_END);
+    post_field_add(&form, "source_ip", source_ip);
   }
-  curl_easy_setopt(ac->c, CURLOPT_HTTPPOST, formpost);
+  curl_easy_setopt(ac->c, CURLOPT_POSTFIELDS, form.buf);
+
+  // Set request headers.
+  curl_slist_append(headers, "Content-Type: application/x-form-www-urlencoded");
+  curl_easy_setopt(ac->c, CURLOPT_HTTPHEADER, headers);
 
-  responsebuf_init(&rbuf);
+  cbuf_init(&responsebuf, 64);
   curl_easy_setopt(ac->c, CURLOPT_WRITEFUNCTION, responsebuf_callback);
-  curl_easy_setopt(ac->c, CURLOPT_WRITEDATA, (void *)&rbuf);
+  curl_easy_setopt(ac->c, CURLOPT_WRITEDATA, (void *)&responsebuf);
 
   res = curl_easy_perform(ac->c);
   if (res == CURLE_OK) {
     // Check the auth server response.
-    if (!strncmp(rbuf.buf, "OK", 2)) {
+    if (!strncmp(responsebuf.buf, "OK", 2)) {
       retval = AC_OK;
-    } else if (!strncmp(rbuf.buf, "OTP_REQUIRED", 12)) {
+    } else if (!strncmp(responsebuf.buf, "OTP_REQUIRED", 12)) {
       retval = AC_ERR_OTP_REQUIRED;
-    } else if (!strncmp(rbuf.buf, "ERROR", 5)) {
+    } else if (!strncmp(responsebuf.buf, "ERROR", 5)) {
       retval = AC_ERR_AUTHENTICATION_FAILURE;
     } else {
       retval = AC_ERR_BAD_RESPONSE;
@@ -156,8 +200,9 @@ int auth_client_authenticate(auth_client_t ac,
     retval = auth_client_err_from_curl(res);
   }
 
-  curl_formfree(formpost);
-  responsebuf_free(&rbuf);
+  cbuf_free(&form);
+  cbuf_free(&responsebuf);
+  curl_slist_free_all(headers);
 
   return retval;
 }