Skip to content

Instantly share code, notes, and snippets.

@prestancedesign
Last active August 5, 2024 16:45
Show Gist options
  • Save prestancedesign/e0da3f57d8a07855c2d9fcb26198f0b4 to your computer and use it in GitHub Desktop.
Save prestancedesign/e0da3f57d8a07855c2d9fcb26198f0b4 to your computer and use it in GitHub Desktop.
Ring session authentication with Reitit
(ns authexample.web
(:gen-class)
(:require [buddy.auth :refer [authenticated? throw-unauthorized]]
[buddy.auth.backends.session :refer [session-backend]]
[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
[clojure.java.io :as io]
[compojure.response :refer [render]]
[reitit.ring :as ring]
[ring.adapter.jetty :as jetty]
[ring.middleware.params :refer [wrap-params]]
[ring.middleware.session :refer [wrap-session]]
[ring.util.response :refer [redirect]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Controllers ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Home page controller (ring handler)
;; If incoming user is not authenticated it raises a
;; not authenticated exception, else it simply shows a
;; hello world message.
(defn home
[request]
(if-not (authenticated? request)
(throw-unauthorized)
(let [content (slurp (io/resource "index.html"))]
(render content request))))
;; Login page controller
;; It returns a login page on get requests.
(defn login
[request]
(let [content (slurp (io/resource "login.html"))]
(render content request)))
;; Logout handler
;; Responsible for clearing the session.
(defn logout
[_]
(-> (redirect "/login")
(assoc :session {})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Authentication ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def authdata
"Global var that stores valid users with their
respective passwords."
{:admin "secret"
:test "secret"})
;; Authentication Handler
;; Used to respond to POST requests to /login.
(defn login-authenticate
"Check request username and password against authdata
username and passwords.
On successful authentication, set appropriate user
into the session and redirect to the value of
(:next (:query-params request)). On failed
authentication, renders the login page."
[request]
(let [username (get-in request [:form-params "username"])
password (get-in request [:form-params "password"])
session (:session request)
found-password (get authdata (keyword username))]
(if (and found-password (= found-password password))
(let [next-url (get-in request [:query-params "next"] "/")
updated-session (assoc session :identity (keyword username))]
(-> (redirect next-url)
(assoc :session updated-session)))
(let [content (slurp (io/resource "login.html"))]
(render content request)))))
;; User defined unauthorized handler
;;
;; This function is responsible for handling
;; unauthorized requests (when unauthorized exception
;; is raised by some handler)
(defn unauthorized-handler
[request metadata]
(cond
;; If request is authenticated, raise 403 instead
;; of 401 (because user is authenticated but permission
;; denied is raised).
(authenticated? request)
(-> (render (slurp (io/resource "error.html")) request)
(assoc :status 403))
;; In other cases, redirect the user to login page.
:else
(let [current-url (:uri request)]
(redirect (format "/login?next=%s" current-url)))))
;; Create an instance of auth backend.
(def auth-backend
(session-backend {:unauthorized-handler unauthorized-handler}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Routes and Middlewares ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def app
(ring/ring-handler
(ring/router
[["/" home]
["/login" {:get login
:post login-authenticate}]
["/logout" logout]]
{:data
{:middleware [[wrap-authorization auth-backend]
[wrap-authentication auth-backend]
wrap-params]}})
(ring/create-default-handler)
{:middleware [wrap-session]}))
(defn -main []
(jetty/run-jetty #'app {:port 3000
:join? false}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment