diff --git a/cmd/auth-server-http-endpoint/main.go b/cmd/auth-server-http-endpoint/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2ecfdb37044c37f0e37fe963ef5ee2b9305d9d5
--- /dev/null
+++ b/cmd/auth-server-http-endpoint/main.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"log"
+	"net/http"
+
+	"git.autistici.org/id/auth"
+	"git.autistici.org/id/auth/client"
+)
+
+func main() {
+	var port int
+
+	flag.IntVar(&port, "port", 0, "A port to bind to on the specified addresses")
+	flag.Parse()
+
+	if port == 0 {
+		port = 4041
+	}
+
+	log.Fatal(http.ListenAndServe(
+		fmt.Sprintf("127.0.0.1:%d", port),
+		http.HandlerFunc(authHandler)))
+}
+
+type authPayload struct {
+	User string `json:"username"`
+	Pass string `json:"password"`
+}
+
+func authHandler(w http.ResponseWriter, r *http.Request) {
+	var p authPayload
+	err := json.NewDecoder(r.Body).Decode(&p)
+	if err != nil {
+		log.Printf("malformed request: %s", err)
+		w.WriteHeader(http.StatusBadRequest)
+		return
+	}
+
+	c := client.New(client.DefaultSocketPath)
+	resp, err := c.Authenticate(r.Context(), &auth.Request{
+		Service:  "xmpp",
+		Username: p.User,
+		Password: []byte(p.Pass),
+	})
+	if err != nil {
+		log.Printf("auth error: %s", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	switch resp.Status {
+	case auth.StatusOK:
+		w.WriteHeader(http.StatusOK)
+		return
+	case auth.StatusInsufficientCredentials:
+		w.WriteHeader(http.StatusForbidden)
+		return
+	case auth.StatusError:
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}