This post is part of the Web Application Development with Clojure tutorial. You might want to read the previous posts before this post for continuity’s sake.

Introduction

Over that past 4 parts you have seen how to start with creating a simple blog using Clojure, render the posts and finally how to add a simple login page that will check the username and password and redirect to “admin” page. In this part we’ll finish the administration area of the blog that will help us create new blog posts.

Source code on github

The code for this series is now available on github and the source code is tagged with part names. If you want to checkout the code for a specific part of this tutorial you can do so using the following command:

git clone git://github.com/vijaykiran/clog.git cd clog git checkout part5 1 2 3 git clone git : / / github .com / vijaykiran / clog .git cd clog git checkout part5

Sessions

You can read more about ring’s session and cookie store here and here

When a user logs into the “admin” area using our login form, we need to keep track of her session and allow them to perform administrative operations such as managing posts or setting their password etc. Ring library provides a simple session middleware that uses various session storage options – cookie, in-memory and database backed session stores. You can create your own session implementations by implementing the SessionStore protocol. For our application we’ll just use the cookie-store to store the session information.

First step in creating the session is using the wrap-session function. We’ll add this to our routes and make sure that the session is wrapped for all the starting with “/admin”. Update your routes definition in core.clj as follows:

;; Routes definition (def routes (app (wrap-file "resources/public") (wrap-params) (wrap-session {:cookie-name "clog-session" :store (cookie-store)}) ["login"] (delegate login) ["admin"] (delegate admin) [""] (delegate index) [id] (delegate post id))) 1 2 3 4 5 6 7 8 9 10 ;; Routes definition ( def routes ( app ( wrap-file "resources/public" ) ( wrap-params ) ( wrap-session { : cookie-name "clog-session" : store ( cookie-store ) } ) [ "login" ] ( delegate login ) [ "admin" ] ( delegate admin ) [ "" ] ( delegate index ) [ id ] ( delegate post id ) ) )

Also make sure that you add the necessary namespaces to the core.clj namespace.

(ns clog.core (:use ring.adapter.jetty ring.middleware.resource ring.middleware.reload ring.util.response ring.middleware.file ring.middleware.params ring.middleware.session ring.middleware.session.cookie net.cgrand.moustache clog.controller)) 1 2 3 4 5 6 7 8 9 10 11 ( ns clog . core ( : use ring . adapter . jetty ring . middleware . resource ring . middleware . reload ring . util . response ring . middleware . file ring . middleware . params ring . middleware . session ring . middleware . session . cookie net . cgrand . moustache clog . controller ) )

When a user logs in, we can use the session to store the username, and in subsequent requests to admin pages, we’ll verify if the session contains the username otherwise we send the user back to login page. First let us update the login method in the controller to make sure that we set the “username” on the session cookie.

(defn login "Login Handler" [req] (let [params (:params req)] (if (empty? params) (response (login-page)) (if (= (get params "username") (get params "password")) (assoc (redirect "/admin") :session {:username (get params "username")}) (response (login-page "Invalid username or password")))))) 1 2 3 4 5 6 7 8 9 ( defn login "Login Handler" [ req ] ( let [ params ( : params req ) ] ( if ( empty ? params ) ( response ( login-page ) ) ( if ( = ( get params "username" ) ( get params "password" ) ) ( assoc ( redirect "/admin" ) : session { : username ( get params "username" ) } ) ( response ( login-page "Invalid username or password" ) ) ) ) ) )

When the username is valid according to our logic – we set the session map during redirect. This will set a cookie with the name “clog-session” when the user is logged in. You can check the cookie in your browser’s inspector.

Now we’ll tweak the admin handler to check whether the username is set on the session cookie, if not, we’ll redirect the user to the login page.

(defn admin "Admin handler" [req] (let [username (:username (:session req))] (if (nil? username) (redirect "/login") (response "Admin Page")))) 1 2 3 4 5 6 7 ( defn admin "Admin handler" [ req ] ( let [ username ( : username ( : session req ) ) ] ( if ( nil ? username ) ( redirect "/login" ) ( response "Admin Page" ) ) ) )

The next step is to add a logout function that will clear the session to make sure the username key is removed from the session.

defn logout "Logout handler" [req] (assoc (redirect "/") :session nil)) 1 2 3 4 defn logout "Logout handler" [ req ] ( assoc ( redirect "/" ) : session nil ) )

We also need to update our routes to make sure that”/logout” works as expected. Just add the new logout handler to the routes in core.clj as follows:

(def routes (app (wrap-file "resources/public") (wrap-params) (wrap-session {:cookie-name "clog-session" :store (cookie-store)}) ["login"] (delegate login) ["logout"] (delegate logout) ["admin"] (delegate admin) [""] (delegate index) [id] (delegate post id))) 1 2 3 4 5 6 7 8 9 10 ( def routes ( app ( wrap-file "resources/public" ) ( wrap-params ) ( wrap-session { : cookie-name "clog-session" : store ( cookie-store ) } ) [ "login" ] ( delegate login ) [ "logout" ] ( delegate logout ) [ "admin" ] ( delegate admin ) [ "" ] ( delegate index ) [ id ] ( delegate post id ) ) )

After these changes, when you try to access “/admin” without logging in you’ll be redirected to the login page as expected. Browsing to “/logout” page will “clear” your session redirecting you back to the home page of the blog.

Since we are now having only one “secure” page, we just added the session check to the admin handler function itself. Clearly this isn’t a nice way, since in a production version of your blog, you might have many more admin pages such as user settings, post settings etc. This approach of verifying the session in every function look pretty stupid in that case.

You can move the session checking logic to a different security wrapper handler which will check the request before forwarding to the next handler. If this security wrapper handler sees that the url is one of the “secure” urls, it will verify the session. When the session doesn’t contain the username key, it can redirect to the login page.

Blog post admin page

Now we have our simple security in place, let us finish our blog posting function that will allow us to create a new post. First create the admin html page which contains the form for the blog post.

Clog - Admin<script src="/bootstrap/js/html5.js" type="text/javascript"></script> 1 Clog - Admin <script src = "/bootstrap/js/html5.js" type = "text/javascript" > </script>

1

New Post Title Content </text>

</div>

<p>

<br />

<div class=”form-actions”>

<button type=”submit” class=”btn btn-primary”>Save</button>

</div>

<p>

</form>

<p>

</div>

<p>

</div>

<p>

</body>

</html> </pre>

<p>The admin page contains a simple form with title field and a text area for the post content. We also added a logout link in case the user wants to logout of the admin area. The above html should be placed into <em>clog/resources/admin.html</em> along with other templates.</p>

<p>Update the <em>templates.clj</em> and create a oneliner that will use this template.</p>

<pre lang=”clojure”>

(deftemplate admin-page “admin.html” [])

</pre>

<p>Now replace the <em>admin</em> handler in <em>controller.clj</em> so that it will render the admin template as response.</p>

<pre lang=”clojure”>

(defn admin

“Admin handler”

[req]

(let [username (:username (:session req))]

(if (nil? username)

(redirect “/login”)

(response (admin-page)))))

</pre>

<p>As you can see in the admin html, the form is posting the content to the same <em>admin</em> URL. So we need to capture that form parameters and update the database with the post details.</p>

<div class=”asideBlock”>Clearly this code looks super-contrived, but this is just to make things super simple and as straight forward as possible. You should use more idiomatic clojure to make this code look good.</div>

<pre lang=”clojure”>

(defn admin

“Admin handler”

[req]

(let [username (:username (:session req))

params (:params req)]

(if (nil? username)

(redirect “/login”)

(do

(if-not (empty? params)

(let [id (inc (count (select posts)))

author-id (:id (first (select authors (fields :id) (where {:username username}))))]

(insert posts (values (assoc params

:id id

:author author-id)))))

(response (admin-page)))))) </pre>

<p>Once this function is in, you can now login to the blog admin area using jane or john and add a new post to the blog.</p>

<h2>Conclusion</h2>

<p>This concludes the part 5 of this series. In the next and final part, I’ll discuss what sort of deployment options available and how you can deploy your Clog engine to “production”. As you can see the webapp we built so far is no where near to a production standard application, but this should help you get started with developing your own apps. You can now enhance clog with list of posts, deleting posts, adding comments etc. by just using the conceptual framework presented in this series. If you have any questions, feel free to ask in the comments of this post.</p>

<p>Make sure you <a href=”http://vijaykiran.com/feed”>subscribe</a> to the RSS feed or follow me on <a href=”http://twitter.com/vijaykiran”>Twitter</a> to get notified.</p>

