OpenID 2.0 Authentication in clojure with friend

The clojure friend library is an extensible authentication and authorization library for Clojure/Ring web applications and services. It is pretty new, concise, and absolutely usable. I gave it a try with an OpenID authentication which I needed anyway for a project. The official friend demo at https://github.com/cemerick/friend-demo is a great starting point, but I missed the details of how to handle failed authorizations, resource protection and roles in the OpenID part.

Friend allows you to apply authentication and authorisation to already defined ring routes, or protect individual pieces of code like this:

(defroutes routes
   (GET "/unprotected" 
     req (response/file "unprotected.html") )
   (GET "/protected" 
     req (friend/authorize #{::admin} (response/file "protected.html") ))

The existing routes can then subjected to extra authentication treatment supplying (optional) parameters and desired workflow(s), e.g.:

(def secured-app (handler/site
            (friend/authenticate
              routes
              {:allow-anon? true
               :default-landing-uri "/"
               :workflows [(openid/workflow
                             :openid-uri "/login"
                             :credential-fn . . .)]})))

Guarded pieces of code will throw an exception (ok, a slingshot stone) if security conditions are not met and friend will switch the necessary handlers: :unauthorized-handler or :unauthenticated-handler. The have default :unauthenticated-handler will start an interactive authentication by redirecting to :login-uri.

To get the OpenID authentication stared, an identifier containing the OpenID uri has to be POSTed to the :openid-uri:

<form id="login-form" action="/login" method="POST">
        <input type="hidden" name="identifier" id="login-identifier"
               value="https://www.google.com/accounts/o8/id"/>
        <input type="submit" value="Google"/>
    </form>

A real-world page would probably include more options than just google. On completion of the authentication proces, the workflow will end on :default-landing-uri or :login-failure-handler.

Any available authentication data will be available in the session under the :cemerick.friend/identity key:

{:current "https://www.google.com/accounts/o8/id?id=xxxx", 
 :authentications 
  {"https://www.google.com/accounts/o8/id?id=xxxxx" 
   {:roles #{:civi.core/admin}, 
   :email "assen.kolov@gmail.com", 
   :firstname "Assen", 
   :language "en", 
   :country "NL", 
   :lastname "Kolov", 
   :identity "https://www.google.com/accounts/o8/id?id=xxxxx"}}}

In the above map, :roles is a special entry. Its value has been provided by a call to the :credential-fn supplied as a parameter above. This function takes an identity map and returns one enriched with :roles. Roles can be a map or, from release 1.5.0 yesterday, a function returning such a map.

From the few available workflows I am mostly interested in the openid workflow at the moment. It relies on the openid4java library to handle the OpenID conversation. Regardless of the workflow, he guarded pieces of code (friend/authorize or friend/authenticated)

Here is a working configuration:

(def secured-app (handler/site
 (friend/authenticate routes
  {:allow-anon? true
   :default-landing-uri  "/"
   :unauthenticated-handler  (fn[_] (file "not-authorized.html"))
   :workflows [(openid/workflow
                login-failure-handler  (fn[_] (file "login-failed.html"))
                :openid-uri "/login"
                   ; everybody with an email is an admin
                :credential-fn #(-> % :email nil? not (if (merge % {:roles #{::admin}}))))]})))

The extra :unauthenticated-handler is absolutely necessary, because the default one redirecting to :login-uri of openid workflow fails. The reason is that an identifier parameter is expected by the workflow.

See all the code at https://github.com/kolov/friend-openid-demo. The application is running at http://open-friend.herokuapp.com/, Be patient, dormant heroku applications take a while to start.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Your email address will not be published.


7 + 7 =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>