This is a simple OAuth 2 provider that is designed to be used as a primary authentication provider for a Clojure Ring app.
It currently handles OAuth2 bearer authentication and interactive authentication.
See draft-ietf-oauth-v2-bearer
The following bearer tokens are implemented:
Add the following dependency to your project.clj
file:
[clauth "1.0.0-rc17"]
The oauth models have been refactored to be simple maps instead of records. The factory functions are simpler and take a simple map of data.
If you have your own token and authcode creator functions for versions prior to 1.0.0-rc14 please verify that they work when upgrading.
There are currently 2 middlewares defined:
Both of them take as a parameter a function which should return a object representing the token. This could be a user object, but could also be a token object with specific meta-data. I may standardize on something when more of the framework is developed.
The object returned by your function is set to :access-token entry in the request.
The difference between wrap-bearer-token and require-bearer-token! is that wrap will find a token but not require it. require-bearer-token will return a HTTP 401 header.
Currently the following Grant types are supported:
Grant types are implemented using multimethods. To implement one
(defmethod token-request-handler "my_grant_type" [req authenticator] ...)
We currently support the following authorization requests:
You can control the ones you wish to support by using the configuration parameter :allowed-response-types to the authorization-handler
(authorization-handler {:allowed-response-types ["code"]}) ;; Only support Authorization Code Grants
Implement custom authorization requests:
(defmethod authorization-request-handler "custom"
... )
There is a protocol defined called Expirable which implements one function:
(is-valid? token)
This is implementend by IPersistentMap so {} represents a valid token where {:expires (date-time 2011)} is invalid.
A OAuthToken map can be instantiated and stored easily by the create-token function:
(create-token client user)
A ClientApplication map can be instantiated and stored easily by the register-app function:
(register-app name url)
A client application has a client-id and a client-secret which is used for issuing tokens.
A User map exists which can be instantiated and stored easily by the register-user function:
(register-user login password name url)
Stores are used to store tokens and will be used to store clients and users as well.
There is a generalized protocol called Store and currently a simple memory implementation used for it.
It should be pretty simple to implement this Store with redis, sql, datomic or what have you.
It includes a simple Redis implementation based on Carmine.
The stores used by the various parts are defined in an atom for each type. reset! each of them with your own implementation.
The following stores are currently defined:
To use the redis store add the following to your code:
(def server-conn
{:pool {}
:spec {:host "127.0.0.1"
:port 6379
:db 14}})
(reset! token-store (create-redis-store "tokens" server-conn))
(reset! auth-code-store (create-redis-store "auth-codes" server-conn))
(reset! client-store (create-redis-store "clients" server-conn))
(reset! user-store (create-redis-store "users" server-conn))
There is currently a single authorization-handler that handles authorization called authorization-handler. Install it in your routes by convention at "/authorize" or "/oauth/authorize".
(defn routes [req]
(case (req :uri)
"/authorize" ((authorization-handler) req)
((require-bearer-token! handler) req)))
Authorization handler comes with defaults that use the various built in token, user etc. stores. You can override these by passing in a configuration map containing functions.
(authorization-handler {:authorization-form authorization-form-handler
:client-lookup clauth.client/fetch-client
:token-lookup clauth.token/fetch-token
:token-creator clauth.token/create-token
:auth-code-creator clauth.auth-code/create-auth-code})
There is currently a single token-handler that provides token issuance called token-handler. Install it in your routes by convention at "/token" or "/oauth/token".
(defn routes [req]
(case (req :uri)
"/token" ((token-handler) req)
((require-bearer-token! handler) req)))
Token handler comes with defaults that use the various built in token, user etc. stores. You can override these by passing in a configuration map containing functions.
(token-handler {:client-authenticator clauth.client/authenticate-client
:user-authenticator clauth.user/authenticate-user
:token-creator clauth.token/create-token
:auth-code-revoker clauth.auth-code/revoke-auth-code!
:auth-code-lookup clauth.auth-code/fetch-auth-code })
One of the ideas of this is using OAuth tokens together with traditional sessions based authentication providing the benefits of both. To do this we create a new token when a user logs in and adds it to the session.
Why is this a good idea?
To use this make sure to wrap the session middleware. We have a login handler endpoint that could be used like this:
(defn routes [master-client]
(fn [req]
(case (req :uri)
"/login" ((login-handler master-client) req)
((require-bearer-token! handler) req))))
The master-client is a client record representing your own application. A default login view is defined in clauth.views/login-form-handler but you can add your own. This just needs to be a ring handler presenting a form with the parameters "username" and "password".
(defn routes [master-client]
(fn [req]
(case (req :uri)
"/login" ((login-handler my-own-login-form-handler master-client) req)
((require-bearer-token! handler) req))))
A mini server demo is available. It creates a client for you and prints out instructions on how to issue tokens with curl.
lein run -m clauth.demo
The goal is to implement the full OAuth2 spec. The only main feature missing is. I'll aim for that for 1.1 as most people currently don't use refresh tokens:
You will need to have a Redis database running in the background in order to have some of the tests pass, otherwise, you will get an error about the connection being refused.
If you have Homebrew on Mac OSX, you can get Redis by typing brew install redis
in the command line. Once that's done, get the Redis database started in your Terminal window by typing the following:
redis-server /usr/local/etc/redis.conf
Copyright (C) 2012 Pelle Braendgaard http://stakeventures.com
Distributed under the Eclipse Public License, the same as Clojure.