kabel-auth
December 23, 2025 · View on GitHub
Authentication middleware for kabel. Provides multiple authentication strategies for WebSocket connections:
- JWT validation (HS256, RS256) for token-based auth
- Password hashing via bcrypt for traditional auth flows
- Passwordless authentication via email/SMS token verification
- Session middleware for attaching identity to messages
- HTTP routes for auth endpoints (login, register, refresh)
- Pluggable storage for tokens and user data
Used in replikativ to build authenticated p2p networks.
Installation
Add to your dependencies:
;; deps.edn
{:deps {io.replikativ/kabel-auth {:mvn/version "LATEST"}}}
JWT Authentication
Validate JWT tokens on WebSocket upgrade using the authenticated http-kit handler:
HS256 (Shared Secret)
(require '[kabel-auth.http-kit :as auth-hk]
'[kabel-auth.jwt :as jwt]
'[superv.async :refer [S]])
(def validate-request!
(jwt/build-bearer-validator {:alg :HS256
:secret "your-secret-key"
:required-claims {:iss "your-issuer" :aud "your-audience"}}))
(def handler
(auth-hk/create-authenticated-http-kit-handler! S "ws://localhost:8080/ws" :peer-id validate-request!))
RS256 (Public Key)
(def validate-rs256!
(jwt/build-bearer-validator {:alg :RS256
:public-key "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"}))
(def handler
(auth-hk/create-authenticated-http-kit-handler! S "ws://localhost:8080/ws" :peer-id validate-rs256!))
Messages received on the input channel will include :kabel/principal when authentication succeeds.
Password Hashing
Secure password hashing via bcrypt (buddy-hashers):
(require '[kabel-auth.password :as password])
;; Hash a password
(def hashed (password/hash-password "user-password"))
;; Verify a password
(password/verify-password "user-password" hashed) ;; => true
Session Middleware
Attach identity to inbound messages and strip local metadata from outbound:
(require '[kabel-auth.session :as session]
'[superv.async :refer [S]])
(defn session-fn [S peer msg]
(when-let [u (:user msg)]
{:kabel/principal {:user u}}))
(def wrapped [S peer-ch]
(session/session-middleware session-fn [S peer-ch]))
;; Outbound messages will have :kabel/* keys removed automatically
HTTP Routes
Reitit-based HTTP routes for auth endpoints:
(require '[kabel-auth.routes :as routes])
;; Create auth routes with your store and config
(def auth-routes (routes/auth-routes store config))
;; Mount in your Reitit router
Pluggable Storage
Protocol-based storage with a memory implementation included:
(require '[kabel-auth.store.protocol :as store-proto]
'[kabel-auth.store.memory :as memory-store])
;; Create an in-memory store
(def store (memory-store/create-memory-store))
;; Implement the protocol for your own storage backend
Passwordless Authentication
The original passwordless flow for email/SMS-based authentication:
Instead of asking users for a password when they try to log in, just ask them for their username (or email or mobile phone number). Create a temporary authorization code on the backend and store it in your database. Send the user an email or SMS with a link that contains the code. The user clicks the link which opens your app and sends the authorization code to your backend. Verify that the code is valid and exchange it for a long-lived token.
(require '[kabel-auth.core :refer [auth inbox-auth register-external-token external-tokens]]
'[postal.core :refer [send-message]]
'[superv.async :refer [S]])
(auth (atom #{"trusted-peer.com" "localhost" "127.0.0.1"})
receiver-token-store ;; konserve store for receiver tokens
sender-token-store ;; konserve store for sender tokens
;; decide which messages need protection
(fn [{:keys [type]}] (or ({:state-changing-msg-type :auth} type)
:unrelated))
;; notification when authentication is needed
(fn [protocol user] (alert! "Check channel " protocol " for " user))
;; send authentication link
(fn [{:keys [protocol token user]}]
(let [ext-tok (register-external-token token)]
(send-message {:host "smtp.your-host.com"}
{:from "no-reply@your-host.com"
:to user
:subject "Please authenticate"
:body (str "Visit http://your-end-point/auth/" ext-tok)})))
[S peer [in out]])
Provide an endpoint for authentication:
(routes
(GET "/auth/:token" [token]
(put! inbox-auth {:token (@external-tokens (java.util.UUID/fromString token))})))
A full example using an early prototype of kabel-auth can be found in topiq.
Build
# Run tests
clj -M:test
# Check code formatting
clj -M:format
# Auto-fix formatting
clj -M:ffix
# Build JAR
clj -T:build jar
# Deploy to Clojars
clj -T:build deploy
Development
For local development against a sibling kabel checkout:
clj -M:dev:test
The :dev alias overrides kabel with {:local/root "../kabel"}.
License
Copyright © 2016-2025 Christian Weilbach
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.